[5.8] Nanite landscape painted weightmap layers collapse to UV origin

Summary

On a Nanite-enabled landscape, painted weightmap layers render collapsed/shrunk toward each component’s UV origin (the painted pattern almost disappears, leaving the base layer). The same landscape renders correctly with Nanite disabled. This is a regression between 5.7.4 (correct) and 5.8.0 (broken).

Root cause: texcoords that a material uses only in the pixel shader and does not customize are zeroed in the Nanite vertex-factory path, so the landscape weightmap UV (TexCoord3) reaches the pixel shader as (0,0).

What Type of Bug are you experiencing?

Rendering (Graphics / Niagara)

Steps to Reproduce

  1. Open/create a World Partition map with a landscape whose material has painted layers (Landscape Layer Blend / Landscape Layer Weight nodes, i.e. weightmap sampling at TexCoord3).
  2. Paint at least one non-default layer in a recognizable pattern.
  3. On the Landscape → Details → Nanite → Enable Nanite, and build the Nanite data.
  4. Observe the painted layers in the viewport.

Expected Result

Painted layers appear in the same place as with Nanite disabled (they follow the paint).

Observed Result

Painted layers collapse/fade toward each component’s UV origin (toward the SW). The base layer dominates and the painted pattern is largely lost. Toggling Enable Nanite off restores correct paint. A Nanite rebuild does not.

Affects Versions

5.8

Platform(s)

Windows

Upload an image

Additional Notes

Root cause findings

  • The landscape weightmap is sampled at TexCoord3 (FHLSLMaterialTranslator::StaticTerrainLayerWeightTextureCoordinate(3)). The material reads this UV only in the pixel shader and does not expose it as a Customized UV.
  • Generated GetMaterialCustomizedUVs (MaterialTemplate.ush) zero-initialises all NUM_TEX_COORD_INTERPOLATORS entries, then writes only the customized indices [0, NUM_CUSTOMIZED_UVS).
  • In the Nanite path (NaniteVertexFactory.ush, TransformNaniteVerts), all pixel texcoords are delivered to the pixel shader via OutVerts[].CustomizedUVs[] (filled solely by GetMaterialCustomizedUVs, then barycentric-interpolated in GetPixelAttributes). There is no separate raw-texcoord interpolant path as in the raster vertex factories.
  • So non-customized, pixel-only texcoords (weightmap UV3) are zeroed and never refilled from the decoded vertex UVs → the weightmap samples its origin texel everywhere → paint collapses to the UV origin.
  • The raw vertex UVs are decoded correctly (NANITE_NUM_TEXCOORDS_TO_DECODE = max(vertex, pixel) UVs) and the CPU bake writes correct UV0/UV3 → verified. The loss is purely in populating CustomizedUVs.

Confirmation

Adding, right after GetMaterialCustomizedUVs() in TransformNaniteVerts:

OutVerts[i].CustomizedUVs[3] = InVerts[i].TexCoords[3];   // raw decoded UV

fully restores correct painted layers, demonstrating the value is correct up to the customized-UV population stage and is lost only there.

Suggested fix

In the Nanite vertex factory, after GetMaterialCustomizedUVs() / GetCustomInterpolators(), refill the non-customized texcoord interpolators from the decoded vertex UVs for indices [NUM_CUSTOMIZED_UVS, NUM_MATERIAL_TEXCOORDS):

OutVerts[i].CustomizedUVs[idx] = InVerts[i].TexCoords[min(idx, NANITE_MAX_UVS - 1)];

applied at every GetMaterialCustomizedUVs call site in NaniteVertexFactory.ush (and the parallel path in NaniteTranslucencyFactory.ush). This restores pre-5.8 behaviour where raw pixel-only texcoords reach the Nanite pixel shader, without clobbering customized UVs or custom vertex interpolators.