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 has no per-triangle depth sort (§9),
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 three's depth node, whose DEPTH scope is camera-aware at build time:
a perspective camera expands to viewZToPerspectiveDepth(positionView.z, near, far) — exactly the hyperbolic window-space depth gl_FragCoord.z is — and an
orthographic camera to viewZToOrthographicDepth, which is 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 - brightness fragments sharing this buffer — resolve identically.
Recording this because mesh-pick.fragment.glsl.txt is easy to misread as a
divergence: the snapshot expands depth to the LINEAR
(positionView.z + cameraNear) / (cameraNear - cameraFar) form only because the
codegen harness renders with an ORTHOGRAPHIC camera (see
tsl-harness/mesh.ts), for which that linear form IS gl_FragCoord.z — it is
not the general perspective expansion. The same GLSL-gl_FragCoord.z-vs-TSL-
depth pairing already ships in the gsplat pick shaders, where it is
undocumented.
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 has no per-triangle depth sort (§9), 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 writes three'sdepthnode, whoseDEPTHscope is camera-aware at build time: a perspective camera expands toviewZToPerspectiveDepth(positionView.z, near, far)— exactly the hyperbolic window-space depthgl_FragCoord.zis — and an orthographic camera toviewZToOrthographicDepth, which is 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 - brightnessfragments sharing this buffer — resolve identically.Recording this because
mesh-pick.fragment.glsl.txtis easy to misread as a divergence: the snapshot expandsdepthto the LINEAR(positionView.z + cameraNear) / (cameraNear - cameraFar)form only because the codegen harness renders with an ORTHOGRAPHIC camera (seetsl-harness/mesh.ts), for which that linear form ISgl_FragCoord.z— it is not the general perspective expansion. The same GLSL-gl_FragCoord.z-vs-TSL-depthpairing already ships in the gsplat pick shaders, where it is undocumented.Source-of-truth for GLSL3; the WebGPU counterpart lives in
./pick.tsland is referenced through theShaderSource.webgpufactory below.