diff --git a/examples/jsm/tsl/display/TRAAPassNode.js b/examples/jsm/tsl/display/TRAANode.js similarity index 53% rename from examples/jsm/tsl/display/TRAAPassNode.js rename to examples/jsm/tsl/display/TRAANode.js index 07c3ad9f44e788..e67a1207382883 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. @@ -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 ) ); diff --git a/examples/webgpu_postprocessing_ao.html b/examples/webgpu_postprocessing_ao.html index 5f3c835b3eadd6..12cb538499b1d0 100644 --- a/examples/webgpu_postprocessing_ao.html +++ b/examples/webgpu_postprocessing_ao.html @@ -28,9 +28,9 @@