Skip to content

Conversation

@CodyJasonBennett
Copy link
Contributor

@CodyJasonBennett CodyJasonBennett commented Jul 5, 2025

Fixed #29770

Description

Introduces ReversedCoordinateSystem to produce a [0, 1] reversed projection matrix. This was previously done internally on the GPU with #29445. For backwards compatibility purposes, I have deprecated this conversion, and it now emits a warning, encouraging cameras to be configured with ReversedCoordinateSystem when using reverse depth. As reverse depth in WebGL relies on EXT_clip_control, it is best to use feature detection:

const renderer = new THREE.WebGLRenderer( { reverseDepthBuffer: true } );

const camera = new THREE.PerspectiveCamera();

if ( renderer.capabilities.reverseDepthBuffer ) { // Firefox, namely

	camera.coordinateSystem = THREE.ReversedCoordinateSystem;
	camera.updateProjectionMatrix();

}

Shadow maps are also fixed in this PR, which previously had misconfigured shadow cameras and the wrong comparison operator (as depth is now reversed). Shaders that rely on a specific NDC range or linearize depth still need to be updated.

I have not updated cascaded shadow maps (CSM) and other add-ons like GTAO with this PR.

Before After
Before After

All shadow map types were tested with this PR. Below is a quality comparison after changes with/without reverseDepthBuffer.

Note that an ideal setup for reverseDepthBuffer configures a floating-point depth buffer in post-processing #29445 (comment).

Default Reverse Depth
BasicShadowMap Default Reverse Depth
PCFShadowMap Default Reverse Depth
PCFSoftShadowMap Default Reverse Depth
VSMShadowMap Default Reverse Depth

@github-actions
Copy link

github-actions bot commented Jul 5, 2025

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 337.69
78.76
338.36
78.95
+675 B
+187 B
WebGPU 557.69
154.41
557.72
154.42
+25 B
+8 B
WebGPU Nodes 556.61
154.19
556.64
154.2
+25 B
+7 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 468.91
113.45
469.6
113.66
+691 B
+205 B
WebGPU 633.32
171.43
633.39
171.45
+73 B
+24 B
WebGPU Nodes 588.45
160.78
588.52
160.8
+73 B
+25 B

Comment on lines -46 to +60
float depth = 1.0 - unpackRGBAToDepth( texture2D( tDiffuse, vUv ) );
gl_FragColor = vec4( vec3( depth ), opacity );
float depth = unpackRGBAToDepth( texture2D( tDiffuse, vUv ) );
#ifdef USE_REVERSEDEPTHBUF
if ( depth == 1.0 ) depth = 0.0; // wrong clear value?
// [0, 1] -> [-1, 1]
depth = depth * 2.0 - 1.0;
// Reverse to forward depth (precision is already destroyed at this point)
depth = 1.0 - depth;
#endif
gl_FragColor = vec4( vec3( 1.0 - depth ), opacity );
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that appearance is consistent regardless of configuration (press T on the shadow map example).

The special handling on L50 is a clearing bug, though, but I am not sure how to go about it. The same is in shadowmap_pars_fragment.

Comment on lines +1582 to +1590
/**
* Reversed coordinate system [1, 0].
*
* Used for reverse depth buffer.
*
* @type {number}
* @constant
*/
export const ReversedCoordinateSystem = 2002;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not technically need "ReversedWebGPUCoordinateSystem" and "ReversedWebGLCoordinateSystem"? Or is this just for WebGPU?

Copy link
Collaborator

@Mugen87 Mugen87 Jul 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point reverse depth buffer will also be supported in WebGPURenderer. If the respective computations in Matrix4 differ in WebGL and WebGPU, then we should indeed rename to ReversedWebGLCoordinateSystem.

Copy link
Contributor Author

@CodyJasonBennett CodyJasonBennett Jul 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only need one. Matrix4 as implemented will just work in WebGPU (its derivation is based on how we make one for WebGPU normally), provided that depth comparisons and clear values are configured to be reversed. Reverse depth can only be implemented with a [0, 1] NDC range, which is what WebGPU and modern graphics APIs use and what we use in WebGL via EXT_clip_control, which is otherwise [−1, 1]. I explain why this range matters in floating point and the nature of it in previous comments, more recently in #29445 (comment).

Regarding changes, apart from the reversed range, they are the same as you would make when migrating from WebGL to WebGPU. The WebGPU side would only be concerned about the reversed range. The only other constant I would consider would be for an infinite reversed matrix, which only works for perspective #11755. Explicitly handling Infinity might be a better way, provided we document it.

Importantly, as we no longer automatically convert the projection matrix for a configured camera, it is possible to construct an infinite reversed projection matrix in user-land. I'd be happy to create an example for exploration purposes and show its effect for derivations in post-processing. I found it simplifies things tremendously when dealing with depth and occlusion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reversed coordinate system?

The depth range or depth mapping is reversed, not the coordinate system.

Copy link
Contributor Author

@CodyJasonBennett CodyJasonBennett Jul 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not true here, and yet the use is still incorrect, although not strictly, but this is the API we established to make WebGPU possible #26140, which uses modern D3D convention. Do we really need to be pedantic about this, or can you suggest another API that would make sense?

I must say that I am incredibly unhappy to entertain sweeping, breaking changes for the sake of correctness if not strictly (in)correct, and I think that notion is disillusioned to begin with. Unless you have another suggestion, I think we need to live with words not having a strictly encompassing or even correct meaning at all times.

@Mugen87 Mugen87 added this to the r179 milestone Jul 6, 2025
@Mugen87 Mugen87 merged commit 36c2b66 into mrdoob:dev Jul 7, 2025
19 of 20 checks passed
@CodyJasonBennett CodyJasonBennett deleted the fix-shadows-reverse-z branch July 14, 2025 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support shadow mapping with reverse Z

4 participants