Skip to content

Commit af3a657

Browse files
WebGPURenderer: Logarithmic Depth Refinement (#29561)
* 1. Improved wording on a TODO in AnalyticLightNode.js. 2. Removed unnecessary TODO in NodeMaterial.js. 3. Removed unnecessary ".add( 1 ).div( 2 )" from the log depth calculation and added more detail to the log depth comments in ViewportDepthNode.js. * Made the camera near plane a precalculated variable in perspectiveDepthToLogarithmicDepth(), added Desmos link for logarithmic curve visualization. * Adding 1 to each division by cameraNear to ensure the depth curve is shifted to the left as cameraNear increases. Updated Desmos graph link. * Fixed typo in comment.
1 parent 3b49478 commit af3a657

File tree

3 files changed

+25
-24
lines changed

3 files changed

+25
-24
lines changed

src/materials/nodes/NodeMaterial.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,6 @@ class NodeMaterial extends Material {
240240

241241
if ( camera.isPerspectiveCamera ) {
242242

243-
// Note: normally we could use "float( camera.near )" and "float( camera.far )" for the near/far arguments, but
244-
// there is currently a bug with TSL/Three Shading Language whereby a "float()" expression using a huge value
245-
// in scientific notation like "float( 1e27 )" will output "1e+27.0" to the shader code, which is causing problems.
246-
// Since it's possible that camera.near/camera.far values may be using huge values like this (such as the logarithmic
247-
// depth buffer examples on threejs.org), we must use the cameraNear/cameraFar nodes for now.
248-
// TODO: can the float() node be fixed to allow for expressions like "float( 1e27 )"?
249243
depthNode = perspectiveDepthToLogarithmicDepth( modelViewProjection().w, cameraNear, cameraFar );
250244

251245
} else {

src/nodes/display/ViewportDepthNode.js

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -118,26 +118,33 @@ export const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).d
118118

119119
export const perspectiveDepthToLogarithmicDepth = ( perspectiveW, near, far ) => {
120120

121-
// The final logarithmic depth formula used here is adapted from one described in an article
122-
// by Thatcher Ulrich (see http://tulrich.com/geekstuff/log_depth_buffer.txt), which was an
123-
// improvement upon an earlier formula one described in an
121+
// The final logarithmic depth formula used here is adapted from one described in an
122+
// article by Thatcher Ulrich (see http://tulrich.com/geekstuff/log_depth_buffer.txt),
123+
// which was an improvement upon an earlier formula one described in an
124124
// Outerra article (https://outerra.blogspot.com/2009/08/logarithmic-z-buffer.html).
125-
// The Outerra article ignored the camera near plane (it always assumed it was 0) and instead
125+
// Ulrich's formula is the following:
126+
// z = K * log( w / cameraNear ) / log( cameraFar / cameraNear )
127+
// where K = 2^k - 1, and k is the number of bits in the depth buffer.
128+
// The Outerra variant ignored the camera near plane (it assumed it was 0) and instead
126129
// opted for a "C-constant" for resolution adjustment of objects near the camera.
127-
// Outerra states this about their own formula: "Notice that the 'C' variant doesn’t use a near
128-
// plane distance, it has it set at 0." (quote from https://outerra.blogspot.com/2012/11/maximizing-depth-buffer-range-and.html)
129-
// It was debated here whether Outerra's "C-constant" version or Ulrich's "near plane" version should
130-
// be used, and ultimately Ulrich's "near plane" version was chosen for simplicity, since no "C-constant"
131-
// needs to be worried about.
132-
// Outerra eventually made another improvement to their original "C-constant" formula, but it still
133-
// does not incorporate the camera near plane (for this version,
130+
// Outerra states: "Notice that the 'C' variant doesn’t use a near plane distance, it has it
131+
// set at 0" (quote from https://outerra.blogspot.com/2012/11/maximizing-depth-buffer-range-and.html).
132+
// Ulrich's variant has the benefit of constant relative precision over the whole near-far range.
133+
// It was debated here whether Outerra's "C-constant" or Ulrich's "near plane" variant should
134+
// be used, and ultimately Ulrich's "near plane" version was chosen.
135+
// Outerra eventually made another improvement to their original "C-constant" variant,
136+
// but it still does not incorporate the camera near plane (for this version,
134137
// see https://outerra.blogspot.com/2013/07/logarithmic-depth-buffer-optimizations.html).
135-
near = near.max( 1e-6 ); // <-- clamp so we don't divide by 0
136-
const numerator = log2( perspectiveW.div( near ) );
137-
const denominator = log2( far.div( near ) );
138-
// The only modification we make to Ulrich's formula is
139-
// adding 1 to the final depth value and dividing by 2.
140-
return numerator.div( denominator ).add( 1 ).div( 2 );
138+
// Here we make 4 changes to Ulrich's formula:
139+
// 1. Clamp the camera near plane so we don't divide by 0.
140+
// 2. Use log2 instead of log to avoid an extra multiply (shaders implement log using log2).
141+
// 3. Assume K is 1 (K = maximum value in depth buffer; see Ulrich's formula above).
142+
// 4. Add 1 to each division by cameraNear to ensure the depth curve is shifted to the left as cameraNear increases.
143+
// For visual representation of this depth curve, see https://www.desmos.com/calculator/lz5rqfysih
144+
near = near.max( 1e-6 ).toVar();
145+
const numerator = log2( perspectiveW.div( near ).add( 1 ) );
146+
const denominator = log2( far.div( near ).add( 1 ) );
147+
return numerator.div( denominator );
141148

142149
};
143150

src/nodes/lighting/AnalyticLightNode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ class AnalyticLightNode extends LightingNode {
337337

338338
// The normally available "cameraNear" and "cameraFar" nodes cannot be used here because they do not get
339339
// updated to use the shadow camera. So, we have to declare our own "local" ones here.
340-
// TODO: Can we fix cameraNear/cameraFar in src/nodes/accessors/Camera.js so we don't have to declare local ones here?
340+
// TODO: How do we get the cameraNear/cameraFar nodes to use the shadow camera so we don't have to declare local ones here?
341341
const cameraNearLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.near );
342342
const cameraFarLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.far );
343343

0 commit comments

Comments
 (0)