On Nanite meshes, placing a VertexInterpolator node in a material graph without using it (unconnected, or routed through a disabled static switch) silently breaks the Opacity Mask and the material rasterizes as fully opaque. Connecting at least one interpolator restores correct masking. Non-Nanite paths are unaffected.
Turning off r.Material.UseShaderCompilationParameters fixes the issue therefore I suspect that it was introduced in CL 51712304 “Fix conservative parameters leading to excessive amount of shaders being compiled.” submitted by @cristian.bobescu
Root cause summarized by LLM (unverified):
Two independent sources disagree on whether the material uses a vertex interpolator:
- Presence - FMaterialCachedExpressionData::bHasVertexInterpolator is set by mere node presence in FMaterialCachedExpressionData::UpdateForExpression, it does not trace connectivity or evaluate static switches.
- Actual usage - FMaterialCompilationOutput::bUsesVertexInterpolator is set only when the interpolator is genuinely compiled into the pixel shader.
With r.Material.UseShaderCompilationParameters=1, the two diverge:
- Shader compilation uses usage: InitializeShaderLayoutParamsFromCompOutput sets bHasVertexInterpolator = InCompilationOutput.bUsesVertexInterpolator. IsVertexProgrammable therefore decides the material is not vertex-programmable, so only the non-vertex-UV raster permutation is compiled.
- Runtime bin/relevance selection uses presence: FMaterialResource::HasVertexInterpolator() returns the cached-expression flag -> MaterialRelevance.bUsesVertexInterpolator -> RasterPipeline.bVertexUVs, and PackMaterialBitFlags.
The runtime requests a vertex-UV / vertex-programmable Nanite raster+shading bin that was never compiled, the lookup misses the programmable permutation, and rasterization falls back to the fixed-function opaque path - which never runs the pixel discard. The pre-existing comment at NaniteShared.h:470-473 already notes this class of conservative-flag/static-switch mismatch.
[Attachment Removed]