Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...

    Module rendering/picking/mesh/shaders

    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:

    1. 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.
    2. Vertex granularity, not element granularity. The draw is indexed (faces is the index buffer), so under drawElements gl_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.
    3. 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

    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.

    MESH_PICK_VERTEX_SHADER
    MESH_PICK_FRAGMENT_SHADER
    MESH_PICK_SOURCE