Mesh picking material TSL factory — NodeMaterial counterpart to
MESH_PICK_SOURCE in shaders.ts.
Fragment output (the shared RGBA32F pick encoding):
R: nodeId (uniform)
G: elementId LOW 16 bits (= the VERTEX ordinal — mesh has no ordering
attribute to indirect through, §6.5)
B: brightness = the coverage vAlpha * uOpacity, or 1.0 for a cutout
survivor (which the visual shader draws fully opaque), times the shared
perspective near fade — so pick salience tracks visible salience right up
to the near plane, and a fragment below the 0.01 reject is discarded
A: the same elementId's HIGH 16 bits (one f32 channel cannot carry the
whole index exactly — see luxarElementIdSplit)
Depth = 1 / (1 + brightness) (brightest wins), or the real fragment depth when
uSurfaceDepth == 1 (the depth-ordered opaque/normal surface modes:
front-most wins). Both selectors are runtime uniforms, so a layers-panel
blending-mode switch never rebuilds this graph.
The flat-interpolation requirement is not optional here
vElementId and vNodeId use flat interpolation, following the LINE pick
precedent rather than the point pick. The point pick omits flat
and gets away with it only because a single-instance quad's four corners all
carry the same id, making interpolation the identity. A mesh is an indexed,
shared-vertex draw where vertexIndex differs at every corner: interpolated, the
id would arrive FRACTIONAL and the readback's Math.round would resolve to
arbitrary unrelated vertices.
WGSL's @interpolate(flat) samples the FIRST vertex of the primitive where
OpenGL ES fixes it to the LAST, so the exact corner would differ by backend
unless aligned: WEBGL_provoking_vertex aligns them where available (see
./material.ts), and where it is absent PickingSystem instead rotates GL's
indexed triples so the last vertex is the original first corner — WebGL and
core WebGPU therefore always agree exactly. Compatibility-mode WebGPU
disallows first sampling altogether, so it samples either corner
instead; the id reported there satisfies only §6.5's weaker "a corner of
the front-most triangle" contract, not an exact match.
Mesh picking material TSL factory — NodeMaterial counterpart to
MESH_PICK_SOURCEinshaders.ts.Fragment output (the shared RGBA32F pick encoding):
vAlpha * uOpacity, or 1.0 for a cutout survivor (which the visual shader draws fully opaque), times the shared perspective near fade — so pick salience tracks visible salience right up to the near plane, and a fragment below the 0.01 reject is discardedluxarElementIdSplit)Depth =
1 / (1 + brightness)(brightest wins), or the real fragment depth whenuSurfaceDepth == 1(the depth-orderedopaque/normalsurface modes: front-most wins). Both selectors are runtime uniforms, so a layers-panel blending-mode switch never rebuilds this graph.The flat-interpolation requirement is not optional here
vElementIdandvNodeIduse flat interpolation, following the LINE pick precedent rather than the point pick. The point pick omitsflatand gets away with it only because a single-instance quad's four corners all carry the same id, making interpolation the identity. A mesh is an indexed, shared-vertex draw wherevertexIndexdiffers at every corner: interpolated, the id would arrive FRACTIONAL and the readback'sMath.roundwould resolve to arbitrary unrelated vertices.WGSL's
@interpolate(flat)samples the FIRST vertex of the primitive where OpenGL ES fixes it to the LAST, so the exact corner would differ by backend unless aligned:WEBGL_provoking_vertexaligns them where available (see./material.ts), and where it is absent PickingSystem instead rotates GL's indexed triples so the last vertex is the original first corner — WebGL and core WebGPU therefore always agree exactly. Compatibility-mode WebGPU disallowsfirstsampling altogether, so it sampleseithercorner instead; the id reported there satisfies only §6.5's weaker "a corner of the front-most triangle" contract, not an exact match.