From 4ca3e3cf7a3302cd186cbacd3cb3e6a5de60fec4 Mon Sep 17 00:00:00 2001 From: PoseidonEnergy Date: Thu, 19 Sep 2024 16:39:58 -0500 Subject: [PATCH 1/5] Fixed shadows not rendering correctly when logarithmicDepthBuffer = true (issue #29200) --- src/materials/nodes/NodeMaterial.js | 10 +++++----- src/nodes/display/ViewportDepthNode.js | 3 +++ src/nodes/lighting/AnalyticLightNode.js | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index 0fd9aa25b5c6f9..7eb4a5db050680 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/src/materials/nodes/NodeMaterial.js @@ -10,7 +10,7 @@ import { normalLocal } from '../../nodes/accessors/Normal.js'; import { instance } from '../../nodes/accessors/InstanceNode.js'; import { batch } from '../../nodes/accessors/BatchNode.js'; import { materialReference } from '../../nodes/accessors/MaterialReferenceNode.js'; -import { positionLocal } from '../../nodes/accessors/Position.js'; +import { positionLocal, positionView } from '../../nodes/accessors/Position.js'; import { skinningReference } from '../../nodes/accessors/SkinningNode.js'; import { morphReference } from '../../nodes/accessors/MorphNode.js'; import { lights } from '../../nodes/lighting/LightsNode.js'; @@ -19,8 +19,8 @@ import { float, vec3, vec4 } from '../../nodes/tsl/TSLBase.js'; import AONode from '../../nodes/lighting/AONode.js'; import { lightingContext } from '../../nodes/lighting/LightingContextNode.js'; import IrradianceNode from '../../nodes/lighting/IrradianceNode.js'; -import { depth } from '../../nodes/display/ViewportDepthNode.js'; -import { cameraLogDepth } from '../../nodes/accessors/Camera.js'; +import { depth, orthographicDepthToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.js'; +import { cameraFar, cameraLogDepth, cameraNear } from '../../nodes/accessors/Camera.js'; import { clipping, clippingAlpha } from '../../nodes/accessors/ClippingNode.js'; import NodeMaterialObserver from './manager/NodeMaterialObserver.js'; @@ -231,9 +231,9 @@ class NodeMaterial extends Material { } else if ( renderer.logarithmicDepthBuffer === true ) { - const fragDepth = modelViewProjection().w.add( 1 ); + const fragDepth = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); - depthNode = fragDepth.log2().mul( cameraLogDepth ).mul( 0.5 ); + depthNode = orthographicDepthToLogarithmicDepth( fragDepth, cameraLogDepth ); } diff --git a/src/nodes/display/ViewportDepthNode.js b/src/nodes/display/ViewportDepthNode.js index a3934889696d65..ee74f6397ad8b8 100644 --- a/src/nodes/display/ViewportDepthNode.js +++ b/src/nodes/display/ViewportDepthNode.js @@ -116,6 +116,9 @@ export const viewZToPerspectiveDepth = ( viewZ, near, far ) => near.add( viewZ ) // maps perspective depth in [ 0, 1 ] to viewZ export const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).div( far.sub( near ).mul( depth ).sub( far ) ); +export const perspectiveDepthToOrthographicDepth = ( depth, near, far ) => viewZToOrthographicDepth( perspectiveDepthToViewZ( depth, near, far ), near, far ); +export const orthographicDepthToLogarithmicDepth = ( depth, logDepthBufFC ) => depth.log2().mul( logDepthBufFC ).mul( 0.25 ).add( 1 ); + const depthBase = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_BASE ); export const depth = /*@__PURE__*/ nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH ); diff --git a/src/nodes/lighting/AnalyticLightNode.js b/src/nodes/lighting/AnalyticLightNode.js index a2067fee7ee485..fe18006a9e99fa 100644 --- a/src/nodes/lighting/AnalyticLightNode.js +++ b/src/nodes/lighting/AnalyticLightNode.js @@ -16,6 +16,7 @@ import { Loop } from '../utils/LoopNode.js'; import { screenCoordinate } from '../display/ScreenNode.js'; import { HalfFloatType, LessCompare, RGFormat, VSMShadowMap, WebGPUCoordinateSystem } from '../../constants.js'; import { renderGroup } from '../core/UniformGroupNode.js'; +import { orthographicDepthToLogarithmicDepth, perspectiveDepthToOrthographicDepth } from '../display/ViewportDepthNode.js'; const BasicShadowMap = Fn( ( { depthTexture, shadowCoord } ) => { @@ -321,6 +322,24 @@ class AnalyticLightNode extends LightingNode { } + if ( renderer.logarithmicDepthBuffer === true ) { + + // the normally available cameraNear, cameraFar, and cameraLogDepth nodes cannot be used here because they do + // not get updated to use the shadow camera, so we have to declare our own "local" nodes here. + // TODO: can we fix the cameraNear/cameraFar/cameraLogDepth nodes in src/nodes/accessors/Camera.js so we don't have to declare local ones here? + if ( shadow.camera.isPerspectiveCamera ) { + + const cameraNearLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.near ); + const cameraFarLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.far ); + coordZ = perspectiveDepthToOrthographicDepth( coordZ, cameraNearLocal, cameraFarLocal ); + + } + + const cameraLogDepthLocal = uniform( 'float' ).onRenderUpdate( () => 2.0 / ( Math.log( shadow.camera.far + 1.0 ) / Math.LN2 ) ); + coordZ = orthographicDepthToLogarithmicDepth( coordZ, cameraLogDepthLocal ); + + } + shadowCoord = vec3( shadowCoord.x, shadowCoord.y.oneMinus(), // follow webgpu standards From c3a5cb63be04583110840fa5d0992956e2d67778 Mon Sep 17 00:00:00 2001 From: PoseidonEnergy Date: Sat, 21 Sep 2024 13:47:21 -0500 Subject: [PATCH 2/5] Further improvements to the logarithmic depth buffer shadows bugfix: 1) Disabled logarithmic depth buffer for orthographic cameras. 2) Removed "cameraLogDepth" node from Camera.js as it is no longer used in the perspective-to-logarithmic depth conversion. 3) Ensured that modelViewProjection.w is used for perspective-to-logarithmic depth conversion, and that positionView.z is always used for orthographic cameras. 4) Adapted Outerra's logarithmic depth buffer formula, re-added "C" constant for increased resolution close to the camera, per Outerra's findings. (issue #29200, PR #29447) --- src/materials/nodes/NodeMaterial.js | 12 +++++---- src/nodes/accessors/Camera.js | 3 ++- src/nodes/display/ViewportDepthNode.js | 16 +++++++++-- src/nodes/lighting/AnalyticLightNode.js | 36 +++++++++++-------------- 4 files changed, 39 insertions(+), 28 deletions(-) diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index 7eb4a5db050680..6daa0a51011c69 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/src/materials/nodes/NodeMaterial.js @@ -19,8 +19,8 @@ import { float, vec3, vec4 } from '../../nodes/tsl/TSLBase.js'; import AONode from '../../nodes/lighting/AONode.js'; import { lightingContext } from '../../nodes/lighting/LightingContextNode.js'; import IrradianceNode from '../../nodes/lighting/IrradianceNode.js'; -import { depth, orthographicDepthToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.js'; -import { cameraFar, cameraLogDepth, cameraNear } from '../../nodes/accessors/Camera.js'; +import { depth, perspectiveDepthToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.js'; +import { cameraFar, cameraIsPerspective, cameraNear } from '../../nodes/accessors/Camera.js'; import { clipping, clippingAlpha } from '../../nodes/accessors/ClippingNode.js'; import NodeMaterialObserver from './manager/NodeMaterialObserver.js'; @@ -231,9 +231,11 @@ class NodeMaterial extends Material { } else if ( renderer.logarithmicDepthBuffer === true ) { - const fragDepth = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); - - depthNode = orthographicDepthToLogarithmicDepth( fragDepth, cameraLogDepth ); + // Note: if you manually provide a "C" constant here for the logarithmic depth calculation, remember + // to also include it in the shadow depth correction in AnalyticLightNode.setupShadow()! + const logarithmicDepth = perspectiveDepthToLogarithmicDepth( modelViewProjection().w, cameraFar ); + const orthographicDepth = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); + depthNode = cameraIsPerspective.equal( 1 ).select( logarithmicDepth, orthographicDepth ); } diff --git a/src/nodes/accessors/Camera.js b/src/nodes/accessors/Camera.js index 8a15c0136afb14..97059fb580ac1b 100644 --- a/src/nodes/accessors/Camera.js +++ b/src/nodes/accessors/Camera.js @@ -2,9 +2,10 @@ import { uniform } from '../core/UniformNode.js'; import { renderGroup } from '../core/UniformGroupNode.js'; import { Vector3 } from '../../math/Vector3.js'; +export const cameraIsPerspective = /*@__PURE__*/ uniform( 'float' ).label( 'cameraIsPerspective' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.isPerspectiveCamera === true ? 1 : 0 ); +export const cameraIsOrthographic = /*@__PURE__*/ uniform( 'float' ).label( 'cameraIsOrthographic' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.isOrthographicCamera === true ? 1 : 0 ); export const cameraNear = /*@__PURE__*/ uniform( 'float' ).label( 'cameraNear' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.near ); export const cameraFar = /*@__PURE__*/ uniform( 'float' ).label( 'cameraFar' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.far ); -export const cameraLogDepth = /*@__PURE__*/ uniform( 'float' ).label( 'cameraLogDepth' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); export const cameraProjectionMatrix = /*@__PURE__*/ uniform( 'mat4' ).label( 'cameraProjectionMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrix ); export const cameraProjectionMatrixInverse = /*@__PURE__*/ uniform( 'mat4' ).label( 'cameraProjectionMatrixInverse' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrixInverse ); export const cameraViewMatrix = /*@__PURE__*/ uniform( 'mat4' ).label( 'cameraViewMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorldInverse ); diff --git a/src/nodes/display/ViewportDepthNode.js b/src/nodes/display/ViewportDepthNode.js index ee74f6397ad8b8..4530ab8ddbf4bd 100644 --- a/src/nodes/display/ViewportDepthNode.js +++ b/src/nodes/display/ViewportDepthNode.js @@ -1,5 +1,5 @@ import Node from '../core/Node.js'; -import { nodeImmutable, nodeProxy } from '../tsl/TSLBase.js'; +import { log2, nodeImmutable, nodeProxy } from '../tsl/TSLBase.js'; import { cameraNear, cameraFar } from '../accessors/Camera.js'; import { positionView } from '../accessors/Position.js'; import { viewportDepthTexture } from './ViewportDepthTextureNode.js'; @@ -117,7 +117,19 @@ export const viewZToPerspectiveDepth = ( viewZ, near, far ) => near.add( viewZ ) export const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).div( far.sub( near ).mul( depth ).sub( far ) ); export const perspectiveDepthToOrthographicDepth = ( depth, near, far ) => viewZToOrthographicDepth( perspectiveDepthToViewZ( depth, near, far ), near, far ); -export const orthographicDepthToLogarithmicDepth = ( depth, logDepthBufFC ) => depth.log2().mul( logDepthBufFC ).mul( 0.25 ).add( 1 ); +export const perspectiveDepthToLogarithmicDepth = ( perspectiveW, far, C = 10 ) => { + + // Final equation is adapted from Outerra. + // See https://outerra.blogspot.com/2013/07/logarithmic-depth-buffer-optimizations.html + // C is a constant that determines the resolution near the camera. + // A value of C = 1 means no adjustment. A value of C = 10 removes the z-fighting visible on the + // "micrososcopic" scale text in the "webgpu_camera_logarithmicdepthbuffer" example on threejs.org. + // See https://outerra.blogspot.com/2009/08/logarithmic-z-buffer.html for description of the C constant. + const numerator = log2( perspectiveW.mul( C ).add( 1 ).max( 1e-6 ) ); + const denominator = log2( far.mul( C ).add( 1 ) ); + return numerator.div( denominator ); + +}; const depthBase = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_BASE ); diff --git a/src/nodes/lighting/AnalyticLightNode.js b/src/nodes/lighting/AnalyticLightNode.js index fe18006a9e99fa..017f6a0ae1224c 100644 --- a/src/nodes/lighting/AnalyticLightNode.js +++ b/src/nodes/lighting/AnalyticLightNode.js @@ -16,7 +16,7 @@ import { Loop } from '../utils/LoopNode.js'; import { screenCoordinate } from '../display/ScreenNode.js'; import { HalfFloatType, LessCompare, RGFormat, VSMShadowMap, WebGPUCoordinateSystem } from '../../constants.js'; import { renderGroup } from '../core/UniformGroupNode.js'; -import { orthographicDepthToLogarithmicDepth, perspectiveDepthToOrthographicDepth } from '../display/ViewportDepthNode.js'; +import { perspectiveDepthToLogarithmicDepth } from '../display/ViewportDepthNode.js'; const BasicShadowMap = Fn( ( { depthTexture, shadowCoord } ) => { @@ -312,38 +312,34 @@ class AnalyticLightNode extends LightingNode { const position = object.material.shadowPositionNode || positionWorld; let shadowCoord = uniform( shadow.matrix ).setGroup( renderGroup ).mul( position.add( normalWorld.mul( normalBias ) ) ); - shadowCoord = shadowCoord.xyz.div( shadowCoord.w ); - let coordZ = shadowCoord.z.add( bias ); + let coordZ; - if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) { + if ( shadow.camera.isOrthographicCamera || renderer.logarithmicDepthBuffer !== true ) { - coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Convertion [ 0, 1 ] to [ - 1, 1 ] + shadowCoord = shadowCoord.xyz.div( shadowCoord.w ); + if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) { - } - - if ( renderer.logarithmicDepthBuffer === true ) { - - // the normally available cameraNear, cameraFar, and cameraLogDepth nodes cannot be used here because they do - // not get updated to use the shadow camera, so we have to declare our own "local" nodes here. - // TODO: can we fix the cameraNear/cameraFar/cameraLogDepth nodes in src/nodes/accessors/Camera.js so we don't have to declare local ones here? - if ( shadow.camera.isPerspectiveCamera ) { - - const cameraNearLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.near ); - const cameraFarLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.far ); - coordZ = perspectiveDepthToOrthographicDepth( coordZ, cameraNearLocal, cameraFarLocal ); + coordZ = shadowCoord.z.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ] } - const cameraLogDepthLocal = uniform( 'float' ).onRenderUpdate( () => 2.0 / ( Math.log( shadow.camera.far + 1.0 ) / Math.LN2 ) ); - coordZ = orthographicDepthToLogarithmicDepth( coordZ, cameraLogDepthLocal ); + } else { + + const w = shadowCoord.w; + shadowCoord = shadowCoord.xy.div( w ); // <-- Only divide X/Y coords since we don't need Z + // The normally available "cameraFar" node cannot be used here because it does not get + // updated to use the shadow camera. So, we have to declare our own "local" one here. + // TODO: Can we fix cameraNear/cameraFar in src/nodes/accessors/Camera.js so we don't have to declare new ones here? + const cameraFarLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.far ); + coordZ = perspectiveDepthToLogarithmicDepth( w, cameraFarLocal ); } shadowCoord = vec3( shadowCoord.x, shadowCoord.y.oneMinus(), // follow webgpu standards - coordZ + coordZ.add( bias ) ); const frustumTest = shadowCoord.x.greaterThanEqual( 0 ) From 6a2d716afc8ea3b366fd9282ccb7cb571808436e Mon Sep 17 00:00:00 2001 From: PoseidonEnergy Date: Mon, 23 Sep 2024 16:15:02 -0500 Subject: [PATCH 3/5] Further improvements to the logarithmic depth buffer shadows bugfix: 1) Removed "cameraLogDepth" node from Camera.js as it is no longer used in the perspective-to-logarithmic depth conversion. 2) Removed "cameraIsPerspective" and "cameraIsOrthographic" nodes that were added in a previous commit 3) Ensured that modelViewProjection.w is used for perspective-to-logarithmic depth conversion, and that positionView.z is always used for orthographic cameras. 4) Adapted a modified version of Outerra's logarithmic depth buffer without requiring a "C" constant (issue #29200, PR #29447) --- src/materials/nodes/NodeMaterial.js | 24 ++++++++++++----- src/nodes/accessors/Camera.js | 2 -- src/nodes/display/ViewportDepthNode.js | 34 ++++++++++++++++--------- src/nodes/lighting/AnalyticLightNode.js | 9 ++++--- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index 6daa0a51011c69..aa628b25921977 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/src/materials/nodes/NodeMaterial.js @@ -20,7 +20,7 @@ import AONode from '../../nodes/lighting/AONode.js'; import { lightingContext } from '../../nodes/lighting/LightingContextNode.js'; import IrradianceNode from '../../nodes/lighting/IrradianceNode.js'; import { depth, perspectiveDepthToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.js'; -import { cameraFar, cameraIsPerspective, cameraNear } from '../../nodes/accessors/Camera.js'; +import { cameraFar, cameraNear } from '../../nodes/accessors/Camera.js'; import { clipping, clippingAlpha } from '../../nodes/accessors/ClippingNode.js'; import NodeMaterialObserver from './manager/NodeMaterialObserver.js'; @@ -215,7 +215,7 @@ class NodeMaterial extends Material { setupDepth( builder ) { - const { renderer } = builder; + const { renderer, camera } = builder; // Depth @@ -231,11 +231,21 @@ class NodeMaterial extends Material { } else if ( renderer.logarithmicDepthBuffer === true ) { - // Note: if you manually provide a "C" constant here for the logarithmic depth calculation, remember - // to also include it in the shadow depth correction in AnalyticLightNode.setupShadow()! - const logarithmicDepth = perspectiveDepthToLogarithmicDepth( modelViewProjection().w, cameraFar ); - const orthographicDepth = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); - depthNode = cameraIsPerspective.equal( 1 ).select( logarithmicDepth, orthographicDepth ); + if ( camera.isPerspectiveCamera ) { + + // Note: normally we could use "float( camera.near )" and "float( camera.far )" for the near/far arguments, but + // there is currently a bug with TSL/Three Shading Language whereby a "float()" expression using a huge value + // in scientific notation like "float( 1e27 )" will output "1e+27.0" to the shader code, which is causing problems. + // Since it's possible that camera.near/camera.far values may be using huge values like this ( such as the logarithmic + // depth buffer examples on threejs.org use huge values like those), we must use the cameraNear/cameraFar nodes for now. + // TODO: can the float() node be fixed to allow for expressions like "float( 1e27 )"? + depthNode = perspectiveDepthToLogarithmicDepth( modelViewProjection().w, cameraNear, cameraFar ); + + } else { + + depthNode = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); + + } } diff --git a/src/nodes/accessors/Camera.js b/src/nodes/accessors/Camera.js index 97059fb580ac1b..3a8dcd0e8bc265 100644 --- a/src/nodes/accessors/Camera.js +++ b/src/nodes/accessors/Camera.js @@ -2,8 +2,6 @@ import { uniform } from '../core/UniformNode.js'; import { renderGroup } from '../core/UniformGroupNode.js'; import { Vector3 } from '../../math/Vector3.js'; -export const cameraIsPerspective = /*@__PURE__*/ uniform( 'float' ).label( 'cameraIsPerspective' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.isPerspectiveCamera === true ? 1 : 0 ); -export const cameraIsOrthographic = /*@__PURE__*/ uniform( 'float' ).label( 'cameraIsOrthographic' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.isOrthographicCamera === true ? 1 : 0 ); export const cameraNear = /*@__PURE__*/ uniform( 'float' ).label( 'cameraNear' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.near ); export const cameraFar = /*@__PURE__*/ uniform( 'float' ).label( 'cameraFar' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.far ); export const cameraProjectionMatrix = /*@__PURE__*/ uniform( 'mat4' ).label( 'cameraProjectionMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrix ); diff --git a/src/nodes/display/ViewportDepthNode.js b/src/nodes/display/ViewportDepthNode.js index 4530ab8ddbf4bd..6dc7d1abee7411 100644 --- a/src/nodes/display/ViewportDepthNode.js +++ b/src/nodes/display/ViewportDepthNode.js @@ -116,18 +116,28 @@ export const viewZToPerspectiveDepth = ( viewZ, near, far ) => near.add( viewZ ) // maps perspective depth in [ 0, 1 ] to viewZ export const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).div( far.sub( near ).mul( depth ).sub( far ) ); -export const perspectiveDepthToOrthographicDepth = ( depth, near, far ) => viewZToOrthographicDepth( perspectiveDepthToViewZ( depth, near, far ), near, far ); -export const perspectiveDepthToLogarithmicDepth = ( perspectiveW, far, C = 10 ) => { - - // Final equation is adapted from Outerra. - // See https://outerra.blogspot.com/2013/07/logarithmic-depth-buffer-optimizations.html - // C is a constant that determines the resolution near the camera. - // A value of C = 1 means no adjustment. A value of C = 10 removes the z-fighting visible on the - // "micrososcopic" scale text in the "webgpu_camera_logarithmicdepthbuffer" example on threejs.org. - // See https://outerra.blogspot.com/2009/08/logarithmic-z-buffer.html for description of the C constant. - const numerator = log2( perspectiveW.mul( C ).add( 1 ).max( 1e-6 ) ); - const denominator = log2( far.mul( C ).add( 1 ) ); - return numerator.div( denominator ); +export const perspectiveDepthToLogarithmicDepth = ( perspectiveW, near, far ) => { + + // The final logarithmic depth formula used here is adapted from one described in an article + // by Thatcher Ulrich (see http://tulrich.com/geekstuff/log_depth_buffer.txt), which was an + // improvement upon an earlier formula one described in an + // Outerra article (https://outerra.blogspot.com/2009/08/logarithmic-z-buffer.html). + // The Outerra article ignored the camera near plane (it always assumed it was 0) and instead + // opted for a "C-constant" for resolution adjustment of objects near the camera. + // Outerra states this about their own formula: "Notice that the 'C' variant doesn’t use a near + // plane distance, it has it set at 0." (quote from https://outerra.blogspot.com/2012/11/maximizing-depth-buffer-range-and.html) + // It was debated here whether Outerra's "C-constant" version or Ulrich's "near plane" version should + // be used, and ultimately Ulrich's "near plane" version was chosen for simplicity, since no "C-constant" + // needs to be worried about. + // Outerra eventually made another improvement to their original "C-constant" formula, but it still + // does not incorporate the camera near plane (for this version, + // see https://outerra.blogspot.com/2013/07/logarithmic-depth-buffer-optimizations.html). + near = near.max( 1e-6 ); // <-- clamp so we don't divide by 0 + const numerator = log2( perspectiveW.div( near ) ); + const denominator = log2( far.div( near ) ); + // The only modification we make to Ulrich's formula is + // adding 1 to the final depth value and dividing by 2. + return numerator.div( denominator ).add( 1 ).div( 2 ); }; diff --git a/src/nodes/lighting/AnalyticLightNode.js b/src/nodes/lighting/AnalyticLightNode.js index 017f6a0ae1224c..2ccbdd75074013 100644 --- a/src/nodes/lighting/AnalyticLightNode.js +++ b/src/nodes/lighting/AnalyticLightNode.js @@ -328,11 +328,12 @@ class AnalyticLightNode extends LightingNode { const w = shadowCoord.w; shadowCoord = shadowCoord.xy.div( w ); // <-- Only divide X/Y coords since we don't need Z - // The normally available "cameraFar" node cannot be used here because it does not get - // updated to use the shadow camera. So, we have to declare our own "local" one here. - // TODO: Can we fix cameraNear/cameraFar in src/nodes/accessors/Camera.js so we don't have to declare new ones here? + // The normally available "cameraNear" and "cameraFar" nodes cannot be used here because they do not get + // updated to use the shadow camera. So, we have to declare our own "local" ones here. + // TODO: Can we fix cameraNear/cameraFar in src/nodes/accessors/Camera.js so we don't have to declare local ones here? + const cameraNearLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.near ); const cameraFarLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.far ); - coordZ = perspectiveDepthToLogarithmicDepth( w, cameraFarLocal ); + coordZ = perspectiveDepthToLogarithmicDepth( w, cameraNearLocal, cameraFarLocal ); } From 779d372dd836061ccfc0beee21e9b4ee66d594a1 Mon Sep 17 00:00:00 2001 From: PoseidonEnergy Date: Mon, 23 Sep 2024 16:31:55 -0500 Subject: [PATCH 4/5] Further improvements to the logarithmic depth buffer shadows bugfix: 1) fixed bug where the shadowmap's "coordZ" was not getting set when not using WebGPU 2) fixed typos in comments (issue #29200, PR #29447) --- src/materials/nodes/NodeMaterial.js | 4 ++-- src/nodes/lighting/AnalyticLightNode.js | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index aa628b25921977..90e7b00876b6b9 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/src/materials/nodes/NodeMaterial.js @@ -236,8 +236,8 @@ class NodeMaterial extends Material { // Note: normally we could use "float( camera.near )" and "float( camera.far )" for the near/far arguments, but // there is currently a bug with TSL/Three Shading Language whereby a "float()" expression using a huge value // in scientific notation like "float( 1e27 )" will output "1e+27.0" to the shader code, which is causing problems. - // Since it's possible that camera.near/camera.far values may be using huge values like this ( such as the logarithmic - // depth buffer examples on threejs.org use huge values like those), we must use the cameraNear/cameraFar nodes for now. + // Since it's possible that camera.near/camera.far values may be using huge values like this (such as the logarithmic + // depth buffer examples on threejs.org), we must use the cameraNear/cameraFar nodes for now. // TODO: can the float() node be fixed to allow for expressions like "float( 1e27 )"? depthNode = perspectiveDepthToLogarithmicDepth( modelViewProjection().w, cameraNear, cameraFar ); diff --git a/src/nodes/lighting/AnalyticLightNode.js b/src/nodes/lighting/AnalyticLightNode.js index 2ccbdd75074013..01d80a6ce715a5 100644 --- a/src/nodes/lighting/AnalyticLightNode.js +++ b/src/nodes/lighting/AnalyticLightNode.js @@ -322,6 +322,10 @@ class AnalyticLightNode extends LightingNode { coordZ = shadowCoord.z.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ] + } else { + + coordZ = shadowCoord.z; + } } else { From 8b093086cb4f07d45331582a29a36af24a44d227 Mon Sep 17 00:00:00 2001 From: sunag Date: Fri, 27 Sep 2024 20:55:20 -0300 Subject: [PATCH 5/5] cleanup --- src/nodes/lighting/AnalyticLightNode.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nodes/lighting/AnalyticLightNode.js b/src/nodes/lighting/AnalyticLightNode.js index 6d03bd820589f0..4ee7fb95d4d9f1 100644 --- a/src/nodes/lighting/AnalyticLightNode.js +++ b/src/nodes/lighting/AnalyticLightNode.js @@ -321,13 +321,12 @@ class AnalyticLightNode extends LightingNode { if ( shadow.camera.isOrthographicCamera || renderer.logarithmicDepthBuffer !== true ) { shadowCoord = shadowCoord.xyz.div( shadowCoord.w ); - if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) { - coordZ = shadowCoord.z.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ] + coordZ = shadowCoord.z; - } else { + if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) { - coordZ = shadowCoord.z; + coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ] } @@ -335,11 +334,13 @@ class AnalyticLightNode extends LightingNode { const w = shadowCoord.w; shadowCoord = shadowCoord.xy.div( w ); // <-- Only divide X/Y coords since we don't need Z + // The normally available "cameraNear" and "cameraFar" nodes cannot be used here because they do not get // updated to use the shadow camera. So, we have to declare our own "local" ones here. // TODO: Can we fix cameraNear/cameraFar in src/nodes/accessors/Camera.js so we don't have to declare local ones here? const cameraNearLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.near ); const cameraFarLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.far ); + coordZ = perspectiveDepthToLogarithmicDepth( w, cameraNearLocal, cameraFarLocal ); }