Procedural Landscape Layers

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.