GLSL3 picking shader source for meshes + the ShaderSource record.
The simplest of the four pick shaders, and the only one whose element id is a
built-in rather than an attribute. Three structural differences from its
siblings, all from spec §6.5:
No aSortedIndex indirection. Mesh depth sorting permutes whole index
triples, so there is no draw-slot → storage-slot mapping and no
luxarElementIdParts() call. The element id is gl_VertexID, split by the same shared
luxarElementIdSplit() the sorted-index helper uses, so both paths agree
with voteWinner's recombination by construction.
Vertex granularity, not element granularity. The draw is indexed
(faces is the index buffer), so under drawElementsgl_VertexID is the
ordinal of the vertex in the vertices array — a stable per-vertex id.
gl_VertexID / 3 would be meaningless (shared vertices break it, and WebGL2
has no gl_PrimitiveID), and a FACE ordinal would be renumbered on every
slice change, since §5.4 rewrites only the index buffer. A vertex ordinal is
invariant across slices, which is why it is the chosen granularity — and it
indexes the per-vertex label CSR (§3.2) directly.
No quad expansion, and only half the camera uniforms. A mesh has no
screen-space footprint to size, so this pair binds no uResolution and no
focal length. It does bind uNearCull and the wrapper IS a
CameraAwareMaterial — matching the visual mesh material, whose near fade
the pick coverage has to reproduce or a fading surface would stay fully
pickable (#1431).
Both mode-dependent behaviours are runtime uniforms, not defines, so
mesh-pick stays a single codegen variant and a blending-mode switch from the
layers panel never recompiles the pick program:
Uniform
Set from
Effect
uSurfaceDepth
isNormalMode(mode) || isOpaqueMode(mode)
real projected depth (front-most wins) vs brightness-as-depth (brightest wins)
uAlphaCutout
isOpaqueMode(mode)
apply the visual shader's identical a < uAlphaCutoff discard
LUXAR_MESH_PICK_BASE_COLOR_TEX
the node has a texture
sample the texture's ALPHA into coverage, so cutout holes are unpickable
The surface-depth VALUE matches across backends — despite how the snapshot reads
Under uSurfaceDepth == 1 this shader writes gl_FragCoord.z. Its TSL twin
writes pickFragmentDepthTSL() (../_shared/pick-depth-tsl.ts), which selects per
draw, from the projection matrix, viewZToPerspectiveDepth(positionView.z, near, far) — exactly the hyperbolic window-space depth gl_FragCoord.z is — or
viewZToOrthographicDepth, exactly the orthographic gl_FragCoord.z. So the two
backends write the SAME value per fragment, and cross-node depth comparisons —
including against the commutative modes' 1 / (1 + brightness) fragments sharing
this buffer — resolve identically. (three's own depth node made that choice from
the camera a graph was BUILT under, and three reuses a build across cameras, so
graphs built under different camera kinds disagreed after a perspective/ortho
switch; every pick family now shares the helper.)
The codegen snapshot mesh-pick.fragment.glsl.txt therefore carries both
expansions behind a select on the projection matrix's [3][3] element.
Source-of-truth for GLSL3; the WebGPU counterpart lives in ./pick.tsl and is
referenced through the ShaderSource.webgpu factory below.
GLSL3 picking shader source for meshes + the
ShaderSourcerecord.The simplest of the four pick shaders, and the only one whose element id is a built-in rather than an attribute. Three structural differences from its siblings, all from spec §6.5:
aSortedIndexindirection. Mesh depth sorting permutes whole index triples, so there is no draw-slot → storage-slot mapping and noluxarElementIdParts()call. The element id isgl_VertexID, split by the same sharedluxarElementIdSplit()the sorted-index helper uses, so both paths agree withvoteWinner's recombination by construction.facesis the index buffer), so underdrawElementsgl_VertexIDis the ordinal of the vertex in theverticesarray — a stable per-vertex id.gl_VertexID / 3would be meaningless (shared vertices break it, and WebGL2 has nogl_PrimitiveID), and a FACE ordinal would be renumbered on every slice change, since §5.4 rewrites only the index buffer. A vertex ordinal is invariant across slices, which is why it is the chosen granularity — and it indexes the per-vertex label CSR (§3.2) directly.uResolutionand no focal length. It does binduNearCulland the wrapper IS aCameraAwareMaterial— matching the visual mesh material, whose near fade the pick coverage has to reproduce or a fading surface would stay fully pickable (#1431).Both mode-dependent behaviours are runtime uniforms, not defines, so
mesh-pickstays a single codegen variant and a blending-mode switch from the layers panel never recompiles the pick program:uSurfaceDepthisNormalMode(mode) || isOpaqueMode(mode)uAlphaCutoutisOpaqueMode(mode)a < uAlphaCutoffdiscardLUXAR_MESH_PICK_BASE_COLOR_TEXThe surface-depth VALUE matches across backends — despite how the snapshot reads
Under
uSurfaceDepth == 1this shader writesgl_FragCoord.z. Its TSL twin writespickFragmentDepthTSL()(../_shared/pick-depth-tsl.ts), which selects per draw, from the projection matrix,viewZToPerspectiveDepth(positionView.z, near, far)— exactly the hyperbolic window-space depthgl_FragCoord.zis — orviewZToOrthographicDepth, exactly the orthographicgl_FragCoord.z. So the two backends write the SAME value per fragment, and cross-node depth comparisons — including against the commutative modes'1 / (1 + brightness)fragments sharing this buffer — resolve identically. (three's owndepthnode made that choice from the camera a graph was BUILT under, and three reuses a build across cameras, so graphs built under different camera kinds disagreed after a perspective/ortho switch; every pick family now shares the helper.)The codegen snapshot
mesh-pick.fragment.glsl.txttherefore carries both expansions behind a select on the projection matrix's[3][3]element.Source-of-truth for GLSL3; the WebGPU counterpart lives in
./pick.tsland is referenced through theShaderSource.webgpufactory below.