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

    Module rendering/materials/_shared/erf

    Shared error-function implementations — the single source of truth for every erf in the VIEWER, CPU and GPU. (The Python package carries its own copy of the same A&S 7.1.26 form in luxar/gsplats/lift.py — the two languages cannot share code, but the constants must stay in sync.)

    Two implementations, each matched to its consumer. The TSL builder for the second one lives next door in erf-tsl.ts so this file stays free of three/tsl — see that module's header for why (issue #1679):

    1. erfRef — the Abramowitz & Stegun 7.1.26 rational approximation (max abs error 1.5e-7). The CPU-side reference: precomputing normalization factors (computeRayIntegralFactor in gsplat/math.ts), LUT generation, and the oracle the unit tests measure erfPoly against. Costs one exp and one division per call — fine on the CPU, the expensive form in a fragment shader.

    2. erfPoly / GLSL_ERF_FUNCTIONS (+ erfPolyTSL in erf-tsl.ts) — a pure odd polynomial on [-3, 3], clamped to ±1 outside. NO exp, NO division: the fragment-shader form, built for the #1352 volumetric line primitive (since deleted; no production shader consumes it today — the parity fixture and codegen snapshot are its consumers, keeping it verified for the next erf-hungry shader lane). Degree-13 constrained least-squares fit with P(3) = 1 (to 1e-9 for the printed coefficient set), so the clamp is continuous. Max abs error 5.4e-4 against the exact erf; the worst error in the difference QUOTIENT (erf(x1) − erf(x0)) / (x1 − x0) is 1.4e-3 absolute (0.13% of its 2/√π ≈ 1.128 peak) when the two arguments are ≥ 0.5 apart (callers with closer arguments need a midpoint/Taylor lane instead of the raw difference).

      Two consequences of this being a least-squares fit rather than a bounded approximation — both well inside that error bound, both easy to trip over: it is NOT clamped to ±1 (it peaks at 1.00032 near |x| ≈ 2.72) and it is NOT strictly monotone near saturation, so an erf(x1) − erf(x0) window with x1 > x0 can come out slightly NEGATIVE (worst −8.8e-4, at x0 ≈ 2.72 / x1 ≈ 2.94). A consumer that needs a non-negative window — an optical depth, a coverage weight — must clamp at zero rather than trust the sign.

    The GLSL string and the TSL builder are BOTH generated from ERF_POLY_COEFFS, so the two backends cannot drift in VALUE. The guarantee is value-level, not textual: GLSL literals are serialized here with toFixed(9), while the TSL path passes the same numbers to float() and Three's code generator owns their formatting (numeric parity is what the tsl-shader-parity harness verifies). Same single-source pattern as falloff.ts / volumetric.ts.

    ERF_POLY_CLAMP
    ERF_POLY_COEFFS
    GLSL_ERF_FUNCTIONS
    erfRef
    erfPoly