diff --git a/examples/files.json b/examples/files.json index 17632b31e6116a..63efedff6746fe 100644 --- a/examples/files.json +++ b/examples/files.json @@ -364,7 +364,8 @@ "webgpu_textures_2d-array", "webgpu_tsl_editor", "webgpu_tsl_transpiler", - "webgpu_video_panorama" + "webgpu_video_panorama", + "webgpu_postprocessing_afterimage" ], "webaudio": [ "webaudio_orientation", diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index 586633301f54af..9e73061151a277 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/examples/jsm/nodes/Nodes.js @@ -111,6 +111,8 @@ export { default as ViewportSharedTextureNode, viewportSharedTexture } from './d export { default as ViewportDepthTextureNode, viewportDepthTexture } from './display/ViewportDepthTextureNode.js'; export { default as ViewportDepthNode, viewZToOrthographicDepth, orthographicDepthToViewZ, viewZToPerspectiveDepth, perspectiveDepthToViewZ, depth, depthTexture, depthPixel } from './display/ViewportDepthNode.js'; export { default as GaussianBlurNode, gaussianBlur } from './display/GaussianBlurNode.js'; +export { default as AfterImageNode, afterImage } from './display/AfterImageNode.js'; + export { default as PassNode, pass, depthPass } from './display/PassNode.js'; // code diff --git a/examples/jsm/nodes/display/AfterImageNode.js b/examples/jsm/nodes/display/AfterImageNode.js new file mode 100644 index 00000000000000..c3c3169e1ccc81 --- /dev/null +++ b/examples/jsm/nodes/display/AfterImageNode.js @@ -0,0 +1,131 @@ +import TempNode from '../core/TempNode.js'; +import { nodeObject, addNodeElement, tslFn, float, vec4 } from '../shadernode/ShaderNode.js'; +import { NodeUpdateType } from '../core/constants.js'; +import { uv } from '../accessors/UVNode.js'; +import { texture } from '../accessors/TextureNode.js'; +import { uniform } from '../core/UniformNode.js'; +import { RenderTarget } from 'three'; +import { sign, max } from '../math/MathNode.js'; +import QuadMesh from '../../objects/QuadMesh.js'; + +const quadMeshComp = new QuadMesh(); + +class AfterImageNode extends TempNode { + + constructor( textureNode, damp = 0.96 ) { + + super( textureNode ); + + this.textureNode = textureNode; + this.textureNodeOld = texture(); + this.damp = uniform( damp ); + + this._compRT = new RenderTarget(); + this._oldRT = new RenderTarget(); + + this.updateBeforeType = NodeUpdateType.RENDER; + + } + + setSize( width, height ) { + + this._compRT.setSize( width, height ); + this._oldRT.setSize( width, height ); + + } + + updateBefore( frame ) { + + const { renderer } = frame; + + const textureNode = this.textureNode; + + const currentRenderTarget = renderer.getRenderTarget(); + const currentTexture = textureNode.value; + + this.textureNodeOld.value = this._oldRT.texture; + + // comp + renderer.setRenderTarget( this._compRT ); + quadMeshComp.render( renderer ); + + // Swap the textures + const temp = this._oldRT; + this._oldRT = this._compRT; + this._compRT = temp; + + // set size before swapping fails + const map = currentTexture; + this.setSize( map.image.width, map.image.height ); + + renderer.setRenderTarget( currentRenderTarget ); + textureNode.value = currentTexture; + + } + + setup( builder ) { + + const textureNode = this.textureNode; + const textureNodeOld = this.textureNodeOld; + + if ( textureNode.isTextureNode !== true ) { + + console.error( 'AfterImageNode requires a TextureNode.' ); + + return vec4(); + + } + + // + + const uvNode = textureNode.uvNode || uv(); + + textureNodeOld.uvNode = uvNode; + + const sampleTexture = ( uv ) => textureNode.cache().context( { getUV: () => uv, forceUVContext: true } ); + + const when_gt = tslFn( ( [ x_immutable, y_immutable ] ) => { + + const y = float( y_immutable ).toVar(); + const x = vec4( x_immutable ).toVar(); + + return max( sign( x.sub( y ) ), 0.0 ); + + } ); + + const afterImg = tslFn( () => { + + const texelOld = vec4( textureNodeOld ); + const texelNew = vec4( sampleTexture( uvNode ) ); + + texelOld.mulAssign( this.damp.mul( when_gt( texelOld, 0.1 ) ) ); + return max( texelNew, texelOld ); + + } ); + + // + + const materialComposed = this._materialComposed || ( this._materialComposed = builder.createNodeMaterial( 'MeshBasicNodeMaterial' ) ); + materialComposed.fragmentNode = afterImg(); + + quadMeshComp.material = materialComposed; + + // + + const properties = builder.getNodeProperties( this ); + properties.textureNode = textureNode; + + // + + return texture( this._compRT.texture ); + + } + +} + +export const afterImage = ( node, damp ) => nodeObject( new AfterImageNode( nodeObject( node ), damp ) ); + +addNodeElement( 'afterImage', afterImage ); + +export default AfterImageNode; + diff --git a/examples/screenshots/webgpu_postprocessing_afterimage.jpg b/examples/screenshots/webgpu_postprocessing_afterimage.jpg new file mode 100644 index 00000000000000..bcdcc08185bfb0 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_afterimage.jpg differ diff --git a/examples/webgpu_postprocessing_afterimage.html b/examples/webgpu_postprocessing_afterimage.html new file mode 100644 index 00000000000000..b9ec35c76981da --- /dev/null +++ b/examples/webgpu_postprocessing_afterimage.html @@ -0,0 +1,128 @@ + + +
+