From f384a8f28a0c09ff9b9f1dc9eb72a65ecb8674a5 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Wed, 16 Jul 2025 00:10:39 +0200 Subject: [PATCH 1/6] TRAANode: New TRAA implementation. --- .../display/{TRAAPassNode.js => TRAANode.js} | 322 ++++++++---------- examples/webgpu_postprocessing_ao.html | 65 +--- examples/webgpu_postprocessing_traa.html | 15 +- src/renderers/common/PostProcessing.js | 25 ++ test/e2e/puppeteer.js | 1 + 5 files changed, 195 insertions(+), 233 deletions(-) rename examples/jsm/tsl/display/{TRAAPassNode.js => TRAANode.js} (54%) diff --git a/examples/jsm/tsl/display/TRAAPassNode.js b/examples/jsm/tsl/display/TRAANode.js similarity index 54% rename from examples/jsm/tsl/display/TRAAPassNode.js rename to examples/jsm/tsl/display/TRAANode.js index 07c3ad9f44e788..6164d65798e101 100644 --- a/examples/jsm/tsl/display/TRAAPassNode.js +++ b/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. @@ -114,85 +127,79 @@ class TRAAPassNode extends PassNode { this._resolveMaterial = new NodeMaterial(); 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(); + } /** - * Sets the size of the effect. + * Returns the result of the effect as a texture node. * - * @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. + * @return {PassTextureNode} A texture node that represents the result of the effect. */ - 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 ); + getTextureNode() { - this._invSize.value.set( 1 / this.renderTarget.width, 1 / this.renderTarget.height ); - - needsRestart = true; - - } - - return needsRestart; + return this._textureNode; } /** - * This method is used to render the effect once per frame. + * Sets the size of the effect. * - * @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; + setSize( width, height ) { - _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + this._historyRenderTarget.setSize( width, height ); + this._resolveRenderTarget.setSize( width, height ); - // + this._invSize.value.set( 1 / width, 1 / height ); - this._pixelRatio = renderer.getPixelRatio(); - const size = renderer.getSize( _size ); + } - const needsRestart = this.setSize( size.width, size.height ); + /** + * Defines the TRAA's current jitter as a view offset + * to the scene's camera. + */ + setViewOffset() { // save original/unjittered projection matrix for velocity pass - camera.updateProjectionMatrix(); - this._originalProjectionMatrix.copy( camera.projectionMatrix ); - - // camera configuration + this.camera.updateProjectionMatrix(); + this._originalProjectionMatrix.copy( this.camera.projectionMatrix ); - this._cameraNear.value = camera.near; - this._cameraFar.value = camera.far; + velocity.setProjectionMatrix( this._originalProjectionMatrix ); - // configure jitter as view offset + // const viewOffset = { - fullWidth: this.renderTarget.width, - fullHeight: this.renderTarget.height, + fullWidth: this._resolveRenderTarget.width, + fullHeight: this._resolveRenderTarget.height, offsetX: 0, offsetY: 0, - width: this.renderTarget.width, - height: this.renderTarget.height + width: this._resolveRenderTarget.width, + height: this._resolveRenderTarget.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,34 +209,43 @@ class TRAAPassNode extends PassNode { ); - // configure velocity + } - const mrt = this.getMRT(); - const velocityOutput = mrt.get( 'velocity' ); + /** + * Clears the view offset from the scene's camera. + */ + clearViewOffset() { - if ( velocityOutput !== undefined ) { + this.camera.clearViewOffset(); - velocityOutput.setProjectionMatrix( this._originalProjectionMatrix ); + velocity.setProjectionMatrix( null ); - } else { + // update jitter index - throw new Error( 'THREE:TRAAPassNode: Missing velocity output in MRT configuration.' ); + this._jitterIndex ++; + this._jitterIndex = this._jitterIndex % ( _JitterVectors.length - 1 ); - } + } - // render sample + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { - renderer.setMRT( mrt ); + const { renderer } = frame; - renderer.setClearColor( this.clearColor, this.clearAlpha ); - renderer.setRenderTarget( this._sampleRenderTarget ); - renderer.render( scene, camera ); + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); - renderer.setRenderTarget( null ); - renderer.setMRT( null ); + // + + const size = renderer.getDrawingBufferSize( _size ); - // 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). + const needsRestart = this._historyRenderTarget.width !== size.width || this._historyRenderTarget.height !== size.height; + this.setSize( size.width, size.height ); + + // every time when the dimensions change we need fresh history data if ( needsRestart === true ) { @@ -238,60 +254,24 @@ class TRAAPassNode extends PassNode { renderer.setRenderTarget( this._historyRenderTarget ); renderer.clear(); - renderer.setRenderTarget( this.renderTarget ); + renderer.setRenderTarget( this._resolveRenderTarget ); renderer.clear(); - renderer.setRenderTarget( null ); - - renderer.copyTextureToTexture( this._sampleRenderTarget.texture, this._historyRenderTarget.texture ); - renderer.copyTextureToTexture( this._sampleRenderTarget.texture, this.renderTarget.texture ); - - } else { - - // resolve - - renderer.setRenderTarget( this.renderTarget ); - _quadMesh.material = this._resolveMaterial; - _quadMesh.render( renderer ); - renderer.setRenderTarget( null ); - - // update history - - renderer.copyTextureToTexture( this.renderTarget.texture, this._historyRenderTarget.texture ); - } - // copy depth + // resolve - renderer.copyTextureToTexture( this._sampleRenderTarget.depthTexture, this.renderTarget.depthTexture ); + renderer.setRenderTarget( this._resolveRenderTarget ); + _quadMesh.material = this._resolveMaterial; + _quadMesh.render( renderer ); + renderer.setRenderTarget( null ); - // update jitter index + // update history - this._jitterIndex ++; - this._jitterIndex = this._jitterIndex % ( _JitterVectors.length - 1 ); + renderer.copyTextureToTexture( this._resolveRenderTarget.texture, this._historyRenderTarget.texture ); // restore - if ( originalViewOffset.enabled ) { - - camera.setViewOffset( - - originalViewOffset.fullWidth, originalViewOffset.fullHeight, - - originalViewOffset.offsetX, originalViewOffset.offsetY, - - originalViewOffset.width, originalViewOffset.height - - ); - - } else { - - camera.clearViewOffset(); - - } - - velocityOutput.setProjectionMatrix( null ); - RendererUtils.restoreRendererState( renderer, _rendererState ); } @@ -302,30 +282,12 @@ class TRAAPassNode extends PassNode { * @param {NodeBuilder} builder - The current node builder. * @return {PassTextureNode} */ - setup( builder ) { - - if ( this._sampleRenderTarget === null ) { - - this._sampleRenderTarget = this.renderTarget.clone(); - this._historyRenderTarget = this.renderTarget.clone(); - - this._sampleRenderTarget.texture.minFiler = NearestFilter; - this._sampleRenderTarget.texture.magFilter = NearestFilter; - - const velocityTarget = this._sampleRenderTarget.texture.clone(); - velocityTarget.isRenderTargetTexture = true; - velocityTarget.name = 'velocity'; - - this._sampleRenderTarget.textures.push( velocityTarget ); // for MRT - - } - - // textures + setup( /*builder*/ ) { 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 +357,9 @@ class TRAAPassNode extends PassNode { // materials - this._resolveMaterial.fragmentNode = resolve(); + this._resolveMaterial.colorNode = resolve(); - return super.setup( builder ); + return this._textureNode; } @@ -407,14 +369,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 +378,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 +397,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 ) ); diff --git a/examples/webgpu_postprocessing_ao.html b/examples/webgpu_postprocessing_ao.html index 5f3c835b3eadd6..b3599f46b0c19f 100644 --- a/examples/webgpu_postprocessing_ao.html +++ b/examples/webgpu_postprocessing_ao.html @@ -28,9 +28,9 @@