Largest near/far ratio the near-plane floor will allow.
The depth buffer is 24-bit fixed point (three allocates
DEPTH_COMPONENT24 for a render target with no explicit
depthType, and webgl.renderer.logarithmicDepthBuffer is off), so
depth quantization in world units at eye distance d is
Δz(d) = d² · (far − near) / (near · far) · 2⁻²⁴
— inversely proportional to near. Left unbounded, the
inside-the-sphere branch of calculateClippingPlanesFromSphere
pinned near to R · MIN_NEAR_RADIUS_FACTOR (2e-6 · R), i.e. a
ratio near 6e5:1, which puts Δz at ~4e-2 world units on a
diagonal-100 scene viewed from 8.5 units — coarse enough to z-fight
visibly, and (because near tracks camera distance) to POP as the
camera orbits. Only depth-writing geometry is affected (mesh, and
opaque normal-mode geometry — see rendering/blending-state.ts),
which is exactly where it was reported.
Why 1200 specifically. A LARGER C means a smaller floor: less precision,
but less clipped. Two hard constraints put a floor under C, and only a soft
preference (keep precision) pushes from above — and that preference turns
out to be nearly free, so C is set for MARGIN above the binding constraint
rather than at it:
C > 551, don't clip the zoom target under the scale-multiplier floor.
At maximum zoom-in the
orbit target sits at minDistance = 1e-3 · diagonal
(controls.scaleMultipliers) = 1.905e-3 · R, while far ≈ 1.05 · R.
Keeping it in front of the near plane needs 1.05 R / C < 1.905e-3 R.
NOTE this assumes the orbit target is at the sphere CENTRE. Panned onto a
bbox corner (0.952 · R) the general form (dist + R)/C < 1.905e-3 · R
tightens, and for the last ~2% of the zoom-in range the floor overtakes
minDistance. All four types are inside the fade-suppressed region
throughout that band by construction, so at most the sub-1% line residual
described in the next bullet is lost there, and this does not move C.
Auto-framing installs a separate distance / ZOOM_IN_FACTOR limit. The
projected-bounds fit approaches distance / diagonal = 0.5 for a
view-axis-elongated box at the default FOV, raising this target constraint
to about 1102; C = 1200 clears it there, with a 1.14x near-plane margin at
the most extreme supported zoom. At wide FOVs the ratio falls further and
C does not clear this target constraint, as it did not before this change;
see the ZOOM_IN_FACTOR note in camera-framing.ts.
C ≥ 992, keep everything the floor clips inside the near fade, for all
four geometry types. Every one of them already suppresses anything
closer than nearCull = 1e-3 · diagonal via perspectiveNearFade
(materials/_shared/glsl-lib.ts), and the fade is under 0.01 below
1.0589 · nearCull (the root of smoothstep(1, 2, x) = 0.01, solved
rather than eyeballed in tests/.../clipping/_near-fade-model.ts). That
single bound is what the tests assert — but what each type DOES with a
sub-0.01 fade differs, and the difference is the difference between
"lossless" and "very nearly", so it is worth knowing before trusting this:
Points and GSplats REJECT the vertex outright below 0.01 (both backends),
and Mesh discards the FRAGMENT at the same 0.01 (per fragment because a
triangle spans depth; a discard rather than a multiply because it writes
depth — #1431). For those three the floor is exactly lossless: what it
clips was not going to be rasterized at all.
Lines are the one partial case. They cull only when BOTH endpoints are
near, clip a half-near segment onto the nearCull plane, and then apply
the fade PER-FRAGMENT as a plain multiply with no reject of its own —
their discard is a separate max(rgb) < 1e-4 test on the COLOR, which
the fade never enters. So a line fragment inside the clipped band is
attenuated to under 1% of its authored contribution but is not
necessarily zero, and the floor can take it. That residual is a property
of the line shader, not of this constant: no value of C removes it while
still bounding the ratio, it predates #1431, and at ≤1% of one fragment
inside a 0.1%-of-diagonal shell it is not what sets C. Closing it would
mean giving the line shader its own fade reject.
The worst case is NOT the camera on the sphere surface — it is just
OUTSIDE it, at the crossover dist = R · (C+1)/(C-1) ≈ 1.002 · R,
the last distance at which the floor still beats the surface term and
therefore where the floor sits highest relative to nearCull
(fade 0.00755 there versus 0.00725 on the surface). So
2.002 R / C ≤ 1.0589 · 1.905e-3 · R ⟹ C ≥ 992.
992 binds, and C = 1200 clears it by 20.9%. The margin is deliberate,
and it is cheap: going from the minimum-viable 1000 to 1200 gives away
0.03% of the total precision gain (Δz 7.05e-5 → 8.47e-5 world units on
the reported pose, against 4.10e-2 before the bound), and buys survival of
ordinary tuning elsewhere. Measured: a 10% tightening of nearCull needs
C ≥ 1104, and reducing the fade reject headroom to 1.0 needs C ≥ 1051 —
C = 1000 would have become silently LOSSY under either, C = 1200 holds.
The margin is also enforced rather than trusted: the "clipped band is
already shader-rejected" property in bounds-math.property.test.ts
generates the whole floor-binding region INCLUDING that crossover, a
companion test pins the crossover as strictly worse than the surface, and
the arithmetic test asserts the constant survives that 10% nearCull
tightening — so a future change to nearCull, to the fade band, or to this
constant fails there. (Mesh used to be the FULL exception — it had no near fade
in either backend at all, so the floor could clip it at full brightness. Since
#1431 it carries the same fade with the same 0.01 reject and the same
1.0589 · nearCull headroom, which moves it into the exactly-lossless group
with points and gsplats and leaves lines as the only ≤1% residual above.)
The losslessness argument assumes nearCull and this floor are derived from
the SAME bounds, which holds on the metadata and per-frame paths. It can
diverge in one narrow case: on a metadata-less scene SceneBoundsCache.ensure
never populates, so the materials keep _nearCull's 0.1 default while
autoAdjustFromBounds derives its sphere from Box3.setFromObject. On a
large metadata-less scene the floor can then exceed nearCull and clip
geometry of any of the four types that the fade would have drawn. Compiled
Luxar scenes always carry position_bounds, so this is not reachable through
the normal loader.
Measured payoff at the reported pose (R = 52.5, dist = 8.5, far = 61):
the floor rises 1.05e-4 → 0.0508 and depth quantization improves
4.10e-2 → 8.47e-5 world units, i.e. 485x finer.
PERSPECTIVE ONLY — see the boundNearFarRatio parameter of
nearPlaneFloor. An orthographic projection maps eye depth
LINEARLY to the depth buffer, so its resolution is
(far - near) / 2²⁴ regardless of near: the ratio bound buys ortho
nothing, while still clipping a slab in front of the eye that ortho
(unlike perspective) really does draw — perspectiveNearFade returns
1.0 for ortho, so ALL FOUR geometry types render up to near there.
Measured on a diagonal-100 scene at the deepest legal orbit distance:
applying the bound under ortho clips 43.8% of the eye-to-target depth
versus 0.105% without it, and changes depth resolution by 0.1%.
Largest near/far ratio the near-plane floor will allow.
The depth buffer is 24-bit fixed point (three allocates
DEPTH_COMPONENT24for a render target with no explicitdepthType, andwebgl.renderer.logarithmicDepthBufferis off), so depth quantization in world units at eye distancedis— inversely proportional to
near. Left unbounded, the inside-the-sphere branch of calculateClippingPlanesFromSphere pinnedneartoR · MIN_NEAR_RADIUS_FACTOR(2e-6 · R), i.e. a ratio near 6e5:1, which puts Δz at ~4e-2 world units on a diagonal-100 scene viewed from 8.5 units — coarse enough to z-fight visibly, and (becauseneartracks camera distance) to POP as the camera orbits. Only depth-writing geometry is affected (mesh, and opaquenormal-mode geometry — seerendering/blending-state.ts), which is exactly where it was reported.Why 1200 specifically. A LARGER C means a smaller floor: less precision, but less clipped. Two hard constraints put a floor under C, and only a soft preference (keep precision) pushes from above — and that preference turns out to be nearly free, so C is set for MARGIN above the binding constraint rather than at it:
C > 551, don't clip the zoom target under the scale-multiplier floor. At maximum zoom-in the orbit target sits at
minDistance = 1e-3 · diagonal(controls.scaleMultipliers) =1.905e-3 · R, whilefar ≈ 1.05 · R. Keeping it in front of the near plane needs1.05 R / C < 1.905e-3 R. NOTE this assumes the orbit target is at the sphere CENTRE. Panned onto a bbox corner (0.952 · R) the general form(dist + R)/C < 1.905e-3 · Rtightens, and for the last ~2% of the zoom-in range the floor overtakesminDistance. All four types are inside the fade-suppressed region throughout that band by construction, so at most the sub-1% line residual described in the next bullet is lost there, and this does not move C. Auto-framing installs a separatedistance / ZOOM_IN_FACTORlimit. The projected-bounds fit approachesdistance / diagonal = 0.5for a view-axis-elongated box at the default FOV, raising this target constraint to about 1102; C = 1200 clears it there, with a 1.14x near-plane margin at the most extreme supported zoom. At wide FOVs the ratio falls further and C does not clear this target constraint, as it did not before this change; see theZOOM_IN_FACTORnote incamera-framing.ts.C ≥ 992, keep everything the floor clips inside the near fade, for all four geometry types. Every one of them already suppresses anything closer than
nearCull = 1e-3 · diagonalviaperspectiveNearFade(materials/_shared/glsl-lib.ts), and the fade is under 0.01 below1.0589 · nearCull(the root ofsmoothstep(1, 2, x) = 0.01, solved rather than eyeballed intests/.../clipping/_near-fade-model.ts). That single bound is what the tests assert — but what each type DOES with a sub-0.01 fade differs, and the difference is the difference between "lossless" and "very nearly", so it is worth knowing before trusting this:Points and GSplats REJECT the vertex outright below 0.01 (both backends), and Mesh discards the FRAGMENT at the same 0.01 (per fragment because a triangle spans depth; a discard rather than a multiply because it writes depth — #1431). For those three the floor is exactly lossless: what it clips was not going to be rasterized at all.
Lines are the one partial case. They cull only when BOTH endpoints are near, clip a half-near segment onto the
nearCullplane, and then apply the fade PER-FRAGMENT as a plain multiply with no reject of its own — their discard is a separatemax(rgb) < 1e-4test on the COLOR, which the fade never enters. So a line fragment inside the clipped band is attenuated to under 1% of its authored contribution but is not necessarily zero, and the floor can take it. That residual is a property of the line shader, not of this constant: no value of C removes it while still bounding the ratio, it predates #1431, and at ≤1% of one fragment inside a 0.1%-of-diagonal shell it is not what sets C. Closing it would mean giving the line shader its own fade reject.The worst case is NOT the camera on the sphere surface — it is just OUTSIDE it, at the crossover
dist = R · (C+1)/(C-1) ≈ 1.002 · R, the last distance at which the floor still beats the surface term and therefore where the floor sits highest relative tonearCull(fade 0.00755 there versus 0.00725 on the surface). So2.002 R / C ≤ 1.0589 · 1.905e-3 · R⟹C ≥ 992.992 binds, and C = 1200 clears it by 20.9%. The margin is deliberate, and it is cheap: going from the minimum-viable 1000 to 1200 gives away 0.03% of the total precision gain (Δz 7.05e-5 → 8.47e-5 world units on the reported pose, against 4.10e-2 before the bound), and buys survival of ordinary tuning elsewhere. Measured: a 10% tightening of
nearCullneeds C ≥ 1104, and reducing the fade reject headroom to 1.0 needs C ≥ 1051 — C = 1000 would have become silently LOSSY under either, C = 1200 holds.The margin is also enforced rather than trusted: the "clipped band is already shader-rejected" property in
bounds-math.property.test.tsgenerates the whole floor-binding region INCLUDING that crossover, a companion test pins the crossover as strictly worse than the surface, and the arithmetic test asserts the constant survives that 10%nearCulltightening — so a future change tonearCull, to the fade band, or to this constant fails there. (Mesh used to be the FULL exception — it had no near fade in either backend at all, so the floor could clip it at full brightness. Since #1431 it carries the same fade with the same 0.01 reject and the same1.0589 · nearCullheadroom, which moves it into the exactly-lossless group with points and gsplats and leaves lines as the only ≤1% residual above.)The losslessness argument assumes
nearCulland this floor are derived from the SAME bounds, which holds on the metadata and per-frame paths. It can diverge in one narrow case: on a metadata-less sceneSceneBoundsCache.ensurenever populates, so the materials keep_nearCull's 0.1 default whileautoAdjustFromBoundsderives its sphere fromBox3.setFromObject. On a large metadata-less scene the floor can then exceednearCulland clip geometry of any of the four types that the fade would have drawn. Compiled Luxar scenes always carryposition_bounds, so this is not reachable through the normal loader.Measured payoff at the reported pose (R = 52.5, dist = 8.5, far = 61): the floor rises 1.05e-4 → 0.0508 and depth quantization improves 4.10e-2 → 8.47e-5 world units, i.e. 485x finer.
PERSPECTIVE ONLY — see the
boundNearFarRatioparameter of nearPlaneFloor. An orthographic projection maps eye depth LINEARLY to the depth buffer, so its resolution is(far - near) / 2²⁴regardless ofnear: the ratio bound buys ortho nothing, while still clipping a slab in front of the eye that ortho (unlike perspective) really does draw —perspectiveNearFadereturns 1.0 for ortho, so ALL FOUR geometry types render up tonearthere. Measured on a diagonal-100 scene at the deepest legal orbit distance: applying the bound under ortho clips 43.8% of the eye-to-target depth versus 0.105% without it, and changes depth resolution by 0.1%.