Просмотр исходного кода

WebGPURenderer: Fixed shadows not rendering correctly with `logarithmicDepthBuffer` (#29447)

* Fixed shadows not rendering correctly when logarithmicDepthBuffer = true (issue #29200)

* 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)

* 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)

* 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)

* cleanup
PoseidonEnergy 1 год назад
Родитель
Сommit
a646cbc32b

+ 18 - 6
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, perspectiveDepthToLogarithmicDepth, viewZToOrthographicDepth } from '../../nodes/display/ViewportDepthNode.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,9 +231,21 @@ class NodeMaterial extends Material {
 
 			} else if ( renderer.logarithmicDepthBuffer === true ) {
 
-				const fragDepth = modelViewProjection().w.add( 1 );
+				if ( camera.isPerspectiveCamera ) {
 
-				depthNode = fragDepth.log2().mul( cameraLogDepth ).mul( 0.5 );
+					// 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), 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 );
+
+				}
 
 			}
 

+ 0 - 1
src/nodes/accessors/Camera.js

@@ -4,7 +4,6 @@ import { Vector3 } from '../../math/Vector3.js';
 
 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 );

+ 26 - 1
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';
@@ -116,6 +116,31 @@ 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 ) => {
+
+	// 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 );
+
+};
+
 const depthBase = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_BASE );
 
 export const depth = /*@__PURE__*/ nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH );

+ 26 - 5
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 { perspectiveDepthToLogarithmicDepth } from '../display/ViewportDepthNode.js';
 import { hash } from '../core/NodeUtils.js';
 
 const BasicShadowMap = Fn( ( { depthTexture, shadowCoord } ) => {
@@ -314,20 +315,40 @@ 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 );
+
+				coordZ = shadowCoord.z;
+
+				if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) {
+
+					coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ]
+
+				}
+
+			} else {
+
+				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 );
 
 			}
 
 			shadowCoord = vec3(
 				shadowCoord.x,
 				shadowCoord.y.oneMinus(), // follow webgpu standards
-				coordZ
+				coordZ.add( bias )
 			);
 
 			const frustumTest = shadowCoord.x.greaterThanEqual( 0 )

粤ICP备19079148号