From dd131c70ab34025bebb182c05c37043cbe7c43fc Mon Sep 17 00:00:00 2001 From: PoseidonEnergy Date: Tue, 12 Nov 2024 23:24:52 -0600 Subject: [PATCH 1/5] 1. Renamed the function 'perspectiveDepthToLogarithmicDepth' to 'viewZToLogarithmicDepth' and modified it to expect a negative viewZ value in order maintain consistency with 'viewZToOrthographicDepth' and 'viewZToPerspectiveDepth' 2. Added function 'logarithmicDepthToViewZ' 3. Fixed AO not working when 'logarithmicDepthBuffer' is true --- examples/jsm/tsl/display/GTAONode.js | 22 +++++++++++++++++++-- src/materials/nodes/NodeMaterial.js | 4 ++-- src/nodes/display/ViewportDepthNode.js | 27 ++++++++++++++++++++------ src/nodes/lighting/ShadowNode.js | 4 ++-- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/examples/jsm/tsl/display/GTAONode.js b/examples/jsm/tsl/display/GTAONode.js index 3a7730fb65b7d2..7dda82dde299a5 100644 --- a/examples/jsm/tsl/display/GTAONode.js +++ b/examples/jsm/tsl/display/GTAONode.js @@ -1,4 +1,4 @@ -import { DataTexture, RenderTarget, RepeatWrapping, Vector2, Vector3 } from 'three'; +import { DataTexture, RenderTarget, RepeatWrapping, Vector2, Vector3, logarithmicDepthToViewZ, viewZToPerspectiveDepth } from 'three'; import { getNormalFromDepth, getScreenPosition, getViewPosition, QuadMesh, TempNode, nodeObject, Fn, float, NodeUpdateType, uv, uniform, Loop, vec2, vec3, vec4, int, dot, max, pow, abs, If, textureSize, sin, cos, PI, texture, passTexture, mat3, add, normalize, mul, cross, div, mix, sqrt, sub, acos, clamp, NodeMaterial, PostProcessingUtils } from 'three/tsl'; const _quadMesh = /*@__PURE__*/ new QuadMesh(); @@ -27,6 +27,9 @@ class GTAONode extends TempNode { this.resolutionScale = 1; + this.cameraNear = uniform( 'float' ).onRenderUpdate( () => camera.near ); + this.cameraFar = uniform( 'float' ).onRenderUpdate( () => camera.far ); + this.radius = uniform( 0.25 ); this.resolution = uniform( new Vector2() ); this.thickness = uniform( 1 ); @@ -107,7 +110,22 @@ class GTAONode extends TempNode { const uvNode = uv(); - const sampleDepth = ( uv ) => this.depthNode.uv( uv ).x; + const sampleDepth = ( uv ) => { + + const depth = this.depthNode.uv( uv ).x; + + if ( builder.renderer.logarithmicDepthBuffer === true ) { + + const viewZ = logarithmicDepthToViewZ( depth, this.cameraNear, this.cameraFar ); + + return viewZToPerspectiveDepth( viewZ, this.cameraNear, this.cameraFar ); + + } + + return depth; + + }; + const sampleNoise = ( uv ) => this._noiseNode.uv( uv ); const sampleNormal = ( uv ) => ( this.normalNode !== null ) ? this.normalNode.uv( uv ).rgb.normalize() : getNormalFromDepth( uv, this.depthNode.value, this._cameraProjectionMatrixInverse ); diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index 1ecb1e6064aa6e..f8d4afb4f1d2a9 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/src/materials/nodes/NodeMaterial.js @@ -18,7 +18,7 @@ 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, perspectiveDepthToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.js'; +import { depth, viewZToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.js'; import { cameraFar, cameraNear } from '../../nodes/accessors/Camera.js'; import { clipping, clippingAlpha, hardwareClipping } from '../../nodes/accessors/ClippingNode.js'; import NodeMaterialObserver from './manager/NodeMaterialObserver.js'; @@ -285,7 +285,7 @@ class NodeMaterial extends Material { if ( camera.isPerspectiveCamera ) { - depthNode = perspectiveDepthToLogarithmicDepth( modelViewProjection().w, cameraNear, cameraFar ); + depthNode = viewZToLogarithmicDepth( positionView.z, cameraNear, cameraFar ); } else { diff --git a/src/nodes/display/ViewportDepthNode.js b/src/nodes/display/ViewportDepthNode.js index 201d1c6efdb7d8..a0211891d309db 100644 --- a/src/nodes/display/ViewportDepthNode.js +++ b/src/nodes/display/ViewportDepthNode.js @@ -1,5 +1,5 @@ import Node from '../core/Node.js'; -import { log2, nodeImmutable, nodeProxy } from '../tsl/TSLBase.js'; +import { float, log, 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'; @@ -116,8 +116,10 @@ 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 perspectiveDepthToLogarithmicDepth = ( perspectiveW, near, far ) => { +// -near maps to 0; -far maps to 1 +export const viewZToLogarithmicDepth = ( viewZ, near, far ) => { + // NOTE: viewZ must be negative--see explanation at the end of this comment block. // 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 @@ -139,15 +141,28 @@ export const perspectiveDepthToLogarithmicDepth = ( perspectiveW, near, far ) => // 1. Clamp the camera near plane so we don't divide by 0. // 2. Use log2 instead of log to avoid an extra multiply (shaders implement log using log2). // 3. Assume K is 1 (K = maximum value in depth buffer; see Ulrich's formula above). - // 4. Add 1 to each division by cameraNear to ensure the depth curve is shifted to the left as cameraNear increases. - // For visual representation of this depth curve, see https://www.desmos.com/calculator/lz5rqfysih + // 4. To maintain consistency with the functions "viewZToOrthographicDepth" and "viewZToPerspectiveDepth", + // we modify the formula here to use 'viewZ' instead of 'w'. The other functions expect a negative viewZ, + // so we do the same here, hence the 'viewZ.negate()' call. + // For visual representation of this depth curve, see https://www.desmos.com/calculator/uyqk0vex1u near = near.max( 1e-6 ).toVar(); - const numerator = log2( perspectiveW.div( near ).add( 1 ) ); - const denominator = log2( far.div( near ).add( 1 ) ); + const numerator = log2( viewZ.negate().div( near ) ); + const denominator = log2( far.div( near ) ); return numerator.div( denominator ); }; +// maps logarithmic depth in [ 0, 1 ] to viewZ +export const logarithmicDepthToViewZ = ( depth, near, far ) => { + + // NOTE: we add a 'negate()' call to the return value here to maintain consistency with + // the functions "orthographicDepthToViewZ" and "perspectiveDepthToViewZ" (they return + // a negative viewZ). + const exponent = depth.mul( log( far.div( near ) ) ); + return float( Math.E ).pow( exponent ).mul( near ).negate(); + +}; + const depthBase = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_BASE ); export const depth = /*@__PURE__*/ nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH ); diff --git a/src/nodes/lighting/ShadowNode.js b/src/nodes/lighting/ShadowNode.js index f8840052418e27..ee755a79b23f2b 100644 --- a/src/nodes/lighting/ShadowNode.js +++ b/src/nodes/lighting/ShadowNode.js @@ -15,7 +15,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 { perspectiveDepthToLogarithmicDepth } from '../display/ViewportDepthNode.js'; +import { viewZToLogarithmicDepth } from '../display/ViewportDepthNode.js'; const BasicShadowMap = Fn( ( { depthTexture, shadowCoord } ) => { @@ -316,7 +316,7 @@ class ShadowNode extends Node { const cameraNearLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.near ); const cameraFarLocal = uniform( 'float' ).onRenderUpdate( () => shadow.camera.far ); - coordZ = perspectiveDepthToLogarithmicDepth( w, cameraNearLocal, cameraFarLocal ); + coordZ = viewZToLogarithmicDepth( w.negate(), cameraNearLocal, cameraFarLocal ); } From 538f337dd5871dca5474ac2468db75bf7dd61b9c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 13 Nov 2024 11:03:43 +0100 Subject: [PATCH 2/5] Update GTAONode.js Clean up. --- examples/jsm/tsl/display/GTAONode.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/jsm/tsl/display/GTAONode.js b/examples/jsm/tsl/display/GTAONode.js index 7dda82dde299a5..6f79faeb2b1b79 100644 --- a/examples/jsm/tsl/display/GTAONode.js +++ b/examples/jsm/tsl/display/GTAONode.js @@ -1,5 +1,5 @@ -import { DataTexture, RenderTarget, RepeatWrapping, Vector2, Vector3, logarithmicDepthToViewZ, viewZToPerspectiveDepth } from 'three'; -import { getNormalFromDepth, getScreenPosition, getViewPosition, QuadMesh, TempNode, nodeObject, Fn, float, NodeUpdateType, uv, uniform, Loop, vec2, vec3, vec4, int, dot, max, pow, abs, If, textureSize, sin, cos, PI, texture, passTexture, mat3, add, normalize, mul, cross, div, mix, sqrt, sub, acos, clamp, NodeMaterial, PostProcessingUtils } from 'three/tsl'; +import { DataTexture, RenderTarget, RepeatWrapping, Vector2, Vector3 } from 'three'; +import { reference, logarithmicDepthToViewZ, viewZToPerspectiveDepth, getNormalFromDepth, getScreenPosition, getViewPosition, QuadMesh, TempNode, nodeObject, Fn, float, NodeUpdateType, uv, uniform, Loop, vec2, vec3, vec4, int, dot, max, pow, abs, If, textureSize, sin, cos, PI, texture, passTexture, mat3, add, normalize, mul, cross, div, mix, sqrt, sub, acos, clamp, NodeMaterial, PostProcessingUtils } from 'three/tsl'; const _quadMesh = /*@__PURE__*/ new QuadMesh(); const _size = /*@__PURE__*/ new Vector2(); @@ -27,8 +27,8 @@ class GTAONode extends TempNode { this.resolutionScale = 1; - this.cameraNear = uniform( 'float' ).onRenderUpdate( () => camera.near ); - this.cameraFar = uniform( 'float' ).onRenderUpdate( () => camera.far ); + this.cameraNear = reference( 'near', 'float', camera ); + this.cameraFar = reference( 'far', 'float', camera ); this.radius = uniform( 0.25 ); this.resolution = uniform( new Vector2() ); From 32255b6901e22a5211a9701697258b026e520cb2 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 13 Nov 2024 11:04:57 +0100 Subject: [PATCH 3/5] Update ShadowNode.js Clean up. --- src/nodes/lighting/ShadowNode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nodes/lighting/ShadowNode.js b/src/nodes/lighting/ShadowNode.js index ee755a79b23f2b..d1826da3aeb441 100644 --- a/src/nodes/lighting/ShadowNode.js +++ b/src/nodes/lighting/ShadowNode.js @@ -313,8 +313,8 @@ class ShadowNode extends Node { // 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: How do we get the cameraNear/cameraFar nodes to use the shadow camera 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 ); + const cameraNearLocal = reference( 'near', 'float', shadow.camera ); + const cameraFarLocal = reference( 'far', 'float', shadow.camera ); coordZ = viewZToLogarithmicDepth( w.negate(), cameraNearLocal, cameraFarLocal ); From 92d794fd49279144b58485aac999bbd35b584485 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 13 Nov 2024 11:14:38 +0100 Subject: [PATCH 4/5] Update ShadowNode.js Add `setGroup()`. --- src/nodes/lighting/ShadowNode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nodes/lighting/ShadowNode.js b/src/nodes/lighting/ShadowNode.js index d1826da3aeb441..d32d98777b19a3 100644 --- a/src/nodes/lighting/ShadowNode.js +++ b/src/nodes/lighting/ShadowNode.js @@ -313,8 +313,8 @@ class ShadowNode extends Node { // 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: How do we get the cameraNear/cameraFar nodes to use the shadow camera so we don't have to declare local ones here? - const cameraNearLocal = reference( 'near', 'float', shadow.camera ); - const cameraFarLocal = reference( 'far', 'float', shadow.camera ); + const cameraNearLocal = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup );; + const cameraFarLocal = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup );; coordZ = viewZToLogarithmicDepth( w.negate(), cameraNearLocal, cameraFarLocal ); From ce7371ac5c8ca46a724fa27c6e912add3715c5b1 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 13 Nov 2024 11:19:58 +0100 Subject: [PATCH 5/5] Update ShadowNode.js --- src/nodes/lighting/ShadowNode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nodes/lighting/ShadowNode.js b/src/nodes/lighting/ShadowNode.js index d32d98777b19a3..69e3c6195b4654 100644 --- a/src/nodes/lighting/ShadowNode.js +++ b/src/nodes/lighting/ShadowNode.js @@ -313,8 +313,8 @@ class ShadowNode extends Node { // 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: How do we get the cameraNear/cameraFar nodes to use the shadow camera so we don't have to declare local ones here? - const cameraNearLocal = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup );; - const cameraFarLocal = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup );; + const cameraNearLocal = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup ); + const cameraFarLocal = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup ); coordZ = viewZToLogarithmicDepth( w.negate(), cameraNearLocal, cameraFarLocal );