Browse Source

TRAANode: New TRAA implementation. (#31421)

* TRAANode: New TRAA implementation.

* TRAANode: Fix first frame.

* introduce post-processing context

* add for renderAsync

* TRAANode: Fix resize darkening bug.

* TRAANode: Fix render target selection.

---------

Co-authored-by: sunag <sunagbrasil@gmail.com>
Michael Herzog 7 months ago
parent
commit
71f36f2080

+ 179 - 170
examples/jsm/tsl/display/TRAAPassNode.js → examples/jsm/tsl/display/TRAANode.js

@@ -1,5 +1,5 @@
-import { Color, Vector2, NearestFilter, Matrix4, RendererUtils, PassNode, QuadMesh, NodeMaterial } from 'three/webgpu';
-import { add, float, If, Loop, int, Fn, min, max, clamp, nodeObject, texture, uniform, uv, vec2, vec4, luminance } from 'three/tsl';
+import { HalfFloatType, Vector2, RenderTarget, RendererUtils, QuadMesh, NodeMaterial, TempNode, NodeUpdateType, Matrix4 } from 'three/webgpu';
+import { add, float, If, Loop, int, Fn, min, max, clamp, nodeObject, texture, uniform, uv, vec2, vec4, luminance, convertToTexture, passTexture, velocity } from 'three/tsl';
 
 const _quadMesh = /*@__PURE__*/ new QuadMesh();
 const _size = /*@__PURE__*/ new Vector2();
@@ -8,34 +8,34 @@ let _rendererState;
 
 
 /**
- * A special render pass node that renders the scene with TRAA (Temporal Reprojection Anti-Aliasing).
- *
- * Note: The current implementation does not yet support MRT setups.
+ * A special node that applies TRAA (Temporal Reprojection Anti-Aliasing).
  *
  * References:
  * - {@link https://alextardif.com/TAA.html}
  * - {@link https://www.elopezr.com/temporal-aa-and-the-quest-for-the-holy-trail/}
  *
  * @augments PassNode
- * @three_import import { traaPass } from 'three/addons/tsl/display/TRAAPassNode.js';
+ * @three_import import { traa } from 'three/addons/tsl/display/TRAANode.js';
  */
-class TRAAPassNode extends PassNode {
+class TRAANode extends TempNode {
 
 	static get type() {
 
-		return 'TRAAPassNode';
+		return 'TRAANode';
 
 	}
 
 	/**
-	 * Constructs a new TRAA pass node.
+	 * Constructs a new TRAA node.
 	 *
-	 * @param {Scene} scene - The scene to render.
-	 * @param {Camera} camera - The camera to render the scene with.
+	 * @param {TextureNode} beautyNode - The texture node that represents the input of the effect.
+	 * @param {TextureNode} depthNode - A node that represents the scene's depth.
+	 * @param {TextureNode} velocityNode - A node that represents the scene's velocity.
+	 * @param {Camera} camera - The camera the scene is rendered with.
 	 */
-	constructor( scene, camera ) {
+	constructor( beautyNode, depthNode, velocityNode, camera ) {
 
-		super( PassNode.COLOR, scene, camera );
+		super( 'vec4' );
 
 		/**
 		 * This flag can be used for type testing.
@@ -44,40 +44,53 @@ class TRAAPassNode extends PassNode {
 		 * @readonly
 		 * @default true
 		 */
-		this.isTRAAPassNode = true;
+		this.isTRAANode = true;
 
 		/**
-		 * The clear color of the pass.
+		 * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders
+		 * its effect once per frame in `updateBefore()`.
 		 *
-		 * @type {Color}
-		 * @default 0x000000
+		 * @type {string}
+		 * @default 'frame'
 		 */
-		this.clearColor = new Color( 0x000000 );
+		this.updateBeforeType = NodeUpdateType.FRAME;
 
 		/**
-		 * The clear alpha of the pass.
+		 * The texture node that represents the input of the effect.
 		 *
-		 * @type {number}
-		 * @default 0
+		 * @type {TextureNode}
 		 */
-		this.clearAlpha = 0;
+		this.beautyNode = beautyNode;
 
 		/**
-		 * The jitter index selects the current camera offset value.
+		 * A node that represents the scene's velocity.
 		 *
-		 * @private
-		 * @type {number}
-		 * @default 0
+		 * @type {TextureNode}
 		 */
-		this._jitterIndex = 0;
+		this.depthNode = depthNode;
 
 		/**
-		 * Used to save the original/unjittered projection matrix.
+		 * A node that represents the scene's velocity.
+		 *
+		 * @type {TextureNode}
+		 */
+		this.velocityNode = velocityNode;
+
+		/**
+		 *  The camera the scene is rendered with.
+		 *
+		 * @type {TextureNode}
+		 */
+		this.camera = camera;
+
+		/**
+		 * The jitter index selects the current camera offset value.
 		 *
 		 * @private
-		 * @type {Matrix4}
+		 * @type {number}
+		 * @default 0
 		 */
-		this._originalProjectionMatrix = new Matrix4();
+		this._jitterIndex = 0;
 
 		/**
 		 * A uniform node holding the inverse resolution value.
@@ -88,22 +101,22 @@ class TRAAPassNode extends PassNode {
 		this._invSize = uniform( new Vector2() );
 
 		/**
-		 * The render target that holds the current sample.
+		 * The render target that represents the history of frame data.
 		 *
 		 * @private
 		 * @type {?RenderTarget}
-		 * @default null
 		 */
-		this._sampleRenderTarget = null;
+		this._historyRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } );
+		this._historyRenderTarget.texture.name = 'TRAANode.history';
 
 		/**
-		 * The render target that represents the history of frame data.
+		 * The render target for the resolve.
 		 *
 		 * @private
 		 * @type {?RenderTarget}
-		 * @default null
 		 */
-		this._historyRenderTarget = null;
+		this._resolveRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } );
+		this._resolveRenderTarget.texture.name = 'TRAANode.resolve';
 
 		/**
 		 * Material used for the resolve step.
@@ -112,7 +125,41 @@ class TRAAPassNode extends PassNode {
 		 * @type {NodeMaterial}
 		 */
 		this._resolveMaterial = new NodeMaterial();
-		this._resolveMaterial.name = 'TRAA.Resolve';
+		this._resolveMaterial.name = 'TRAA.resolve';
+
+		/**
+		 * The result of the effect is represented as a separate texture node.
+		 *
+		 * @private
+		 * @type {PassTextureNode}
+		 */
+		this._textureNode = passTexture( this, this._resolveRenderTarget.texture );
+
+		/**
+		 * Used to save the original/unjittered projection matrix.
+		 *
+		 * @private
+		 * @type {Matrix4}
+		 */
+		this._originalProjectionMatrix = new Matrix4();
+
+		/**
+		 * Sync the post processing stack with the TRAA node.
+		 * @private
+		 * @type {boolean}
+		 */
+		this._needsPostProcessingSync = false;
+
+	}
+
+	/**
+	 * Returns the result of the effect as a texture node.
+	 *
+	 * @return {PassTextureNode} A texture node that represents the result of the effect.
+	 */
+	getTextureNode() {
+
+		return this._textureNode;
 
 	}
 
@@ -121,78 +168,48 @@ class TRAAPassNode extends PassNode {
 	 *
 	 * @param {number} width - The width of the effect.
 	 * @param {number} height - The height of the effect.
-	 * @return {boolean} Whether the TRAA needs a restart or not. That is required after a resize since buffer data with different sizes can't be resolved.
 	 */
 	setSize( width, height ) {
 
-		super.setSize( width, height );
-
-		let needsRestart = false;
-
-		if ( this.renderTarget.width !== this._sampleRenderTarget.width || this.renderTarget.height !== this._sampleRenderTarget.height ) {
-
-			this._sampleRenderTarget.setSize( this.renderTarget.width, this.renderTarget.height );
-			this._historyRenderTarget.setSize( this.renderTarget.width, this.renderTarget.height );
-
-			this._invSize.value.set( 1 / this.renderTarget.width, 1 / this.renderTarget.height );
-
-			needsRestart = true;
-
-		}
+		this._historyRenderTarget.setSize( width, height );
+		this._resolveRenderTarget.setSize( width, height );
 
-		return needsRestart;
+		this._invSize.value.set( 1 / width, 1 / height );
 
 	}
 
 	/**
-	 * This method is used to render the effect once per frame.
+	 * Defines the TRAA's current jitter as a view offset
+	 * to the scene's camera.
 	 *
-	 * @param {NodeFrame} frame - The current node frame.
+	 * @param {number} width - The width of the effect.
+	 * @param {number} height - The height of the effect.
 	 */
-	updateBefore( frame ) {
-
-		const { renderer } = frame;
-		const { scene, camera } = this;
-
-		_rendererState = RendererUtils.resetRendererState( renderer, _rendererState );
-
-		//
-
-		this._pixelRatio = renderer.getPixelRatio();
-		const size = renderer.getSize( _size );
-
-		const needsRestart = this.setSize( size.width, size.height );
+	setViewOffset( width, height ) {
 
 		// save original/unjittered projection matrix for velocity pass
 
-		camera.updateProjectionMatrix();
-		this._originalProjectionMatrix.copy( camera.projectionMatrix );
+		this.camera.updateProjectionMatrix();
+		this._originalProjectionMatrix.copy( this.camera.projectionMatrix );
 
-		// camera configuration
+		velocity.setProjectionMatrix( this._originalProjectionMatrix );
 
-		this._cameraNear.value = camera.near;
-		this._cameraFar.value = camera.far;
-
-		// configure jitter as view offset
+		//
 
 		const viewOffset = {
 
-			fullWidth: this.renderTarget.width,
-			fullHeight: this.renderTarget.height,
+			fullWidth: width,
+			fullHeight: height,
 			offsetX: 0,
 			offsetY: 0,
-			width: this.renderTarget.width,
-			height: this.renderTarget.height
+			width: width,
+			height: height
 
 		};
 
-		const originalViewOffset = Object.assign( {}, camera.view );
-
-		if ( originalViewOffset.enabled ) Object.assign( viewOffset, originalViewOffset );
-
 		const jitterOffset = _JitterVectors[ this._jitterIndex ];
 
-		camera.setViewOffset(
+		this.camera.setViewOffset(
 
 			viewOffset.fullWidth, viewOffset.fullHeight,
 
@@ -202,95 +219,88 @@ class TRAAPassNode extends PassNode {
 
 		);
 
-		// configure velocity
-
-		const mrt = this.getMRT();
-		const velocityOutput = mrt.get( 'velocity' );
-
-		if ( velocityOutput !== undefined ) {
-
-			velocityOutput.setProjectionMatrix( this._originalProjectionMatrix );
-
-		} else {
-
-			throw new Error( 'THREE:TRAAPassNode: Missing velocity output in MRT configuration.' );
-
-		}
-
-		// render sample
+	}
 
-		renderer.setMRT( mrt );
+	/**
+	 * Clears the view offset from the scene's camera.
+	 */
+	clearViewOffset() {
 
-		renderer.setClearColor( this.clearColor, this.clearAlpha );
-		renderer.setRenderTarget( this._sampleRenderTarget );
-		renderer.render( scene, camera );
+		this.camera.clearViewOffset();
 
-		renderer.setRenderTarget( null );
-		renderer.setMRT( null );
+		velocity.setProjectionMatrix( null );
 
-		// every time when the dimensions change we need fresh history data. Copy the sample
-		// into the history and final render target (no AA happens at that point).
+		// update jitter index
 
-		if ( needsRestart === true ) {
+		this._jitterIndex ++;
+		this._jitterIndex = this._jitterIndex % ( _JitterVectors.length - 1 );
 
-			// bind and clear render target to make sure they are initialized after the resize which triggers a dispose()
+	}
 
-			renderer.setRenderTarget( this._historyRenderTarget );
-			renderer.clear();
+	/**
+	 * This method is used to render the effect once per frame.
+	 *
+	 * @param {NodeFrame} frame - The current node frame.
+	 */
+	updateBefore( frame ) {
 
-			renderer.setRenderTarget( this.renderTarget );
-			renderer.clear();
+		const { renderer } = frame;
 
-			renderer.setRenderTarget( null );
+		// keep the TRAA in sync with the dimensions of the beauty node
 
-			renderer.copyTextureToTexture( this._sampleRenderTarget.texture, this._historyRenderTarget.texture );
-			renderer.copyTextureToTexture( this._sampleRenderTarget.texture, this.renderTarget.texture );
+		const beautyRenderTarget = ( this.beautyNode.isRTTNode ) ? this.beautyNode.renderTarget : this.beautyNode.passNode.renderTarget;
 
-		} else {
+		const width = beautyRenderTarget.texture.width;
+		const height = beautyRenderTarget.texture.height;
 
-			// resolve
+		//
 
-			renderer.setRenderTarget( this.renderTarget );
-			_quadMesh.material = this._resolveMaterial;
-			_quadMesh.render( renderer );
-			renderer.setRenderTarget( null );
+		if ( this._needsPostProcessingSync === true ) {
 
-			// update history
+			this.setViewOffset( width, height );
 
-			renderer.copyTextureToTexture( this.renderTarget.texture, this._historyRenderTarget.texture );
+			this._needsPostProcessingSync = false;
 
 		}
 
-		// copy depth
+		_rendererState = RendererUtils.resetRendererState( renderer, _rendererState );
+
+		//
 
-		renderer.copyTextureToTexture( this._sampleRenderTarget.depthTexture, this.renderTarget.depthTexture );
+		const needsRestart = this._historyRenderTarget.width !== width || this._historyRenderTarget.height !== height;
+		this.setSize( width, height );
 
-		// update jitter index
+		// every time when the dimensions change we need fresh history data
 
-		this._jitterIndex ++;
-		this._jitterIndex = this._jitterIndex % ( _JitterVectors.length - 1 );
+		if ( needsRestart === true ) {
 
-		// restore
+			// bind and clear render target to make sure they are initialized after the resize which triggers a dispose()
 
-		if ( originalViewOffset.enabled ) {
+			renderer.setRenderTarget( this._historyRenderTarget );
+			renderer.clear();
 
-			camera.setViewOffset(
+			renderer.setRenderTarget( this._resolveRenderTarget );
+			renderer.clear();
 
-				originalViewOffset.fullWidth, originalViewOffset.fullHeight,
+			// make sure to reset the history with the contents of the beauty buffer otherwise subsequent frames after the
+			// resize will fade from a darker color to the correct one because the history was cleared with black.
 
-				originalViewOffset.offsetX, originalViewOffset.offsetY,
+			renderer.copyTextureToTexture( beautyRenderTarget.texture, this._historyRenderTarget.texture );
 
-				originalViewOffset.width, originalViewOffset.height
+		}
 
-			);
+		// resolve
 
-		} else {
+		renderer.setRenderTarget( this._resolveRenderTarget );
+		_quadMesh.material = this._resolveMaterial;
+		_quadMesh.render( renderer );
+		renderer.setRenderTarget( null );
 
-			camera.clearViewOffset();
+		// update history
 
-		}
+		renderer.copyTextureToTexture( this._resolveRenderTarget.texture, this._historyRenderTarget.texture );
 
-		velocityOutput.setProjectionMatrix( null );
+		// restore
 
 		RendererUtils.restoreRendererState( renderer, _rendererState );
 
@@ -304,28 +314,31 @@ class TRAAPassNode extends PassNode {
 	 */
 	setup( builder ) {
 
-		if ( this._sampleRenderTarget === null ) {
+		const postProcessing = builder.context.postProcessing;
 
-			this._sampleRenderTarget = this.renderTarget.clone();
-			this._historyRenderTarget = this.renderTarget.clone();
+		if ( postProcessing ) {
 
-			this._sampleRenderTarget.texture.minFiler = NearestFilter;
-			this._sampleRenderTarget.texture.magFilter = NearestFilter;
+			this._needsPostProcessingSync = true;
 
-			const velocityTarget = this._sampleRenderTarget.texture.clone();
-			velocityTarget.isRenderTargetTexture = true;
-			velocityTarget.name = 'velocity';
+			postProcessing.context.onBeforePostProcessing = () => {
 
-			this._sampleRenderTarget.textures.push( velocityTarget ); // for MRT
+				const size = builder.renderer.getDrawingBufferSize( _size );
+				this.setViewOffset( size.width, size.height );
 
-		}
+			};
 
-		// textures
+			postProcessing.context.onAfterPostProcessing = () => {
+
+				this.clearViewOffset();
+
+			};
+
+		}
 
 		const historyTexture = texture( this._historyRenderTarget.texture );
-		const sampleTexture = texture( this._sampleRenderTarget.textures[ 0 ] );
-		const velocityTexture = texture( this._sampleRenderTarget.textures[ 1 ] );
-		const depthTexture = texture( this._sampleRenderTarget.depthTexture );
+		const sampleTexture = this.beautyNode;
+		const depthTexture = this.depthNode;
+		const velocityTexture = this.velocityNode;
 
 		const resolve = Fn( () => {
 
@@ -395,9 +408,9 @@ class TRAAPassNode extends PassNode {
 
 		// materials
 
-		this._resolveMaterial.fragmentNode = resolve();
+		this._resolveMaterial.colorNode = resolve();
 
-		return super.setup( builder );
+		return this._textureNode;
 
 	}
 
@@ -407,14 +420,8 @@ class TRAAPassNode extends PassNode {
 	 */
 	dispose() {
 
-		super.dispose();
-
-		if ( this._sampleRenderTarget !== null ) {
-
-			this._sampleRenderTarget.dispose();
-			this._historyRenderTarget.dispose();
-
-		}
+		this._historyRenderTarget.dispose();
+		this._resolveRenderTarget.dispose();
 
 		this._resolveMaterial.dispose();
 
@@ -422,7 +429,7 @@ class TRAAPassNode extends PassNode {
 
 }
 
-export default TRAAPassNode;
+export default TRAANode;
 
 // These jitter vectors are specified in integers because it is easier.
 // I am assuming a [-8,8) integer grid, but it needs to be mapped onto [-0.5,0.5)
@@ -441,12 +448,14 @@ const _JitterVectors = [
 ];
 
 /**
- * TSL function for creating a TRAA pass node for Temporal Reprojection Anti-Aliasing.
+ * TSL function for creating a TRAA node for Temporal Reprojection Anti-Aliasing.
  *
  * @tsl
  * @function
- * @param {Scene} scene - The scene to render.
- * @param {Camera} camera - The camera to render the scene with.
+ * @param {TextureNode} beautyNode - The texture node that represents the input of the effect.
+ * @param {TextureNode} depthNode - A node that represents the scene's depth.
+ * @param {TextureNode} velocityNode - A node that represents the scene's velocity.
+ * @param {Camera} camera - The camera the scene is rendered with.
  * @returns {TRAAPassNode}
  */
-export const traaPass = ( scene, camera ) => nodeObject( new TRAAPassNode( scene, camera ) );
+export const traa = ( beautyNode, depthNode, velocityNode, camera ) => nodeObject( new TRAANode( convertToTexture( beautyNode ), depthNode, velocityNode, camera ) );

+ 17 - 47
examples/webgpu_postprocessing_ao.html

@@ -28,9 +28,9 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { pass, mrt, output, normalView } from 'three/tsl';
+			import { pass, mrt, output, normalView, velocity } from 'three/tsl';
 			import { ao } from 'three/addons/tsl/display/GTAONode.js';
-			import { denoise } from 'three/addons/tsl/display/DenoiseNode.js';
+			import { traa } from 'three/addons/tsl/display/TRAANode.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
@@ -38,10 +38,11 @@
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+			import Stats from 'three/addons/libs/stats.module.js';
 
-			let camera, scene, renderer, postProcessing, controls;
+			let camera, scene, renderer, postProcessing, controls, stats;
 
-			let aoPass, denoisePass, blendPassAO, blendPassDenoise, scenePassColor;
+			let aoPass, traaPass, blendPassAO, scenePassColor;
 
 			const params = {
 				distanceExponent: 1,
@@ -66,12 +67,15 @@
 
 				scene = new THREE.Scene();
 
-				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer = new THREE.WebGPURenderer();
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setAnimationLoop( animate );
 				document.body.appendChild( renderer.domElement );
 
+				stats = new Stats();
+				document.body.appendChild( stats.domElement );
+
 				await renderer.init();
 
 				const environment = new RoomEnvironment();
@@ -99,12 +103,14 @@
 				const scenePass = pass( scene, camera );
 				scenePass.setMRT( mrt( {
 					output: output,
-					normal: normalView
+					normal: normalView,
+					velocity: velocity
 				} ) );
 
 				scenePassColor = scenePass.getTextureNode( 'output' );
 				const scenePassNormal = scenePass.getTextureNode( 'normal' );
 				const scenePassDepth = scenePass.getTextureNode( 'depth' );
+				const scenePassVelocity = scenePass.getTextureNode( 'velocity' );
 
 				// ao
 
@@ -112,12 +118,11 @@
 				aoPass.resolutionScale = 0.5; // running AO in half resolution is often sufficient
 				blendPassAO = aoPass.getTextureNode().mul( scenePassColor );
 
-				// denoise (optional, use it if you need best quality but is has a noticeable hit on performance)
+				// traa
 
-				denoisePass = denoise( aoPass.getTextureNode(), scenePassDepth, scenePassNormal, camera );
-				blendPassDenoise = denoisePass.mul( scenePassColor );
+				traaPass = traa( blendPassAO, scenePassDepth, scenePassVelocity, camera );
 
-				postProcessing.outputNode = blendPassAO;
+				postProcessing.outputNode = traaPass;
 
 				//
 
@@ -158,38 +163,6 @@
 				gui.add( params, 'radius' ).min( 0.01 ).max( 1 ).onChange( updateParameters );
 				gui.add( params, 'scale' ).min( 0.01 ).max( 2 ).onChange( updateParameters );
 				gui.add( params, 'thickness' ).min( 0.01 ).max( 2 ).onChange( updateParameters );
-				gui.add( params, 'enabled' ).onChange( updatePassChain );
-				const folder = gui.addFolder( 'Denoise settings' );
-				folder.add( params, 'denoiseRadius' ).min( 0.01 ).max( 10 ).name( 'radius' ).onChange( updateParameters );
-				folder.add( params, 'lumaPhi' ).min( 0.01 ).max( 10 ).onChange( updateParameters );
-				folder.add( params, 'depthPhi' ).min( 0.01 ).max( 10 ).onChange( updateParameters );
-				folder.add( params, 'normalPhi' ).min( 0.01 ).max( 10 ).onChange( updateParameters );
-				folder.add( params, 'denoised' ).name( 'enabled' ).onChange( updatePassChain );
-
-			}
-
-			function updatePassChain() {
-
-				if ( params.enabled === true ) {
-
-					if ( params.denoised === true ) {
-
-						postProcessing.outputNode = blendPassDenoise;
-
-					} else {
-
-						postProcessing.outputNode = blendPassAO;
-
-					}
-
-				} else {
-
-					postProcessing.outputNode = scenePassColor;
-
-				}
-
-				postProcessing.needsUpdate = true;
-
 
 			}
 
@@ -201,11 +174,6 @@
 				aoPass.scale.value = params.scale;
 				aoPass.thickness.value = params.thickness;
 
-				denoisePass.radius.value = params.denoiseRadius;
-				denoisePass.lumaPhi.value = params.lumaPhi;
-				denoisePass.depthPhi.value = params.depthPhi;
-				denoisePass.normalPhi.value = params.normalPhi;
-
 			}
 
 			function onWindowResize() {
@@ -224,6 +192,8 @@
 
 				controls.update();
 
+				stats.update();
+
 				postProcessing.render();
 
 			}

+ 10 - 4
examples/webgpu_postprocessing_traa.html

@@ -25,8 +25,8 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { mrt, output, velocity } from 'three/tsl';
-			import { traaPass } from 'three/addons/tsl/display/TRAAPassNode.js';
+			import { mrt, output, pass, velocity } from 'three/tsl';
+			import { traa } from 'three/addons/tsl/display/TRAANode.js';
 
 			import Stats from 'three/addons/libs/stats.module.js';
 
@@ -74,13 +74,19 @@
 				// postprocessing
 
 				postProcessing = new THREE.PostProcessing( renderer );
-				const scenePass = traaPass( scene, camera );
+				const scenePass = pass( scene, camera );
 				scenePass.setMRT( mrt( {
 					output: output,
 					velocity: velocity
 				} ) );
 
-				postProcessing.outputNode = scenePass;
+				const scenePassColor = scenePass.getTextureNode( 'output' );
+				const scenePassDepth = scenePass.getTextureNode( 'depth' );
+				const scenePassVelocity = scenePass.getTextureNode( 'velocity' );
+
+				const traaNode = traa( scenePassColor, scenePassDepth, scenePassVelocity, camera );
+			
+				postProcessing.outputNode = traaNode;
 
 				//
 

+ 9 - 0
src/nodes/utils/RTTNode.js

@@ -41,6 +41,15 @@ class RTTNode extends TextureNode {
 
 		super( renderTarget.texture, uv() );
 
+		/**
+		 * This flag can be used for type testing.
+		 *
+		 * @type {boolean}
+		 * @readonly
+		 * @default true
+		 */
+		this.isRTTNode = true;
+
 		/**
 		 * The node to render a texture with.
 		 *

+ 56 - 2
src/renderers/common/PostProcessing.js

@@ -82,6 +82,15 @@ class PostProcessing {
 		 */
 		this._quadMesh = new QuadMesh( material );
 
+		/**
+		 * The context of the post processing stack.
+		 *
+		 * @private
+		 * @type {?Object}
+		 * @default null
+		 */
+		this._context = null;
+
 	}
 
 	/**
@@ -91,9 +100,11 @@ class PostProcessing {
 	 */
 	render() {
 
+		const renderer = this.renderer;
+
 		this._update();
 
-		const renderer = this.renderer;
+		if ( this._context.onBeforePostProcessing !== null ) this._context.onBeforePostProcessing();
 
 		const toneMapping = renderer.toneMapping;
 		const outputColorSpace = renderer.outputColorSpace;
@@ -115,6 +126,20 @@ class PostProcessing {
 		renderer.toneMapping = toneMapping;
 		renderer.outputColorSpace = outputColorSpace;
 
+		if ( this._context.onAfterPostProcessing !== null ) this._context.onAfterPostProcessing();
+
+	}
+
+	/**
+	 * Returns the current context of the post processing stack.
+	 *
+	 * @readonly
+	 * @type {?Object}
+	 */
+	get context() {
+
+		return this._context;
+
 	}
 
 	/**
@@ -140,7 +165,32 @@ class PostProcessing {
 			const toneMapping = renderer.toneMapping;
 			const outputColorSpace = renderer.outputColorSpace;
 
-			this._quadMesh.material.fragmentNode = this.outputColorTransform === true ? renderOutput( this.outputNode, toneMapping, outputColorSpace ) : this.outputNode.context( { toneMapping, outputColorSpace } );
+			const context = {
+				postProcessing: this,
+				onBeforePostProcessing: null,
+				onAfterPostProcessing: null
+			};
+
+			let outputNode = this.outputNode;
+
+			if ( this.outputColorTransform === true ) {
+
+				outputNode = outputNode.context( context );
+
+				outputNode = renderOutput( outputNode, toneMapping, outputColorSpace );
+
+			} else {
+
+				context.toneMapping = toneMapping;
+				context.outputColorSpace = outputColorSpace;
+
+				outputNode = outputNode.context( context );
+
+			}
+
+			this._context = context;
+
+			this._quadMesh.material.fragmentNode = outputNode;
 			this._quadMesh.material.needsUpdate = true;
 
 			this.needsUpdate = false;
@@ -161,6 +211,8 @@ class PostProcessing {
 
 		this._update();
 
+		if ( this._context.onBeforePostProcessing !== null ) this._context.onBeforePostProcessing();
+
 		const renderer = this.renderer;
 
 		const toneMapping = renderer.toneMapping;
@@ -183,6 +235,8 @@ class PostProcessing {
 		renderer.toneMapping = toneMapping;
 		renderer.outputColorSpace = outputColorSpace;
 
+		if ( this._context.onAfterPostProcessing !== null ) this._context.onAfterPostProcessing();
+
 	}
 
 }

+ 1 - 0
test/e2e/puppeteer.js

@@ -172,6 +172,7 @@ const exceptionList = [
 	'webgpu_textures_2d-array_compressed',
 	'webgpu_rendertarget_2d-array_3d',
 	'webgpu_materials_envmaps_bpcem',
+	'webgpu_postprocessing_ao',
 	'webgpu_postprocessing_sobel',
 	'webgpu_postprocessing_3dlut',
 	'webgpu_postprocessing_fxaa',

粤ICP备19079148号