Procedural Landscape Layers

I cannot seem to figure this part out.. :frowning: Would you be amenable to sharing a bit of code on how to do this?

Physics works fine, but is binary. Does sampling the grass-layer on the GPU push a gradient or a binary value?

It pushes a gradient. As mentioned, all covered in the docs now, which makes it a lot easier. Specifically, look at the screen shot after the code snippet in step 3. Should make it click for you, otherwise let me know and I can explain more.

1 Like

So I still cannot make this work :rofl:

I had to upgrade to 5.7 to get the grassmasks in PCG, so all good there.

Code pastes/compiles from the snippets above, so all good there.

But I get this:

Error

I did try the ‘GSM_PCGGrass1’ string per the original code, but same error in that it (GSM_PCGGrass1) was not found within the incoming tags.. Even with a 1 fed into the grassmaps within the landscape material, I dunno what else I could be missing..

I’ll try RVTs and sampling those but I am pretty sure I followed the docs?

EDIT: and this is now just-dumb. hitting an undeclared-function usage?

I stepped through the various kernel-types for the custom-node and none of the types has a declaration for that function; wtheck Epic?

I can be obtuse, but I’m just lost or I cannot read.

EDIT 3: Even better, trying to sample from an RVT, I think I am hitting a bug? Is this comment incorrect? It looks like part of the function declaration is borked:

I have this working in both 5.6 and 5.7, so that won’t be an issue. Couple of things I can think of.

  1. Does “Grassy” have Landscape Grass Type asset connected to it in the Landscape material? It just needs to be an empty asset.
  2. I don’t use the Selected Grass Types in the Generate Landscape Textures node. Just leave that array empty and tick Exclude Selected Grass Types.

Perhaps for now, just comment out all HSLS lines other than the bare minimum to read the grass type. Just to get this working.

1 Like

I DID manage to get this working by swapping out the ‘xxxx’ string parm with an index. I need to futz with the z-positioning, I know I can get the height here too, but otherwise data is flowing and stuff is spawning AND respecting the grassmap. Thank you immensely for your help. Going to try the RVT bit again in a bit..

WIERD though… check the following. Despite being commented out, the existence of the string-name still throws an error..? This threw me a loop and I spent a tick chasing nothings down; grrr. Is this just me or expected?? Given this and issue with the commented function, does Epic need to fall on their sword here?

EDIT: yay!

Thanks again for the direction; you helped get me here.

The error in your screen shot about Grassy not being present despite commenting that line out is probably because that line also includes the declaration for Thresh, which it now can’t find, which is probably preventing it from compiling the HLSL at all at which point all sorts of weird things can happen like this.

I was going to say you can sample the landscape height in your HLSL to get the correct z position, but I see you figured that out. Glad you got it working.

I wouldn’t mind moving my landscape grass map math to an RVT as well like you are doing. I just haven’t got around to it. I assume you are using a separate material to do it outside of the landscape material?

The Grassy error is just that, I had a poor screenshot above; it was conflated with another error I was chasing. It presents on it’s own, no matter the configuration of the map-making node or other factors. It does bother me a bit I cannot get it working with a named reference, but index is just as easy to use..

The landscape is the standard render-stuff-to-RVTs and sample-from-same. I render height-information as well as alpha for the various types of terrain: rock, stoney, gravel, dirt, soil, and multiple grass-logics for basic coverage, flowers, and whatnot. Throw them into the RVTs and then sample those same alphas in the same landscape material for the paint/presentation layer post-RVT.

The intent with this approach is to make the alpha/landscape-info I cache widely-available to other objects, and with PCG it seem to only be getting better and better. Originally it was for performance, but I get a better paint-pass too since I can sample textures at more detailed resolution vs having to sink resources into a 4MTex RVT. Things are better looking, sharper, etc w/no color-drift as you might get from an RVT.. With this setup I can run a 512KTex RVT for the alpha-maps for a 4k landscape, and 1MTex for displacement on same. Vs pushing PBR information that might just-start looking good at 4MTex. :face_with_diagonal_mouth:

Now that I have the PoC working for GPU-grass, I’ll try the RVT path to see what does what for how-much. Whatever gets the sample-count in the landscape to the lowest-level possible will likely win. Either way, just moving to GPU makes TONS of grass practically instantaneous, so I think we’re ‘there’ in that regard in that grass/foliage might just be a non-issue going forwards..

I’ll update the thread w/my results.

Question back to you/the-thread: is there a way to use HLSL to add/push an attribute to a point? I want to add a few random-seed values w/the data-packer on the static mesh spawner, ultimately to use in the object’s material-shader. Currently I can do so w/add attribute but that pushes GPU-data back down to the CPU and then it has to go back up to the GPU to spawn the meshes; less than ideal.

I think the (current?) answer is ‘no’, but to put the question out there..

Again, thanks for your input/help. Getting this working really does make a certain kind of problem just go away.. :smiley:

Yes/No. There doesn’t seem to be a way to create an attribute, but one can mod an existing attribute or property.

The limitation is that currently, it seems, the GPU InstanceDataPacker doesn’t support using a property; something prefixed by a $:

LogPCG: Error: [PCGRuntimePartitionGridActor_25600_7_-2 - Anonymous task]: Attribute ‘$Color.R’ is invalid. GPU instance packer implementation currently only supports basic attributes.

One can disable the static-mesh spawner from the GPU to allow proper packing & doesn’t add a perceptible amount of overhead, so it seems OK (for today)..

Got it working with custom-attributes off the GPU. The item is buried in the UI:

EDIT:

Yup, best of both worlds. I can use the alpha-layer in my RVT to act as the grassmask. (Essentially) the same maths would be feeding into the gass-output node in the landscape material, but at the cost of 3 texture-samplings (in my particular case). Per-layer, if I had a few masks, it would add up; no-go for me. With this method, I don’t even need a gass-node, so there’s 0cost there.. :smiley:

I already pack multiple alphas into a single channel in the RVT, so its easy-enough to replicate that bit of the shader to unpack it in the Custom node, use it in place of the regular grassmask, and voilà! Same function, same gradients, etc, but at less cost in the shader; so win-win.

Summary

CODE

float3 Min = GetComponentBoundsMin();
float3 Max = GetComponentBoundsMax();
float Offset = 250;

// compute position first so we have our set of just-points
float3 Position = CreateGrid2D(ElementIndex, NumPoints, Min, Max);
uint Seed = ComputeSeedFromPosition(Position);

// sample the aphas RVT
bool bInsideVolume;
float3 BaseColor;
float Specular;
float Roughness;
float WorldHeight;
float3 Normal;
float Displacement;
float Mask;
float4 Mask4;
VT_SampleVirtualTexture(‘rvt_a_4k_256’, Position, bInsideVolume, BaseColor, Specular, Roughness, WorldHeight, Normal, Displacement, Mask, Mask4);

// cull based on threshold, removes points w/o a
// value for the grassmask, points not on the mask
float Density = FRand(Seed);
//float Thresh = GrassMaps_SampleWorldPos(0, Position.xy).x;
float Thresh = BaseColor.z;
if ((Density + 0.1) > Thresh)
{
Out_RemovePoint(Out_DataIndex, ElementIndex);
}

// Randomize the XY position
Position.xy += (float2(FRand(Seed), FRand(Seed)) - 0.5) * Offset;
// snap the Z position to the landscape surface
Position.z = LS_GetHeight(Position);
Out_SetPosition(Out_DataIndex, ElementIndex, Position);

// set rotation
FQuat Rot = QuatFromAxisAngle(float3(0., 0., 1.), FRand(Seed)*6.28);
Out_SetRotation(Out_DataIndex, ElementIndex, Rot);

// set random values for data-packer
uint Seed2 = ComputeSeedFromPosition(Position);
// uint Seed3 = ComputeSeedFromPosition(Position.zyx);
// uint Seed4 = ComputeSeedFromPosition(Position.yzx);
// Out_SetColor(Out_DataIndex, ElementIndex, float4(FRand(Seed), FRand(Seed2), FRand(Seed3), FRand(Seed4)));

// set scaling
float ScalingXY = lerp(0.65, 1.15, (FRand(Seed)+(Thresh*Thresh)));
float ScalingZ = lerp (0.65, 2.0, (FRand(Seed2)+Thresh));
Out_SetScale(Out_DataIndex, ElementIndex, float3(ScalingXY, ScalingXY, ScalingZ));

// needs an existing attribute to mod, cannot create inside HLSL
Out_SetFloat(Out_DataIndex, ElementIndex, ‘Randy’, FRand(Seed2));

Swap out ‘rvt_a_4k_256’ for your RVT/alphas source.

For me, the performance is locked at 0.00ms → 0.01, although sometimes, like my dear friend Bender, I see a 2..

Performance is good:

Grass is like the saturate of features, (practically) free to spawn/manage.. Hopefully this helps you.