Skip to content

Commit 6e78ad5

Browse files
authored
TSL: Pre-pass using global context (#32276)
1 parent aee0fd4 commit 6e78ad5

File tree

6 files changed

+117
-46
lines changed

6 files changed

+117
-46
lines changed

examples/jsm/tsl/display/GTAONode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,4 +568,4 @@ function generateMagicSquare( size ) {
568568
* @param {Camera} camera - The camera the scene is rendered with.
569569
* @returns {GTAONode}
570570
*/
571-
export const ao = ( depthNode, normalNode, camera ) => nodeObject( new GTAONode( nodeObject( depthNode ), nodeObject( normalNode ), camera ) );
571+
export const ao = ( depthNode, normalNode, camera ) => new GTAONode( nodeObject( depthNode ), nodeObject( normalNode ), camera );

examples/webgpu_postprocessing_ao.html

Lines changed: 56 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<script type="module">
3737

3838
import * as THREE from 'three/webgpu';
39-
import { pass, mrt, output, normalView, velocity, vec3, vec4, directionToColor, colorSpaceToWorking } from 'three/tsl';
39+
import { sample, pass, mrt, context, screenUV, normalView, velocity, vec3, vec4, directionToColor, colorToDirection, colorSpaceToWorking } from 'three/tsl';
4040
import { ao } from 'three/addons/tsl/display/GTAONode.js';
4141
import { traa } from 'three/addons/tsl/display/TRAANode.js';
4242

@@ -49,7 +49,7 @@
4949

5050
let camera, scene, renderer, postProcessing, controls;
5151

52-
let aoPass, traaPass, blendPassAO, scenePassColor;
52+
let aoPass, traaPass;
5353

5454
const params = {
5555
samples: 16,
@@ -79,15 +79,7 @@
7979

8080
await renderer.init();
8181

82-
const environment = new RoomEnvironment();
83-
const pmremGenerator = new THREE.PMREMGenerator( renderer );
84-
85-
scene.background = new THREE.Color( 0x666666 );
86-
scene.environment = pmremGenerator.fromScene( environment ).texture;
87-
environment.dispose();
88-
pmremGenerator.dispose();
89-
90-
//
82+
// controls
9183

9284
controls = new OrbitControls( camera, renderer.domElement );
9385
controls.target.set( 0, 0.5, - 1 );
@@ -97,43 +89,70 @@
9789
controls.minDistance = 2;
9890
controls.maxDistance = 8;
9991

100-
//
92+
// environment
93+
94+
const environment = new RoomEnvironment();
95+
const pmremGenerator = new THREE.PMREMGenerator( renderer );
96+
97+
scene.background = new THREE.Color( 0x666666 );
98+
scene.environment = pmremGenerator.fromScene( environment ).texture;
99+
environment.dispose();
100+
pmremGenerator.dispose();
101+
102+
// post-processing
101103

102104
postProcessing = new THREE.PostProcessing( renderer );
103105

104-
const scenePass = pass( scene, camera );
105-
scenePass.setMRT( mrt( {
106-
output: output,
107-
normal: normalView,
106+
// pre-pass
107+
108+
const prePass = pass( scene, camera ).toInspector( 'Normal', ( inspectNode ) => colorSpaceToWorking( inspectNode, THREE.SRGBColorSpace ) );
109+
prePass.name = 'Pre-Pass';
110+
prePass.transparent = false;
111+
112+
prePass.setMRT( mrt( {
113+
output: directionToColor( normalView ),
108114
velocity: velocity
109115
} ) );
110116

111-
scenePassColor = scenePass.getTextureNode( 'output' ).toInspector( 'Color' );
112-
const scenePassDepth = scenePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => {
117+
const prePassNormal = sample( ( uv ) => {
113118

114-
return scenePass.getLinearDepthNode();
119+
return colorToDirection( prePass.getTextureNode().sample( uv ) );
115120

116121
} );
117-
const scenePassNormal = scenePass.getTextureNode( 'normal' ).toInspector( 'Normal', () => {
118122

119-
return colorSpaceToWorking( directionToColor( scenePassNormal ), THREE.SRGBColorSpace );
123+
const prePassDepth = prePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => prePass.getLinearDepthNode() );
124+
const prePassVelocity = prePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' );
120125

121-
} );
122-
const scenePassVelocity = scenePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' );
126+
// pre-pass - bandwidth optimization
127+
128+
const normalTexture = prePass.getTexture( 'output' );
129+
normalTexture.type = THREE.UnsignedByteType;
130+
131+
// scene pass
132+
133+
const scenePass = pass( scene, camera ).toInspector( 'Color' );
123134

124135
// ao
125136

126-
aoPass = ao( scenePassDepth, scenePassNormal, camera );
137+
aoPass = ao( prePassDepth, prePassNormal, camera ).toInspector( 'GTAO', ( inspectNode ) => inspectNode.r );
127138
aoPass.resolutionScale = 0.5; // running AO in half resolution is often sufficient
128139
aoPass.useTemporalFiltering = true;
129-
blendPassAO = vec4( scenePassColor.rgb.mul( aoPass.r.toInspector( 'AO' ) ), scenePassColor.a ); // the AO is stored only in the red channel
130140

131-
// traa
141+
const aoPassOutput = aoPass.getTextureNode( 'output' );
142+
143+
// scene context
144+
145+
scenePass.contextNode = context( {
146+
ao: aoPassOutput.sample( screenUV ).r
147+
} );
148+
149+
// final output + traa
150+
151+
traaPass = traa( scenePass, prePassDepth, prePassVelocity, camera );
132152

133-
traaPass = traa( blendPassAO, scenePassDepth, scenePassVelocity, camera );
134153
postProcessing.outputNode = traaPass;
135154

136-
//
155+
// models
137156

138157
const dracoLoader = new DRACOLoader();
139158
dracoLoader.setDecoderPath( 'jsm/libs/draco/' );
@@ -148,18 +167,16 @@
148167
model.position.set( 0, 1, 0 );
149168
scene.add( model );
150169

151-
model.traverse( ( o ) => {
152-
153-
// Transparent objects (e.g. loaded via GLTFLoader) might have "depthWrite" set to "false".
154-
// This is wanted when rendering the beauty pass however it produces wrong results when computing
155-
// AO since depth and normal data are out of sync. Computing normals from depth by not using MRT
156-
// can mitigate the issue although the depth information (and thus the normals) are not correct in
157-
// first place. Besides, normal estimation is computationally more expensive than just sampling a
158-
// normal texture. So depending on your scene, consider to enable "depthWrite" for all transparent objects.
170+
//
159171

160-
if ( o.material ) o.material.depthWrite = true;
172+
const transparentMesh = new THREE.Mesh( new THREE.PlaneGeometry( 1.8, 2 ), new THREE.MeshStandardNodeMaterial( { transparent: true, opacity: .1 } ) );
173+
transparentMesh.material.transparent = true;
174+
transparentMesh.position.z = 0;
175+
transparentMesh.position.y = 0.5;
176+
transparentMesh.visible = false;
177+
scene.add( transparentMesh );
161178

162-
} );
179+
// events
163180

164181
window.addEventListener( 'resize', onWindowResize );
165182

@@ -173,6 +190,7 @@
173190
gui.add( params, 'scale', 0.01, 2 ).onChange( updateParameters );
174191
gui.add( params, 'thickness', 0.01, 2 ).onChange( updateParameters );
175192
gui.add( aoPass, 'useTemporalFiltering' ).name( 'temporal filtering' );
193+
gui.add( transparentMesh, 'visible' ).name( 'show transparent mesh' );
176194
gui.add( params, 'aoOnly' ).onChange( ( value ) => {
177195

178196
if ( value === true ) {

src/materials/nodes/NodeMaterial.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1060,9 +1060,17 @@ class NodeMaterial extends Material {
10601060

10611061
}
10621062

1063+
let aoNode = builder.context.ao || null;
1064+
10631065
if ( this.aoNode !== null || builder.material.aoMap ) {
10641066

1065-
const aoNode = this.aoNode !== null ? this.aoNode : materialAO;
1067+
const mtlAO = this.aoNode !== null ? this.aoNode : materialAO;
1068+
1069+
aoNode = aoNode !== null ? aoNode.mul( mtlAO ) : mtlAO;
1070+
1071+
}
1072+
1073+
if ( aoNode !== null ) {
10661074

10671075
materialLightsNode.push( new AONode( aoNode ) );
10681076

src/materials/nodes/manager/NodeMaterialObserver.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ class NodeMaterialObserver {
259259

260260
}
261261

262-
if ( builder.context.modelViewMatrix || builder.context.modelNormalViewMatrix )
262+
if ( builder.context.modelViewMatrix || builder.context.modelNormalViewMatrix || builder.context.ao )
263263
return true;
264264

265265
return false;

src/nodes/core/NodeFrame.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,13 @@ class NodeFrame {
155155

156156
if ( nodeUpdateBeforeMap.frameId !== this.frameId ) {
157157

158-
if ( node.updateBefore( this ) !== false ) {
158+
const previousFrameId = nodeUpdateBeforeMap.frameId;
159159

160-
nodeUpdateBeforeMap.frameId = this.frameId;
160+
nodeUpdateBeforeMap.frameId = this.frameId;
161+
162+
if ( node.updateBefore( this ) === false ) {
163+
164+
nodeUpdateBeforeMap.frameId = previousFrameId;
161165

162166
}
163167

@@ -169,9 +173,13 @@ class NodeFrame {
169173

170174
if ( nodeUpdateBeforeMap.renderId !== this.renderId ) {
171175

172-
if ( node.updateBefore( this ) !== false ) {
176+
const previousRenderId = nodeUpdateBeforeMap.renderId;
177+
178+
nodeUpdateBeforeMap.renderId = this.renderId;
179+
180+
if ( node.updateBefore( this ) === false ) {
173181

174-
nodeUpdateBeforeMap.renderId = this.renderId;
182+
nodeUpdateBeforeMap.renderId = previousRenderId;
175183

176184
}
177185

src/nodes/display/PassNode.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,29 @@ class PassNode extends TempNode {
248248
*/
249249
this.renderTarget = renderTarget;
250250

251+
/**
252+
* An optional override material for the pass.
253+
*
254+
* @type {Material|null}
255+
*/
256+
this.overrideMaterial = null;
257+
258+
/**
259+
* Whether the pass is transparent.
260+
*
261+
* @type {boolean}
262+
* @default false
263+
*/
264+
this.transparent = true;
265+
266+
/**
267+
* Whether the pass is opaque.
268+
*
269+
* @type {boolean}
270+
* @default true
271+
*/
272+
this.opaque = true;
273+
251274
/**
252275
* An optional global context for the pass.
253276
*
@@ -754,8 +777,11 @@ class PassNode extends TempNode {
754777
const currentRenderTarget = renderer.getRenderTarget();
755778
const currentMRT = renderer.getMRT();
756779
const currentAutoClear = renderer.autoClear;
780+
const currentTransparent = renderer.transparent;
781+
const currentOpaque = renderer.opaque;
757782
const currentMask = camera.layers.mask;
758783
const currentContextNode = renderer.contextNode;
784+
const currentOverrideMaterial = scene.overrideMaterial;
759785

760786
this._cameraNear.value = camera.near;
761787
this._cameraFar.value = camera.far;
@@ -772,17 +798,25 @@ class PassNode extends TempNode {
772798

773799
}
774800

801+
if ( this.overrideMaterial !== null ) {
802+
803+
scene.overrideMaterial = this.overrideMaterial;
804+
805+
}
806+
775807
renderer.setRenderTarget( this.renderTarget );
776808
renderer.setMRT( this._mrt );
777809
renderer.autoClear = true;
810+
renderer.transparent = this.transparent;
811+
renderer.opaque = this.opaque;
778812

779813
if ( this.contextNode !== null ) {
780814

781815
if ( this._contextNodeCache === null || this._contextNodeCache.version !== this.version ) {
782816

783817
this._contextNodeCache = {
784818
version: this.version,
785-
context: context( { ...renderer.contextNode.value, ...this.contextNode.getFlowContextData() } )
819+
context: context( { ...renderer.contextNode.getFlowContextData(), ...this.contextNode.getFlowContextData() } )
786820
};
787821

788822
}
@@ -798,10 +832,13 @@ class PassNode extends TempNode {
798832
renderer.render( scene, camera );
799833

800834
scene.name = currentSceneName;
835+
scene.overrideMaterial = currentOverrideMaterial;
801836

802837
renderer.setRenderTarget( currentRenderTarget );
803838
renderer.setMRT( currentMRT );
804839
renderer.autoClear = currentAutoClear;
840+
renderer.transparent = currentTransparent;
841+
renderer.opaque = currentOpaque;
805842
renderer.contextNode = currentContextNode;
806843

807844
camera.layers.mask = currentMask;

0 commit comments

Comments
 (0)