Draw technique for rendering point cloud depth into the pick framebuffer.

Hierarchy (View Summary)

Constructors

Properties

Methods

buildFragmentShader buildVertexShader destroy drawBatch drawMesh fsCode fsColorDeclarations fsDrawDepthDeclarations fsDrawDepthLogic fsDrawFlatColorDeclarations fsDrawFlatColorLogic fsDrawSAODeclarations fsDrawSAOLogic fsDrawShadowDeclarations fsDrawShadowLogic fsEdgeFadeDeclarations fsEdgeFadeLogic fsEmit fsHatchDeclarations fsHatchLogic fsHeader fsLambertShadingDeclarations fsLambertShadingLogic fsLogDepthDeclarations fsLogDepthLogic fsMainBegin fsMainEnd fsOutputColor fsPickMeshDeclarations fsPickMeshLogic fsPointsDeclarations fsPointsGeometryLogic fsPrecisionDeclarations fsSilhouetteDeclarations fsSilhouetteLogic fsSlicingDeclarations fsSlicingLogic fsSnapDeclarations fsSnapLogic fsSRGBEncodeColor fsThickLineDeclarations fsThickLineDiscardOutside fsThickLineLogic init vsCode vsCommonDeclarations vsDrawDepthDeclarations vsDrawDepthLogic vsDrawFlatColorDeclarations vsDrawFlatColorLogic vsDrawShadowDeclarations vsDrawShadowLogic vsDrawVertexColorDeclarations vsDrawVertexColorLogic vsEdgeDepthBiasLogic vsEdgeFadeDeclarations vsEdgeFadeLogic vsEmit vsHatchDeclarations vsHatchLogic vsHeader vsLambertShadingDeclarations vsLambertShadingLogic vsLogDepthDeclarations vsLogDepthLogic vsMainBegin vsMainEnd vsPickDeclarations vsPickDepthLogic vsPickMainBegin vsPickMeshLogic vsPointsDeclarations vsPointsFilterLogicCloseBlock vsPointsFilterLogicOpenBlock vsPointsGeometryLogic vsShadowDepthLogic vsShadowSharedDeclarations vsSilhouetteDeclarations vsSilhouetteLogic vsSlicingDeclarations vsSlicingLogic vsSnapDeclarations vsSnapLogic vsThickLineDeclarations vsThickLineMain webglContextLost webglContextRestored

Constructors

  • Creates a new DrawTechnique.

    Parameters

    • renderContext: RenderContext
    • gpuMemoryReader: GPUMemoryReader
    • cfg: {
          edges?: boolean;
          hasNormals?: boolean;
          hasUVs?: boolean;
          logDepth?: boolean;
          picking?: boolean;
          snap?: 0 | 1 | 2 | 3;
          thickLines?: boolean;
          triplanar?: boolean;
      } = ...
      • Optionaledges?: boolean
      • OptionalhasNormals?: boolean
      • OptionalhasUVs?: boolean
      • OptionallogDepth?: boolean

        Permutation flag. When true, the technique's vertex shader rewrites gl_Position.z so the depth-buffer mapping becomes logarithmic in view-space distance — same trick Cesium / Three.js use to get usable depth resolution across scenes that span huge distance ranges (UTM-scale terrain + close-up BIM, archipelagos, infinite landscapes). Done vertex-side so early-Z stays on; mid-triangle depth is linearly interpolated (fine for typical BIM-shaped meshes).

        Default false. Picking / snap / shadow-depth techniques deliberately stay linear; their depth read-back math would have to grow a log2 term to match.

      • Optionalpicking?: boolean
      • Optionalsnap?: 0 | 1 | 2 | 3
      • OptionalthickLines?: boolean
      • Optionaltriplanar?: boolean

    Returns PointsPickDepth

Properties

_program: WebGLProgram
_renderContext: RenderContext
edges: boolean

When true, the technique binds silhouette-related uniforms using "edge" material settings (edgeColor/edgeAlpha) instead of fill settings (fillColor/fillAlpha).

Used by silhouette-like techniques that can render both filled silhouettes and edge silhouettes.

errors: string[]

Compilation errors encountered during program initialization. Available after init() is called.

fragmentShaderCommentedSrc: string

Fragment shader source code with comments included.

Note that comments are not supported in WebGL shader compilation, so this is for debugging/inspection purposes only.

Available after init() is called.

fragmentShaderSrc: string

Fragment shader source code. Available after init() is called.

hasNormals: boolean

When true, the technique compiles a vertex normal sampler into its shaders and reads smooth view-space normals from the batch's BatchDataTextures.vertexNormalTexture. When false, the fragment shader derives a flat face normal from dFdx/dFdy(vViewPos).

This is the per-batch axis the renderer dispatches on — only the Lambert colour techniques are paired into {false, true} variants; edge, silhouette, pick and snap techniques keep the single flat-shaded path.

hasUVs: boolean

When true, the technique compiles a vertex UV sampler into its shaders and emits a vUV varying for downstream texture sampling. Independent axis from hasNormals; combined into a 4-way variant lookup on the Lambert colour techniques.

logDepth: boolean

When true, the technique's compiled vertex shader rewrites gl_Position.z for a logarithmic depth-buffer mapping. See the constructor's logDepth config field for the permutation contract and which techniques opt in.

picking: boolean

When true, the technique binds uniforms for picking rendering (e.g., pickZNear/pickZFar) and uses picking-specific draw ranges from the batch's view data textures.

Used by the pick rendering pass to render meshes with unique pick colors and output depth for picking.

snap: 0 | 1 | 2 | 3

Snap-pass mode flag.

  • 0 (default) — not a snap pass.
  • 3 — snap-init pass: triangles render to populate the snap FBO's depth + view-position outputs (uses gl.TRIANGLES, same index buffer as colour pass).
  • 2 — edge snap: edge-index buffer drawn as gl.LINES.
  • 1 — vertex snap: every unique vertex drawn as a 1-pixel gl.POINTS (no index buffer).

Snap techniques bind the shared snap uniforms (drawing-buffer size, snap clip-pos centre, snap z-range — same shape as the picking uniforms) and write view-space position into an RGBA32F MRT target the renderer scans on read-back.

thickLines: boolean

When true, line draws are quad-expanded in the vertex shader (six vertices per line, two triangles forming a screen-space aligned rectangle of width uLineWidth pixels) so they render at user-controlled thickness across every WebGL2 implementation — including ones that clamp gl.LINES to a single pixel (notably ANGLE on Windows).

Only consulted on the lines path of _draw: it switches the LinesPrimitive draw call from gl.drawArrays(gl.LINES, …, n * 2) to gl.drawArrays(gl.TRIANGLES, …, n * 6). Triangle / edge / snap / point draws are unaffected.

Mutually exclusive with edges / picking / snap for now — the thick-line shader assumes a LinesPrimitive index pair per primitive. Adding thick edges or thick pick lines is mechanical but currently out of scope.

triplanar: boolean

When true, the technique samples the per-batch albedo / metallic-roughness / normal-map atlases via triplanar world-space projection rather than the vertex vUV attribute. Used for batches whose meshes have textured materials but no UV coordinates — typical of BIM, sweeps and lofted curve geometry.

Mutually exclusive with hasUVs: triplanar variants are constructed with hasUVs: false. Independent axis from hasNormals; combined into a 6-way variant lookup on the Lambert colour techniques ((normals?, uvs?, triplanar?) with uvs && triplanar excluded by construction).

useIndexBuffer: false

When false, vertex positions are addressed directly (no index-buffer lookup). Override to false in point-cloud techniques.

vertexShaderCommentedSrc: string

Vertex shader source code with comments included.

Note that comments are not supported in WebGL shader compilation, so this is for debugging/inspection purposes only.

Available after init() is called.

vertexShaderSrc: string

Vertex shader source code. Available after init() is called.

vertsPerPrim: 1

Number of vertices per primitive: 3 for triangles, 2 for legacy GL_LINES, 6 for thick-line quad expansion, 1 for points. Emitted as a compile-time constant into the vertex shader. Public so introspection tools (the shader inspector, the shaders panel) can surface it alongside the source.

Methods

  • Declares the shadow-map samplers (one per cascade), the per-cascade light-VP array, the cascade split distances, the scalar shadow params, and the PCF / slope-bias data.

    Uniform layout:

    • uShadowMap0..3: sampler2DShadow per cascade. TEXTURE_COMPARE_MODE is set on each depth texture so texture(sampler, vec3(uv, refDepth)) returns the hardware-bilinear PCF comparison (0 = shadow, 1 = lit).
    • uShadowLightVPs[4]: mat4 per cascade, camera-view → cascade light-clip.
    • uShadowCascadeSplits: view-space |z| boundaries between cascades; entry i is the far edge of cascade i. Only entries 0 .. uShadowCascadeCount - 2 are meaningful.
    • uShadowCascadeCount: number of populated cascades in [1, 4].
    • uShadowParams: (intensity, depthBias, texelSize, normalOffsetBias).
    • uShadowSlope: (dirViewX, dirViewY, dirViewZ, slopeBias).
    • uShadowPcfRadius: half-width of the PCF kernel (0 = 1×1, 1 = 3×3…).

    Per-fragment cascade selection happens in fsDrawShadowLogic, so there's no vShadowCoord varying — we transform vViewPos through the chosen cascade's matrix at fragment time instead.

    Returns void

  • Selects the best-fitting cascade for the current fragment (based on its camera-view-space |z|), samples that cascade's shadow map with hardware PCF, and darkens color.

    Per-fragment steps:

    1. Cascade selection from view-space -vViewPos.z against uShadowCascadeSplits.
    2. Compute the light-clip position by applying the chosen cascade's matrix to vViewPos (plus the normal-offset push).
    3. Slope-scaled bias, then the PCF loop (1..7² taps via uShadowPcfRadius).

    vViewPos must be in scope — the shadow-aware techniques always run with Lambert shading which declares it.

    Returns void

  • Multiplies the working color.a by the fade factor.

    smoothstep(start, end, dist) is 0 at dist <= start, 1 at dist >= end. We invert it so near edges keep full alpha and far edges fade to zero. Branch-free: when start >= end, smoothstep collapses to a step at start, which means edges past start simply disappear — that's the documented "set start >= end to disable" path. To keep edges fully opaque always, ship start >= 1.0 AND end >= 1.0.

    Must run AFTER fsSilhouetteLogic (which writes color = vColor) and BEFORE fsOutputColor (which premultiplies and emits the final RGBA).

    Returns void

  • Write gl_FragDepth per pixel using the canonical log-depth formula — gl_FragDepth = log2(vFragDepth) * uLogDepthCoef * 0.5.

    Derivation: the vertex-side scheme used gl_Position.z = (log2(1 + w) * coef − 1) * w which, after the GPU's /w divide, gives an NDC z of log2(1 + w) * coef − 1 ∈ [-1, 1] mapped to the depth buffer's [0, 1] via (z + 1) * 0.5. So the equivalent per-pixel write is gl_FragDepth = log2(1 + w) * coef * 0.5 with 1 + w carried in vFragDepth from the VS.

    The max(1.0e-6, vFragDepth) clamp guards against fragments whose interpolated 1 + w lands ≤ 0 — physically impossible for a fragment in front of the camera, but cheap insurance against a single bad triangle interpolant NaN-ing the depth buffer for the rest of the primitive.

    No-op when logDepth is false. Emit inside the FS main body, typically right before fsMainEnd — the value is independent of color, slicing, etc.

    Returns void

  • Writes the accumulated color variable to the standard fragment output using the premultiplied-alpha convention: (rgb * a, a).

    Paired with the blend func (ONE, ONE_MINUS_SRC_ALPHA) during the transparent pass, this gives correct blending at partial-coverage edges and after any bilinear filtering. For fully-opaque fragments (a = 1), rgb * 1 = rgb — so opaque output is unchanged.

    Pick techniques write directly to MRT outputs and do NOT call this.

    Returns void

  • Discards the fragment if any active section plane clips it. The test is short-circuited two ways for performance —

    1. vClippable == 0u skips the loop entirely for meshes that opted out of clipping (always-visible labels, anchor markers, etc.). The branch is on a flat varying — uniform-coherent across a triangle, so the GPU sees it as a single test per triangle.

    2. uSectionPlaneCount == 0 skips the loop when no planes are active. Branch is on a uniform, so all scenes without active clipping pay one compare and zero iterations.

    Active planes are packed at indices 0..count-1, so the loop bound is uniform and the compiler can emit straight- line code rather than per-iteration active-flag tests.

    Returns void

  • Declares the snap-pass MRT outputs.

    Snap techniques target a single RGBA32F render target carrying view-space position. The init pass (snap === 3) fills the target across triangle surfaces so the subsequent vertex/edge passes z-test against real geometry; the snap pass itself (snap === 1 or 2) only writes where points/lines pass that depth test, so JS can read back and search outward from the cursor for the nearest non-empty texel.

    fsColorDeclarations() is intentionally NOT called by snap techniques — they declare their MRT slot here.

    Returns void

  • Generates fragment-shader logic for the snap pass — emit the high-precision view-space position. The alpha channel is a kind tag so the JS read-back can tell apart:

    • init pass (snap === 3): alpha = 2.0 (surface depth-only)
    • vertex snap (snap === 1): alpha = 1.0
    • edge snap (snap === 2): alpha = 1.0

    Read-back accepts alpha === 1.0 only, so init's surface texels never get mistaken for vertex/edge hits — and crucially we don't have to disable colour writes during init (some GL drivers short-circuit fragment writes when the colour mask is fully off, which also drops the depth-write side-effect).

    Init pass also nudges the surface depth deeper by one pixel of depth gradient (V2's length(vec2(dFdx, dFdy)) trick) so that coplanar vertex/edge fragments in the snap pass reliably pass LEQUAL against the rasterised surface depth. Without this bump, interpolation noise occasionally pushes a visible vertex's depth a hair past the surface's stored depth — the visible vertex fails LEQUAL, doesn't write, and the read-back falls back to whatever back-of-mesh vertices/edges managed to slip through at the rasteriser's edge cases.

    Returns void

  • Inline gamma-2.2 sRGB encode on the working color.rgb. Use when a technique writes straight to the default canvas framebuffer without going through TonemapPipeline (which is the usual sRGB encoder for the scene render) — for example, the overlay-bin pass that runs after PostProcessChain.composite so the gizmo / HUD layer doesn't get tonemapped along with the rest of the scene.

    Matches the same pow(c, 1/2.2) curve TonemapPipeline uses when its uSRGBEncode uniform is set, so a colour authored as (1, 0, 0) shows up identically whether it travelled through tonemap or through this inline encoder. max(c, 0) guards against negative inputs from bad source data — pow of a negative is undefined and yields NaN on some GPUs.

    Called between whichever chunk fills color (typically fsDrawFlatColorLogic) and fsOutputColor.

    Returns void

  • Discards fragments outside the thick-line's rounded-rectangle SDF, without touching any color variable. Used by the pick and snap passes — both rasterise the same quad expansion as the colour pass but write a single MRT (IDs / view position) rather than a blended colour, so the SDF coverage they want is binary "in / out" rather than the soft 1-pixel falloff the colour pass uses.

    Mirrors the SDF derivation in fsThickLineLogic (same varyings, same joint-flag clamp skip) but uses sdf > 0.0 as the discard threshold — keep every fragment whose coverage centre lies within the rounded rectangle, drop the cap-extension corners that sit outside it. Picking the rounded core (not the soft AA halo) is what gives picking / snap a hit area that matches what the user sees as the line's body.

    Emit AFTER fsThickLineDeclarations so the varyings are declared, and BEFORE the pick/snap output is written.

    Returns void

  • Shadow-aware color pass: no-op under CSM. With N cascades the fragment shader has to pick the right cascade per-fragment and transform vViewPos on the fly — precomputing a single vShadowCoord in the vertex stage would force a choice we can't make there. The required varying (vViewPos) is already declared by Lambert shading.

    Kept as a hook so technique subclasses that already call it don't break, and so future shadow techniques (e.g. non-Lambert receivers) can add their own vertex-side setup without changing callers.

    Returns void

  • Pulls the vertex a tiny fraction toward the camera in clip space so that coplanar line/edge geometry wins depth-test ties against the triangle surface it sits on. Subtracting eps * gl_Position.w from gl_Position.z shifts NDC z by exactly eps regardless of vertex distance.

    Kept small on purpose: the depth buffer is non-linear with perspective, so a constant NDC offset covers proportionally more world distance at the far plane than at the near plane. Too large an offset and distant edges start poking through the front of foreground geometry. 2e-5 is still hundreds of times the 24-bit depth-buffer quantum — plenty to win ties from rasterisation noise — while leaving the world-space leakage at the far plane negligible.

    Emit AFTER vsMainBegin (which sets gl_Position) and before vsMainEnd. Intended for edge/line techniques.

    Returns void

  • Pass view-space depth (1.0 + gl_Position.w) through to the FS in the vFragDepth varying. The companion FS snippet (fsLogDepthLogic) writes gl_FragDepth per pixel using the exact log-depth formula, so the depth-buffer value follows the true logarithmic curve regardless of how a triangle stretches across view-space depth.

    This replaces the previous "rewrite gl_Position.z in the VS" implementation, which interpolated the log-depth value linearly across each triangle. The linear interpolation doesn't follow the log curve and produces visible artefacts whenever a triangle's depth range is large — most painfully around the camera plane during walkthroughs, where a single floor/wall/ceiling triangle can span 50 cm to 30 m from the eye and the per-fragment depth lands far enough off the true curve to confuse the clipper. Writing the depth value per pixel from the FS eliminates that interpolation error.

    Trade-off: writing gl_FragDepth disables hardware early-Z for these techniques (the GPU can't cull a fragment by depth before running the FS that determines its depth).

    No-op when logDepth is false.

    Emit before vsMainEnd.

    Returns void

  • Declares the light view-projection matrix uniform used by both the shadow-map depth pass and the shadow-sampling color pass.

    uShadowLightVP is expressed in CAMERA-VIEW space, not world space: it maps camera-space positions to the light's clip space. This is so the shader can apply it to viewPos (which is small, single-precision safe and correct for every RTC tile) instead of worldPos (which is only tile-local and has no true-world meaning for double-precision models).

    Returns void

  • Declares the varyings used by the universal section-plane clip test: world-space fragment position + a per-mesh clippability flag. Declared from every technique's VS so pick / snap / silhouette / edge / colour all share the same clip path — a fragment culled by the colour pass is also culled by the pick pass, the snap pass, etc.

    vWorldPos is shared with the hatch and (optionally) triplanar paths; they read the same varying.

    Returns void

  • Declares the snap-pass uniforms, varyings, and the clip-space remapping helper used to render into the snap framebuffer.

    Mirrors vsPickDeclarations (snap reuses the same "render into a small viewport centred on the cursor" trick) but only emits the varying the snap fragment shader actually consumes — high-precision view-space position. The helper remapSnapClipPos is byte-identical to remapPickClipPos; declared separately so a snap technique can be built without dragging in the pick-only varyings (vBatchIndex, vMeshIndex) that would compile-error if both helpers shared the same uniform names.

    Returns void

  • Declares the thick-line uniforms (uLineWidth, drawingBufferSize) and the per-vertex AA varying. Call once from a thick-line technique's buildVertexShader, after vsCommonDeclarations.

    The thick-line path expands each line into two triangles in the vertex shader. drawingBufferSize converts the pixel thickness into NDC; vSide carries the signed cross-line coordinate (-1 at one edge, +1 at the other) to the fragment shader for antialiasing.

    Parameters

    • skipDrawingBufferSize: boolean = false

      When true, omit the drawingBufferSize uniform declaration. Set this on techniques that already declared the same uniform via vsPickDeclarations or vsSnapDeclarations — GLSL forbids re-declaring a uniform in the same scope.

    Returns void

  • Emits the full thick-line main() body. Pulls both endpoints of the current line, projects each to clip space, computes a screen-space perpendicular, and offsets the chosen endpoint by ±uLineWidth / 2 pixels.

    Stands in for vsMainBegin; the technique's buildVertexShader follows this with any colour / slicing / varying logic the standard path emits AFTER vsMainBegin, then closes with vsMainEnd.

    vertsPerPrim must be 6 on the technique that calls this. The standard _draw switch on LinesPrimitive then routes to gl.TRIANGLES × 6 when this.thickLines is set.

    Returns void