Luxar Viewer API Documentation - v2026.9.22
    Preparing search index...
    MESH_VERTEX_SHADER: "\n precision highp float;\n\n \nbool isInvalidFloat(float v) {\n return isnan(v) || isinf(v);\n}\n\nfloat sanitizePositive(float v, float fallback) {\n return (isInvalidFloat(v) || v <= 0.0) ? fallback : v;\n}\n\nfloat sanitizeNonNegative(float v, float fallback) {\n return (isInvalidFloat(v) || v < 0.0) ? fallback : v;\n}\n\n// Per-element opacity sanitizer: NaN/Inf route to the 1.0 opaque\n// identity (corruption stays LOUD), finite values clamp to [0, 1]\n// (alpha is opacity, never HDR — Python pins the range at write; this\n// guards hand-crafted zarr). The clamp keeps the zero boundary\n// CONTINUOUS (a -1e-4 epsilon vanishes like +0.0 renders, instead of\n// jumping to full opacity) and keeps the value mediump-varying-safe.\nfloat sanitizeAlpha(float v) {\n return isInvalidFloat(v) ? 1.0 : clamp(v, 0.0, 1.0);\n}\n\n\n // Per-vertex colour, always bound (never left to the GL default black —\n // see createMeshDefaultColorAttribute). RGB input arrives as w = 1.0.\n in vec4 color;\n\n #ifdef USE_COLORMAP\n in float aScalar; // per-vertex scalar for the LUT lookup\n uniform sampler2D uColormapTex; // 256x1 LUT texture\n uniform float uScalarMin; // display-range minimum\n uniform float uScalarScale; // 1.0 / (max - min)\n uniform mediump float uInvGamma; // gamma on the VALUE, pre-LUT\n #endif\n\n #ifdef LUXAR_MESH_BASE_COLOR_TEX\n // `uv` needs no declaration: three.js's vertex prefix declares\n // position/normal/uv unconditionally for every program, which is also why\n // `position` and `normal` are used below without appearing here. Declaring it\n // would be a redefinition error.\n out mediump vec2 vUv;\n #endif\n\n out mediump vec3 vColor;\n // SMOOTHLY interpolated, unlike the gsplat shader's `flat out … vAlpha`:\n // a splat's alpha is a per-INSTANCE constant, so interpolating it is a no-op\n // and `flat` documents that. A mesh vertex is not an instance constant — its\n // opacity must vary across the face exactly as the line shader's vAlpha\n // varies along a segment.\n out mediump float vAlpha;\n // VIEW-space position. highp because the fragment stage differentiates it:\n // `cross(dFdx(vViewPos), dFdy(vViewPos))` is the flat-normal fallback, and\n // mediump would quantize the inter-fragment delta into a noisy normal.\n out highp vec3 vViewPos;\n\n #if !defined(LUXAR_MESH_FLAT_NORMAL) && !defined(LUXAR_MESH_NO_SHADING)\n // VIEW-space normal. The attribute is in the node's local display frame and\n // every other input to the shade term is view-space, so it is carried across\n // by `normalMatrix` (the inverse-transpose of the model-view matrix) HERE,\n // before interpolation. The inverse-transpose is load-bearing rather than\n // pedantic: anisotropic voxel spacing (z != xy) is routine in this domain,\n // and under it the plain model-view linear map skews normals off\n // perpendicular. No vertex-stage normalize — the fragment renormalizes\n // anyway, which absorbs the length change normalMatrix introduces.\n out highp vec3 vNormal;\n #endif\n\n void main() {\n #ifdef LUXAR_MESH_BASE_COLOR_TEX\n vUv = uv;\n #endif\n\n #ifdef USE_COLORMAP\n // The display-range window and gamma shape the scalar VALUE before the LUT\n // lookup, not the resulting colour — same rule as the point/line shaders.\n // Intensity/offset apply POST-LUT in the fragment stage.\n float t = clamp((aScalar - uScalarMin) * uScalarScale, 0.0, 1.0);\n #ifndef LUXAR_GAMMA_ONE\n t = pow(t, uInvGamma);\n #endif\n vColor = texture(uColormapTex, vec2(t, 0.5)).rgb;\n #elif defined(LUXAR_MESH_BASE_COLOR_TEX)\n // Left at white: the base colour comes from a PER-FRAGMENT texture fetch, so\n // there is nothing per-vertex to carry. Not skipped altogether because the\n // fragment stage multiplies by `vColor` unconditionally — that keeps the\n // GOG/gamma tail identical across all three colour sources rather than\n // forking it three ways, and white is the multiplicative identity.\n vColor = vec3(1.0);\n #else\n vColor = color.rgb;\n #endif\n\n // Read unconditionally, including under USE_COLORMAP: the scalar replaces\n // the colour, not the opacity. Sanitized for the same reason the sibling\n // shaders sanitize theirs — alpha is load-bearing (it is the whole coverage\n // term for a mesh, which has no per-element intensity), and a NaN would\n // survive into the cutout comparison as a fragment that never discards.\n vAlpha = sanitizeAlpha(color.a);\n\n vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);\n vViewPos = mvPosition.xyz;\n #if !defined(LUXAR_MESH_FLAT_NORMAL) && !defined(LUXAR_MESH_NO_SHADING)\n vNormal = normalMatrix * normal;\n #endif\n gl_Position = projectionMatrix * mvPosition;\n }\n " = ...

    Vertex stage.

    position, normal and normalMatrix are not declared here — three's ShaderMaterial prefix auto-injects all three (attribute vec3 position;, attribute vec3 normal;, uniform mat3 normalMatrix;, with #define attribute in under GLSL3). Re-declaring any of them is a compile error. color and aScalar are ours and are declared below.

    color is read as a vec4 regardless of how many components the attribute carries: GL fills the missing components of a size-3 attribute with (0, 0, 0, 1), so RGB data supplies the opaque w = 1.0 for free. The 8/16-bit family is padded to 4 components CPU-side instead (mesh-geometry.ts), because three r184's WebGPU backend has no valid 3-component unorm8/unorm16 vertex format — see §6.1.1. Either way there is ONE color attribute and ONE shader that reads it.