Add support for all eight UV sets in StandardMaterial - #9376
Conversation
StandardMaterial silently clamped every *MapUv property to 0 or 1, so a map assigned to UV2 or higher sampled UV1 instead. glb files with more UV sets loaded fine since #3591, but their materials rendered wrong. - VertexFormat tracks the texture coordinate sets it contains in a bit mask, exposed through hasUv(index); the undocumented hasUv0/hasUv1 are removed. - The standard material gates each map on the vertex format the mesh instance already passes to getShaderVariant, and the mesh instance folds the uv mask into its shader variant hash, so no further shader-def bits are needed. - The lit vertex shader emits sets 2 to 7 through looped includes (uvSetAttributeVS, uvSetVS, uvSetVaryingVS), keeping uv0VS and uv1VS as they are. - Adds a hidden multi-uv test example rendering all eight sets, and unit tests for the vertex format and the generated shaders. Fixes #3975
Build size reportThis PR changes the size of the minified bundles.
|
mvaligursky
left a comment
There was a problem hiding this comment.
Automated PR review by Codex (GPT-5).
I found one blocking correctness issue in the new high-UV path; see the inline comment. The fixed semantic locations collide with attributes used by hardware instancing and MSDF, so some newly advertised UV sets cannot be used with those existing features.
The current Build Examples Browser check is also failing because examples/src/examples/test/multi-uv.example.mjs does not pass Prettier (the long VertexIterator#set and IndexBuffer calls need formatting).
Validation performed: the focused VertexFormat / multi-UV / parallax tests pass (28 tests), the full unit suite passes (2872 passing, 2 pending), git diff --check passes, and an additional WGSL preprocessing probe for UV6 passed. I also inspected the generated example thumbnail. I did not run controlled WebGL/WebGPU pixel-equivalence captures, so visual equivalence for the existing UV0/UV1 paths is not independently proven here.
| for (let i = 0; i < useUv.length; i++) { | ||
| if (useUv[i]) { | ||
| vDefines.set(`UV${i}`, true); | ||
| attributes[`vertex_texCoord${i}`] = `TEXCOORD${i}`; |
There was a problem hiding this comment.
[P1] Resolve attribute-location collisions for high UV sets
When a map selects UV6 or UV7, this adds TEXCOORD6 / TEXCOORD7 to the shader. Those semantics are fixed to locations 11 / 12, but the default instancing path above adds instance_line1 / instance_line2 as ATTR11 / ATTR12, which use the same locations. WebGL binds both active attributes to one location and WebGPU emits duplicate @location entries, so an instanced StandardMaterial sampling UV6/7 cannot create a valid shader/pipeline. UV3/4 have the same problem with the MSDF ATTR8 / ATTR9 inputs. Please add a compatible location-allocation strategy (or explicitly constrain the supported combinations) and cover these cases in tests.
StandardMaterialsampled texture maps from UV sets 0 and 1 only: every*MapUvabove 1 was silently clamped to 1, so glb files carrying more sets (loaded since #3591) rendered with the wrong coordinates. Maps can now use any of the eight UV sets the mesh provides.Fixes #3975
Changes:
StandardMaterialaccepts*MapUvvalues from 0 to 7 for every map, includinglightMapUv. A map is still only sampled when the mesh provides the set it is assigned to, now checked for every set rather than just the first two, so a glb material pointing at a missing set is dropped instead of producing an invalid pipeline on WebGPU.VertexFormattracks the texture coordinate sets it contains in a bit mask and exposeshasUv(index). The undocumentedhasUv0/hasUv1fields are removed in its favour.SHADERDEF_*bits were needed, which matters because the toggle half of the shader defs is full.uv0VSanduv1VSare unchanged, so nine-slicing and existing chunk overrides keep working.*MapUvdocs state the valid range, and the class description explains that a map assigned to a set the mesh lacks is ignored.API Changes:
VertexFormat#hasUv(index), returning whether the format containsSEMANTIC_TEXCOORD0 + index.uvSetAttributeVS,uvSetVSanduvSetVaryingVS.StandardMaterial*MapUvproperties accept 0 to 7 instead of 0 to 1.Examples:
test/multi-uvexample. Each box carries all eight UV sets, with set N mapping onto atlas cell N; the top row samples the diffuse map from sets 0 to 7, the bottom row covers a transformed map on UV3, two maps from two sets in one shader, a lightmap on UV5, and a material asking for a set the mesh does not have.Notes:
material.getShaderVariantdirectly must passvertexFormat, since UV presence is now read from it. The parallax test is updated accordingly.Meshstream API produces a non-interleaved buffer, which needs one WebGPU vertex buffer slot per stream and exceeds WebGPU's limit of 8. The example builds an interleaved buffer by hand; glb meshes are interleaved and unaffected.