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

WebGPURenderer: Add basic reversed depth buffer support. (#32967)

Michael Herzog 2 недель назад
Родитель
Сommit
1547577ab7

+ 1 - 0
examples/files.json

@@ -440,6 +440,7 @@
 		"webgpu_reflection_roughness",
 		"webgpu_refraction",
 		"webgpu_rendertarget_2d-array_3d",
+		"webgpu_reversed_depth_buffer",
 		"webgpu_rtt",
 		"webgpu_sandbox",
 		"webgpu_shadertoy",

BIN
examples/screenshots/webgpu_reversed_depth_buffer.jpg


+ 242 - 0
examples/webgpu_reversed_depth_buffer.html

@@ -0,0 +1,242 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - reverse depth buffer</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="example.css">
+		<style>
+			#container {
+				display: flex;
+			}
+
+			#container_normal,
+			#container_logarithmic,
+			#container_reverse {
+				width: 33%;
+				display: inline-block;
+				position: relative;
+			}
+
+			.container_label {
+				position: absolute;
+				bottom: 1em;
+				width: 100%;
+				color: white;
+				z-index: 10;
+				display: block;
+				text-align: center;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="container">
+			<div id="container_normal"><h2 class="container_label">normal z-buffer</h2></div>
+			<div id="container_logarithmic"><h2 class="container_label">logarithmic z-buffer</h2></div>
+			<div id="container_reverse"><h2 class="container_label">reverse z-buffer</h2></div>
+		</div>
+
+		<div id="info">
+			<a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
+
+			<div class="title-wrapper">
+				<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Reverse Depth Buffer</span>
+			</div>
+
+			<small>
+				Note: For best results, a floating-point depth buffer should be used with post-processing. See <a href="https://developer.nvidia.com/blog/visualizing-depth-precision" target="_blank" rel="noopener">Visualizing Depth Precision</a>.
+			</small>
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.webgpu.js",
+					"three/webgpu": "../build/three.webgpu.js",
+					"three/tsl": "../build/three.tsl.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three/webgpu';
+			import { pass } from 'three/tsl';
+
+			let camera, reversedCamera, scene;
+			let normalRenderer, logarithmicRenderer, reverseRenderer;
+			let normalRenderPipeline, logarithmicRenderPipeline, reverseRenderPipeline;
+			const meshes = [];
+
+			init().then( animate );
+
+			async function init() {
+
+				camera = new THREE.PerspectiveCamera( 72, 0.33 * window.innerWidth / window.innerHeight, 5, 9999 );
+				camera.position.z = 12;
+
+				reversedCamera = camera.clone();
+
+				scene = new THREE.Scene();
+
+				const xCount = 1;
+				const yCount = 5;
+				const numInstances = xCount * yCount;
+
+				const d = 0.0001; // half distance between two planes
+				const o = 0.5; // half x offset to shift planes so they are only partially overlapping
+
+				const positions = new Float32Array( [
+					- 1 - o, - 1, d,
+					1 - o, - 1, d,
+					- 1 - o, 1, d,
+					1 - o, - 1, d,
+					1 - o, 1, d,
+					- 1 - o, 1, d,
+
+					- 1 + o, - 1, - d,
+					1 + o, - 1, - d,
+					- 1 + o, 1, - d,
+					1 + o, - 1, - d,
+					1 + o, 1, - d,
+					- 1 + o, 1, - d,
+				] );
+
+				const colors = new Float32Array( [
+					1, 0, 0,
+					1, 0, 0,
+					1, 0, 0,
+					1, 0, 0,
+					1, 0, 0,
+					1, 0, 0,
+
+					0, 1, 0,
+					0, 1, 0,
+					0, 1, 0,
+					0, 1, 0,
+					0, 1, 0,
+					0, 1, 0,
+				] );
+
+				const geometry = new THREE.BufferGeometry();
+				geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
+				geometry.setAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
+
+				const material = new THREE.MeshBasicMaterial( { vertexColors: true } );
+
+				for ( let i = 0; i < numInstances; i ++ ) {
+
+					const mesh = new THREE.Mesh( geometry, material );
+					meshes.push( mesh );
+					scene.add( mesh );
+
+				}
+
+				let i = 0;
+				for ( let x = 0; x < xCount; x ++ ) {
+
+					for ( let y = 0; y < yCount; y ++ ) {
+
+						const z = - 800 * i;
+						const s = 1 + 50 * i;
+
+						const mesh = meshes[ i ];
+						mesh.position.set(
+							x - xCount / 2 + 0.5,
+							( 4.0 - 0.2 * z ) * ( y - yCount / 2 + 1.0 ),
+							z
+						);
+						mesh.scale.setScalar( s );
+
+						i ++;
+
+					}
+
+				}
+
+				const normalContainer = document.getElementById( 'container_normal' );
+				normalRenderer = new THREE.WebGPURenderer();
+				normalRenderer.setPixelRatio( window.devicePixelRatio );
+				normalRenderer.setSize( 0.33 * window.innerWidth, window.innerHeight );
+				normalRenderer.domElement.style.position = 'relative';
+				normalContainer.appendChild( normalRenderer.domElement );
+
+				const logarithmicContainer = document.getElementById( 'container_logarithmic' );
+				logarithmicRenderer = new THREE.WebGPURenderer( { logarithmicDepthBuffer: true } );
+				logarithmicRenderer.setPixelRatio( window.devicePixelRatio );
+				logarithmicRenderer.setSize( 0.33 * window.innerWidth, window.innerHeight );
+				logarithmicRenderer.domElement.style.position = 'relative';
+				logarithmicContainer.appendChild( logarithmicRenderer.domElement );
+
+				const reverseContainer = document.getElementById( 'container_reverse' );
+				reverseRenderer = new THREE.WebGPURenderer( { reversedDepthBuffer: true } );
+				reverseRenderer.setPixelRatio( window.devicePixelRatio );
+				reverseRenderer.setSize( 0.33 * window.innerWidth, window.innerHeight );
+				reverseRenderer.domElement.style.position = 'relative';
+				reverseContainer.appendChild( reverseRenderer.domElement );
+
+				//
+
+				normalRenderPipeline = new THREE.RenderPipeline( normalRenderer );
+				normalRenderPipeline.outputNode = pass( scene, camera );
+
+				logarithmicRenderPipeline = new THREE.RenderPipeline( logarithmicRenderer );
+				logarithmicRenderPipeline.outputNode = pass( scene, camera );
+
+				reverseRenderPipeline = new THREE.RenderPipeline( reverseRenderer );
+				reverseRenderPipeline.outputNode = pass( scene, reversedCamera );
+
+				await Promise.all( [ normalRenderer.init(), logarithmicRenderer.init(), reverseRenderer.init() ] );
+
+				window.addEventListener( 'resize', onWindowResize );
+				onWindowResize();
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				const now = performance.now() / 1000;
+
+				for ( let i = 0; i < meshes.length; i ++ ) {
+
+					const angle = THREE.MathUtils.degToRad( 30 );
+					const axis = new THREE.Vector3( Math.sin( now ), Math.cos( now ), 0 );
+					meshes[ i ].quaternion.setFromAxisAngle( axis, angle );
+
+				}
+
+				render();
+
+			}
+
+			function render() {
+
+				normalRenderPipeline.render();
+				logarithmicRenderPipeline.render();
+				reverseRenderPipeline.render();
+
+			}
+
+			function onWindowResize() {
+
+				const width = 0.33 * window.innerWidth;
+				const height = window.innerHeight;
+
+				normalRenderer.setSize( width, height );
+				logarithmicRenderer.setSize( width, height );
+				reverseRenderer.setSize( width, height );
+
+				camera.aspect = 0.33 * window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				reversedCamera.aspect = 0.33 * window.innerWidth / window.innerHeight;
+				reversedCamera.updateProjectionMatrix();
+
+			}
+		</script>
+
+	</body>
+</html>

+ 3 - 3
src/lights/LightShadow.js

@@ -3,7 +3,7 @@ import { Vector2 } from '../math/Vector2.js';
 import { Vector3 } from '../math/Vector3.js';
 import { Vector4 } from '../math/Vector4.js';
 import { Frustum } from '../math/Frustum.js';
-import { UnsignedByteType } from '../constants.js';
+import { UnsignedByteType, WebGPUCoordinateSystem } from '../constants.js';
 
 const _projScreenMatrix = /*@__PURE__*/ new Matrix4();
 const _lightPositionWorld = /*@__PURE__*/ new Vector3();
@@ -213,12 +213,12 @@ class LightShadow {
 		_projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
 		this._frustum.setFromProjectionMatrix( _projScreenMatrix, shadowCamera.coordinateSystem, shadowCamera.reversedDepth );
 
-		if ( shadowCamera.reversedDepth ) {
+		if ( shadowCamera.coordinateSystem === WebGPUCoordinateSystem || shadowCamera.reversedDepth ) {
 
 			shadowMatrix.set(
 				0.5, 0.0, 0.0, 0.5,
 				0.0, 0.5, 0.0, 0.5,
-				0.0, 0.0, 1.0, 0.0,
+				0.0, 0.0, 1.0, 0.0, // Identity Z (preserving the correct [0, 1] range from the projection matrix)
 				0.0, 0.0, 0.0, 1.0
 			);
 

+ 12 - 0
src/nodes/display/ViewportDepthNode.js

@@ -178,6 +178,18 @@ export const orthographicDepthToViewZ = ( depth, near, far ) => near.sub( far ).
  */
 export const viewZToPerspectiveDepth = ( viewZ, near, far ) => near.add( viewZ ).mul( far ).div( far.sub( near ).mul( viewZ ) );
 
+/**
+ * TSL function for converting a viewZ value to a reversed perspective depth value.
+ *
+ * @tsl
+ * @function
+ * @param {Node<float>} viewZ - The viewZ node.
+ * @param {Node<float>} near - The camera's near value.
+ * @param {Node<float>} far - The camera's far value.
+ * @returns {Node<float>}
+ */
+export const viewZToReversedPerspectiveDepth = ( viewZ, near, far ) => near.mul( viewZ.add( far ) ).div( viewZ.mul( near.sub( far ) ) );
+
 /**
  * TSL function for converting a perspective depth value to a viewZ value.
  *

+ 17 - 6
src/nodes/lighting/PointShadowNode.js

@@ -7,12 +7,12 @@ import { renderGroup } from '../core/UniformGroupNode.js';
 import { Matrix4 } from '../../math/Matrix4.js';
 import { Vector3 } from '../../math/Vector3.js';
 import { Color } from '../../math/Color.js';
-import { BasicShadowMap, LessEqualCompare, WebGPUCoordinateSystem } from '../../constants.js';
+import { BasicShadowMap, GreaterEqualCompare, LessEqualCompare, WebGPUCoordinateSystem } from '../../constants.js';
 import { CubeDepthTexture } from '../../textures/CubeDepthTexture.js';
 import { screenCoordinate } from '../display/ScreenNode.js';
 import { interleavedGradientNoise, vogelDiskSample } from '../utils/PostProcessingUtils.js';
 import { abs, normalize, cross } from '../math/MathNode.js';
-import { viewZToPerspectiveDepth } from '../display/ViewportDepthNode.js';
+import { viewZToPerspectiveDepth, viewZToReversedPerspectiveDepth } from '../display/ViewportDepthNode.js';
 
 const _clearColor = /*@__PURE__*/ new Color();
 const _projScreenMatrix = /*@__PURE__*/ new Matrix4();
@@ -94,7 +94,7 @@ export const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, s
 
 } );
 
-const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCoord, shadow } ) => {
+const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCoord, shadow }, builder ) => {
 
 	// for point lights, the uniform @vShadowCoord is re-purposed to hold
 	// the vector from the light to the world-space position of the fragment.
@@ -110,8 +110,19 @@ const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCo
 
 	If( viewZ.sub( shadowCameraFar ).lessThanEqual( 0.0 ).and( viewZ.sub( shadowCameraNear ).greaterThanEqual( 0.0 ) ), () => {
 
-		const dp = viewZToPerspectiveDepth( viewZ.negate(), shadowCameraNear, shadowCameraFar );
-		dp.addAssign( bias );
+		let dp;
+
+		if ( builder.renderer.reversedDepthBuffer ) {
+
+			dp = viewZToReversedPerspectiveDepth( viewZ.negate(), shadowCameraNear, shadowCameraFar );
+			dp.subAssign( bias );
+
+		} else {
+
+			dp = viewZToPerspectiveDepth( viewZ.negate(), shadowCameraNear, shadowCameraFar );
+			dp.addAssign( bias );
+
+		}
 
 		// bd3D = base direction 3D (direction from light to fragment)
 		const bd3D = shadowPosition.normalize();
@@ -206,7 +217,7 @@ class PointShadowNode extends ShadowNode {
 
 		const depthTexture = new CubeDepthTexture( shadow.mapSize.width );
 		depthTexture.name = 'PointShadowDepthTexture';
-		depthTexture.compareFunction = LessEqualCompare;
+		depthTexture.compareFunction = builder.renderer.reversedDepthBuffer ? GreaterEqualCompare : LessEqualCompare;
 
 		const shadowMap = builder.createCubeRenderTarget( shadow.mapSize.width );
 		shadowMap.texture.name = 'PointShadowMap';

+ 2 - 2
src/nodes/lighting/ShadowFilterNode.js

@@ -173,7 +173,7 @@ export const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoo
  * @param {Node<vec3>} inputs.shadowCoord - The shadow coordinates.
  * @return {Node<float>} The filtering result.
  */
-export const VSMShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, depthLayer } ) => {
+export const VSMShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, depthLayer }, builder ) => {
 
 	let distribution = texture( depthTexture ).sample( shadowCoord.xy );
 
@@ -188,7 +188,7 @@ export const VSMShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord,
 	const mean = distribution.x;
 	const variance = max( 0.0000001, distribution.y.mul( distribution.y ) );
 
-	const hardShadow = step( shadowCoord.z, mean );
+	const hardShadow = ( builder.renderer.reversedDepthBuffer ) ? step( mean, shadowCoord.z ) : step( shadowCoord.z, mean );
 
 	const output = float( 1 ).toVar(); // default, fully lit
 

+ 3 - 9
src/nodes/lighting/ShadowNode.js

@@ -11,7 +11,7 @@ import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
 import QuadMesh from '../../renderers/common/QuadMesh.js';
 import { Loop } from '../utils/LoopNode.js';
 import { screenCoordinate } from '../display/ScreenNode.js';
-import { HalfFloatType, LessEqualCompare, LinearFilter, NearestFilter, PCFShadowMap, PCFSoftShadowMap, RGFormat, VSMShadowMap, WebGPUCoordinateSystem } from '../../constants.js';
+import { GreaterEqualCompare, HalfFloatType, LessEqualCompare, LinearFilter, NearestFilter, PCFShadowMap, PCFSoftShadowMap, RGFormat, VSMShadowMap } from '../../constants.js';
 import { renderGroup } from '../core/UniformGroupNode.js';
 import { viewZToLogarithmicDepth } from '../display/ViewportDepthNode.js';
 import { lightShadowMatrix } from '../accessors/Lights.js';
@@ -352,12 +352,6 @@ class ShadowNode extends ShadowBaseNode {
 
 			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;
@@ -376,7 +370,7 @@ class ShadowNode extends ShadowBaseNode {
 		shadowCoord = vec3(
 			shadowCoord.x,
 			shadowCoord.y.oneMinus(), // follow webgpu standards
-			coordZ.add( bias )
+			renderer.reversedDepthBuffer ? coordZ.sub( bias ) : coordZ.add( bias )
 		);
 
 		return shadowCoord;
@@ -400,7 +394,7 @@ class ShadowNode extends ShadowBaseNode {
 
 		const depthTexture = new DepthTexture( shadow.mapSize.width, shadow.mapSize.height );
 		depthTexture.name = 'ShadowDepthTexture';
-		depthTexture.compareFunction = LessEqualCompare;
+		depthTexture.compareFunction = builder.renderer.reversedDepthBuffer ? GreaterEqualCompare : LessEqualCompare;
 
 		const shadowMap = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height );
 		shadowMap.texture.name = 'ShadowMap';

+ 2 - 2
src/renderers/common/Background.js

@@ -203,8 +203,8 @@ class Background extends DataMap {
 
 			//
 
-			renderContext.depthClearValue = renderer._clearDepth;
-			renderContext.stencilClearValue = renderer._clearStencil;
+			renderContext.depthClearValue = renderer.getClearDepth();
+			renderContext.stencilClearValue = renderer.getClearStencil();
 
 			renderContext.clearColor = renderer.autoClearColor === true;
 			renderContext.clearDepth = renderer.autoClearDepth === true;

+ 13 - 1
src/renderers/common/RenderContexts.js

@@ -9,8 +9,17 @@ class RenderContexts {
 
 	/**
 	 * Constructs a new render context management component.
+	 *
+	 * @param {Renderer} renderer - The renderer.
 	 */
-	constructor() {
+	constructor( renderer ) {
+
+		/**
+		 * The renderer.
+		 *
+		 * @type {Renderer}
+		 */
+		this.renderer = renderer;
 
 		/**
 		 * A dictionary that manages render contexts.
@@ -70,6 +79,9 @@ class RenderContexts {
 
 		if ( renderTarget !== null ) renderState.sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples;
 
+		renderState.clearDepthValue = this.renderer.getClearDepth();
+		renderState.clearStencilValue = this.renderer.getClearStencil();
+
 		return renderState;
 
 	}

+ 72 - 10
src/renderers/common/Renderer.js

@@ -59,6 +59,7 @@ class Renderer {
 	 *
 	 * @typedef {Object} Renderer~Options
 	 * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not.
+	 * @property {boolean} [reversedDepthBuffer=false] - Whether reversed depth buffer is enabled or not.
 	 * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque.
 	 * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not.
 	 * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not.
@@ -93,6 +94,7 @@ class Renderer {
 
 		const {
 			logarithmicDepthBuffer = false,
+			reversedDepthBuffer = false,
 			alpha = true,
 			depth = true,
 			stencil = false,
@@ -160,9 +162,19 @@ class Renderer {
 		 *
 		 * @type {boolean}
 		 * @default false
+		 * @readonly
 		 */
 		this.logarithmicDepthBuffer = logarithmicDepthBuffer;
 
+		/**
+		 * Whether reversed depth buffer is enabled or not.
+		 *
+		 * @type {boolean}
+		 * @default false
+		 * @readonly
+		 */
+		this.reversedDepthBuffer = reversedDepthBuffer;
+
 		/**
 		 * Defines the output color space of the renderer.
 		 *
@@ -784,7 +796,7 @@ class Renderer {
 			this._objects = new RenderObjects( this, this._nodes, this._geometries, this._pipelines, this._bindings, this.info );
 			this._renderLists = new RenderLists( this.lighting );
 			this._bundles = new RenderBundles();
-			this._renderContexts = new RenderContexts();
+			this._renderContexts = new RenderContexts( this );
 
 			//
 
@@ -1422,20 +1434,68 @@ class Renderer {
 
 		//
 
-		const coordinateSystem = this.coordinateSystem;
+
 		const xr = this.xr;
 
-		if ( camera.coordinateSystem !== coordinateSystem && xr.isPresenting === false ) {
+		if ( xr.isPresenting === false ) {
+
+			let projectionMatrixNeedsUpdate = false;
+
+			// reversed depth
 
-			camera.coordinateSystem = coordinateSystem;
-			camera.updateProjectionMatrix();
+			if ( this.reversedDepthBuffer === true && camera.reversedDepth !== true ) {
 
-			if ( camera.isArrayCamera ) {
+				camera._reversedDepth = true;
+
+				if ( camera.isArrayCamera ) {
+
+					for ( const subCamera of camera.cameras ) {
+
+						subCamera._reversedDepth = true;
+
+					}
+
+				}
+
+				projectionMatrixNeedsUpdate = true;
+
+			}
 
-				for ( const subCamera of camera.cameras ) {
+			// WebGPU/WebGL coordinate system
 
-					subCamera.coordinateSystem = coordinateSystem;
-					subCamera.updateProjectionMatrix();
+			const coordinateSystem = this.coordinateSystem;
+
+			if ( camera.coordinateSystem !== coordinateSystem ) {
+
+				camera.coordinateSystem = coordinateSystem;
+
+				if ( camera.isArrayCamera ) {
+
+					for ( const subCamera of camera.cameras ) {
+
+						subCamera.coordinateSystem = coordinateSystem;
+
+					}
+
+				}
+
+				projectionMatrixNeedsUpdate = true;
+
+			}
+
+			// camera update
+
+			if ( projectionMatrixNeedsUpdate === true ) {
+
+				camera.updateProjectionMatrix();
+
+				if ( camera.isArrayCamera ) {
+
+					for ( const subCamera of camera.cameras ) {
+
+						subCamera.updateProjectionMatrix();
+
+					}
 
 				}
 
@@ -2005,7 +2065,7 @@ class Renderer {
 	 */
 	getClearDepth() {
 
-		return this._clearDepth;
+		return ( this.reversedDepthBuffer === true ) ? 1 - this._clearDepth : this._clearDepth;
 
 	}
 
@@ -2097,6 +2157,8 @@ class Renderer {
 			renderContext.clearColorValue.g = color.g;
 			renderContext.clearColorValue.b = color.b;
 			renderContext.clearColorValue.a = color.a;
+			renderContext.clearDepthValue = this.getClearDepth();
+			renderContext.clearStencilValue = this.getClearStencil();
 			renderContext.activeCubeFace = this.getActiveCubeFace();
 			renderContext.activeMipmapLevel = this.getActiveMipmapLevel();
 

+ 8 - 1
src/renderers/webgl-fallback/WebGLBackend.js

@@ -28,6 +28,7 @@ class WebGLBackend extends Backend {
 	 *
 	 * @typedef {Object} WebGLBackend~Options
 	 * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not.
+	 * @property {boolean} [reversedDepthBuffer=false] - Whether reversed depth buffer is enabled or not.
 	 * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque.
 	 * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not.
 	 * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not.
@@ -184,7 +185,6 @@ class WebGLBackend extends Backend {
 		 */
 		this._knownBindings = new WeakSet();
 
-
 		/**
 		 * Whether the device supports framebuffers invalidation or not.
 		 *
@@ -265,11 +265,18 @@ class WebGLBackend extends Backend {
 		this.extensions.get( 'WEBGL_render_shared_exponent' );
 		this.extensions.get( 'WEBGL_multi_draw' );
 		this.extensions.get( 'OVR_multiview2' );
+		this.extensions.get( 'EXT_clip_control' );
 
 		this.disjoint = this.extensions.get( 'EXT_disjoint_timer_query_webgl2' );
 		this.parallel = this.extensions.get( 'KHR_parallel_shader_compile' );
 		this.drawBuffersIndexedExt = this.extensions.get( 'OES_draw_buffers_indexed' );
 
+		if ( parameters.reversedDepthBuffer === true && this.extensions.has( 'EXT_clip_control' ) ) {
+
+			this.state.setReversedDepth( true );
+
+		}
+
 	}
 
 	/**

+ 43 - 0
src/renderers/webgl-fallback/utils/WebGLState.js

@@ -12,6 +12,18 @@ import { error, warnOnce } from '../../../utils.js';
 
 let equationToGL, factorToGL;
 
+const reversedFuncs = {
+	[ NeverDepth ]: AlwaysDepth,
+	[ LessDepth ]: GreaterDepth,
+	[ EqualDepth ]: NotEqualDepth,
+	[ LessEqualDepth ]: GreaterEqualDepth,
+
+	[ AlwaysDepth ]: NeverDepth,
+	[ GreaterDepth ]: LessDepth,
+	[ NotEqualDepth ]: EqualDepth,
+	[ GreaterEqualDepth ]: LessEqualDepth,
+};
+
 /**
  * A WebGL 2 backend utility module for managing the WebGL state.
  *
@@ -63,6 +75,7 @@ class WebGLState {
 		this.currentPolygonOffsetFactor = null;
 		this.currentPolygonOffsetUnits = null;
 		this.currentColorMask = null;
+		this.currentDepthReversed = false;
 		this.currentDepthFunc = null;
 		this.currentDepthMask = null;
 		this.currentStencilFunc = null;
@@ -608,6 +621,34 @@ class WebGLState {
 
 	}
 
+
+	/**
+	 * Configures the WebGL state to use a reversed depth buffer.
+	 *
+	 * @param {boolean} reversed - Whether the depth buffer is reversed or not.
+	 */
+	setReversedDepth( reversed ) {
+
+		if ( this.currentDepthReversed !== reversed ) {
+
+			const ext = this.backend.extensions.get( 'EXT_clip_control' );
+
+			if ( reversed ) {
+
+				ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.ZERO_TO_ONE_EXT );
+
+			} else {
+
+				ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.NEGATIVE_ONE_TO_ONE_EXT );
+
+			}
+
+			this.currentDepthReversed = reversed;
+
+		}
+
+	}
+
 	/**
 	 * Specifies whether depth values can be written when rendering
 	 * into a framebuffer or not.
@@ -638,6 +679,8 @@ class WebGLState {
 	 */
 	setDepthFunc( depthFunc ) {
 
+		if ( this.currentDepthReversed ) depthFunc = reversedFuncs[ depthFunc ];
+
 		if ( this.currentDepthFunc !== depthFunc ) {
 
 			const { gl } = this;

+ 1 - 0
src/renderers/webgpu/WebGPUBackend.js

@@ -30,6 +30,7 @@ class WebGPUBackend extends Backend {
 	 *
 	 * @typedef {Object} WebGPUBackend~Options
 	 * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not.
+	 * @property {boolean} [reversedDepthBuffer=false] - Whether reversed depth buffer is enabled or not.
 	 * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque.
 	 * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not.
 	 * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not.

+ 1 - 0
src/renderers/webgpu/WebGPURenderer.js

@@ -32,6 +32,7 @@ class WebGPURenderer extends Renderer {
 	 *
 	 * @typedef {Object} WebGPURenderer~Options
 	 * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not.
+	 * @property {boolean} [reversedDepthBuffer=false] - Whether reversed depth buffer is enabled or not.
 	 * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque.
 	 * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not.
 	 * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not.

+ 14 - 2
src/renderers/webgpu/utils/WebGPUPipelineUtils.js

@@ -17,6 +17,18 @@ import {
 
 import { error, warnOnce } from '../../../utils.js';
 
+const reversedFuncs = {
+	[ NeverDepth ]: AlwaysDepth,
+	[ LessDepth ]: GreaterDepth,
+	[ EqualDepth ]: NotEqualDepth,
+	[ LessEqualDepth ]: GreaterEqualDepth,
+
+	[ AlwaysDepth ]: NeverDepth,
+	[ GreaterDepth ]: LessDepth,
+	[ NotEqualDepth ]: EqualDepth,
+	[ GreaterEqualDepth ]: LessEqualDepth,
+};
+
 /**
  * A WebGPU backend utility module for managing pipelines.
  *
@@ -790,11 +802,11 @@ class WebGPUPipelineUtils {
 
 		if ( material.depthTest === false ) {
 
-			depthCompare = GPUCompareFunction.Always;
+			depthCompare = ( this.backend.parameters.reversedDepthBuffer ) ? GPUCompareFunction.Never : GPUCompareFunction.Always;
 
 		} else {
 
-			const depthFunc = material.depthFunc;
+			const depthFunc = ( this.backend.parameters.reversedDepthBuffer ) ? reversedFuncs[ material.depthFunc ] : material.depthFunc;
 
 			switch ( depthFunc ) {
 

粤ICP备19079148号