Bind colors as a 4-component color attribute, padding RGB→RGBA if needed.
Why always 4 components for the 8/16-bit family
The TSL materials run on a real WebGPURenderer, and three r184's WebGPU
backend exposes no 3-component 8/16-bit vertex format — its
GPUVertexFormat table lists only unorm8x2/unorm8x4 and
unorm16x2/unorm16x4. WebGPU also requires arrayStride to be a multiple of
4, and three uploads a tightly-packed size-3 attribute with
arrayStride = itemSize · BYTES_PER_ELEMENT — a 3-byte stride for uint8, 6
for uint16. Both fail createRenderPipeline validation, so an RGB
uint8/uint16 mesh renders nothing on the WebGPU backend while looking
fine on WebGL.
Padding to unorm8x4 (4-byte stride) / unorm16x4 (8-byte stride) fixes both
the format and the stride, and the memory win over widening to float32 survives:
4 bytes/vertex for uint8 RGBA against 12 for f32×3.
float32 colors are left alone — float32x3 is a valid WebGPU format with a
12-byte (4-multiple) stride, so they bind at their native 3 or 4 components.
The pad alpha is fully opaque (255 for uint8, 65535 for uint16), both of
which normalize to 1.0. That is the same "1.0 for RGB data" contract the
gsplat and line shaders document; mesh just supplies it CPU-side for the formats
WebGPU refuses at size 3, and leaves it to the size-3 w = 1.0 attribute default
for the ones it accepts.
Bind
colorsas a 4-componentcolorattribute, padding RGB→RGBA if needed.Why always 4 components for the 8/16-bit family
The TSL materials run on a real
WebGPURenderer, and three r184's WebGPU backend exposes no 3-component 8/16-bit vertex format — itsGPUVertexFormattable lists onlyunorm8x2/unorm8x4andunorm16x2/unorm16x4. WebGPU also requiresarrayStrideto be a multiple of 4, and three uploads a tightly-packed size-3 attribute witharrayStride = itemSize · BYTES_PER_ELEMENT— a 3-byte stride foruint8, 6 foruint16. Both failcreateRenderPipelinevalidation, so an RGBuint8/uint16mesh renders nothing on the WebGPU backend while looking fine on WebGL.Padding to
unorm8x4(4-byte stride) /unorm16x4(8-byte stride) fixes both the format and the stride, and the memory win over widening to float32 survives: 4 bytes/vertex foruint8RGBA against 12 forf32×3.float32colors are left alone —float32x3is a valid WebGPU format with a 12-byte (4-multiple) stride, so they bind at their native 3 or 4 components.The pad alpha is fully opaque (
255foruint8,65535foruint16), both of which normalize to1.0. That is the same "1.0 for RGB data" contract the gsplat and line shaders document; mesh just supplies it CPU-side for the formats WebGPU refuses at size 3, and leaves it to the size-3w = 1.0attribute default for the ones it accepts.