diff --git a/examples/files.json b/examples/files.json index ebc77bbc271a6f..f0137f4d2b5dfe 100644 --- a/examples/files.json +++ b/examples/files.json @@ -374,6 +374,7 @@ "webgpu_postprocessing_afterimage", "webgpu_postprocessing_anamorphic", "webgpu_postprocessing_ao", + "webgpu_postprocessing_ao_advanced", "webgpu_postprocessing_bloom", "webgpu_postprocessing_bloom_emissive", "webgpu_postprocessing_bloom_selective", diff --git a/examples/screenshots/webgpu_postprocessing_ao_advanced.jpg b/examples/screenshots/webgpu_postprocessing_ao_advanced.jpg new file mode 100644 index 00000000000000..9d5f1e6b9e7686 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_ao_advanced.jpg differ diff --git a/examples/webgpu_postprocessing_ao_advanced.html b/examples/webgpu_postprocessing_ao_advanced.html new file mode 100644 index 00000000000000..975d29b7eda014 --- /dev/null +++ b/examples/webgpu_postprocessing_ao_advanced.html @@ -0,0 +1,279 @@ + + + + three.js webgpu - ambient occlusion advanced (GTAO) + + + + + + + + + + \ No newline at end of file diff --git a/examples/webgpu_postprocessing_standard.html b/examples/webgpu_postprocessing_standard.html new file mode 100644 index 00000000000000..7846d0c7964ff5 --- /dev/null +++ b/examples/webgpu_postprocessing_standard.html @@ -0,0 +1,146 @@ + + + + three.js webgpu - ambient occlusion (GTAO) + + + + + + + + + + \ No newline at end of file diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js index b7439ab67a7bce..297a065c49e6c9 100644 --- a/src/nodes/Nodes.js +++ b/src/nodes/Nodes.js @@ -34,6 +34,8 @@ export { default as UniformNode, uniform } from './core/UniformNode.js'; export { default as VaryingNode, varying } from './core/VaryingNode.js'; export { default as OutputStructNode, outputStruct } from './core/OutputStructNode.js'; export { default as MRTNode, mrt } from './core/MRTNode.js'; +export { default as NodeHandler } from './core/NodeHandler.js'; +export { default as AfterNode, after, before } from './utils/AfterNode.js'; import * as NodeUtils from './core/NodeUtils.js'; export { NodeUtils }; @@ -141,7 +143,6 @@ export { default as BloomNode, bloom } from './display/BloomNode.js'; export { default as TransitionNode, transition } from './display/TransitionNode.js'; export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js'; export { default as PixelationPassNode, pixelationPass } from './display/PixelationPassNode.js'; - export { default as PassNode, pass, passTexture, depthPass } from './display/PassNode.js'; // code diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js index 82e07d4a50779f..79981b186c17a6 100644 --- a/src/nodes/core/NodeBuilder.js +++ b/src/nodes/core/NodeBuilder.js @@ -572,6 +572,14 @@ class NodeBuilder { } + handle( name, node ) { + + const handler = this.renderer.nodeHandler; + + return handler !== null ? handler.handle( name, node, this ) : node; + + } + getPropertyName( node/*, shaderStage*/ ) { return node.name; diff --git a/src/nodes/core/NodeHandler.js b/src/nodes/core/NodeHandler.js new file mode 100644 index 00000000000000..4352fb7939de55 --- /dev/null +++ b/src/nodes/core/NodeHandler.js @@ -0,0 +1,36 @@ +let _id = 0; + +class NodeHandler { + + constructor() { + + this.id = _id ++; + this.handlers = []; + + } + + onHandle( name, callback ) { + + this.handlers[ name ] = callback; + + return this; + + } + + handle( name, node, builder ) { + + const callback = this.handlers[ name ]; + + if ( callback !== undefined ) { + + node = callback( node, builder ); + + } + + return node || null; + + } + +} + +export default NodeHandler; diff --git a/src/nodes/display/AfterImageNode.js b/src/nodes/display/AfterImageNode.js index 0bf75fca829b8c..f5c040780f6f60 100644 --- a/src/nodes/display/AfterImageNode.js +++ b/src/nodes/display/AfterImageNode.js @@ -149,4 +149,3 @@ export const afterImage = ( node, damp ) => nodeObject( new AfterImageNode( node addNodeElement( 'afterImage', afterImage ); export default AfterImageNode; - diff --git a/src/nodes/display/DenoiseNode.js b/src/nodes/display/DenoiseNode.js index 70ef3f6299e75c..bca343f0184ec7 100644 --- a/src/nodes/display/DenoiseNode.js +++ b/src/nodes/display/DenoiseNode.js @@ -191,7 +191,7 @@ function generateDenoiseSamples( numSamples, numRings, radiusExponent ) { } -export const denoise = ( node, depthNode, normalNode, noiseNode, camera ) => nodeObject( new DenoiseNode( nodeObject( node ).toTexture(), nodeObject( depthNode ), nodeObject( normalNode ), nodeObject( noiseNode ), camera ) ); +export const denoise = ( node, depthNode, normalNode, noiseNode, camera ) => nodeObject( new DenoiseNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), nodeObject( noiseNode ).toTexture(), camera ) ); addNodeElement( 'denoise', denoise ); diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js index fad780b36ffe9e..4b65e90ba88e49 100644 --- a/src/nodes/display/PassNode.js +++ b/src/nodes/display/PassNode.js @@ -10,6 +10,7 @@ import { HalfFloatType/*, FloatType*/ } from '../../constants.js'; import { Vector2 } from '../../math/Vector2.js'; import { DepthTexture } from '../../textures/DepthTexture.js'; import { RenderTarget } from '../../core/RenderTarget.js'; +import { viewportTopLeft } from './ViewportNode.js'; const _size = /*@__PURE__*/ new Vector2(); @@ -17,7 +18,7 @@ class PassTextureNode extends TextureNode { constructor( passNode, texture ) { - super( texture ); + super( texture, viewportTopLeft ); this.passNode = passNode; @@ -78,6 +79,11 @@ class PassNode extends TempNode { this.camera = camera; this.options = options; + this.opaque = true; + this.transparent = true; + this.overrideMaterial = null; + this.handler = null; + this._pixelRatio = 1; this._width = 1; this._height = 1; @@ -231,17 +237,32 @@ class PassNode extends TempNode { const currentRenderTarget = renderer.getRenderTarget(); const currentMRT = renderer.getMRT(); + const currentOpaque = renderer.opaque; + const currentTransparent = renderer.transparent; + const currentNodeHandler = renderer.nodeHandler; this._cameraNear.value = camera.near; this._cameraFar.value = camera.far; + const currentOverrideMaterial = scene.overrideMaterial; + scene.overrideMaterial = this.overrideMaterial; + renderer.setRenderTarget( this.renderTarget ); renderer.setMRT( this._mrt ); + renderer.opaque = this.opaque; + renderer.transparent = this.transparent; + renderer.nodeHandler = this.handler; + renderer.render( scene, camera ); renderer.setRenderTarget( currentRenderTarget ); renderer.setMRT( currentMRT ); + renderer.opaque = currentOpaque; + renderer.transparent = currentTransparent; + renderer.nodeHandler = currentNodeHandler; + + scene.overrideMaterial = currentOverrideMaterial; } diff --git a/src/nodes/materials/NodeMaterial.js b/src/nodes/materials/NodeMaterial.js index 66e29f09866166..a88c633da0986b 100644 --- a/src/nodes/materials/NodeMaterial.js +++ b/src/nodes/materials/NodeMaterial.js @@ -415,14 +415,26 @@ class NodeMaterial extends Material { } + // AO + + let aoNode = null; + if ( this.aoNode !== null || builder.material.aoMap ) { - const aoNode = this.aoNode !== null ? this.aoNode : materialAOMap; + aoNode = this.aoNode !== null ? this.aoNode : materialAOMap; + + } + + aoNode = builder.handle( 'ao', aoNode ); + + if ( aoNode !== null ) { materialLightsNode.push( new AONode( aoNode ) ); } + // + let lightsN = this.lightsNode || builder.lightsNode; if ( materialLightsNode.length > 0 ) { diff --git a/src/nodes/utils/AfterNode.js b/src/nodes/utils/AfterNode.js new file mode 100644 index 00000000000000..cfa92478cc609e --- /dev/null +++ b/src/nodes/utils/AfterNode.js @@ -0,0 +1,38 @@ +import Node from '../core/Node.js'; +import { nodeProxy, addNodeElement } from '../shadernode/ShaderNode.js'; + +class AfterNode extends Node { + + constructor( node, afterNode ) { + + super( 'void' ); + + this.node = node; + this.afterNode = afterNode; + + } + + getNodeType( builder ) { + + return this.afterNode.getNodeType( builder ); + + } + + setup( builder ) { + + this.node.build( builder ); + + return this.afterNode; + + } + +} + +export const after = nodeProxy( AfterNode ); +export const before = ( node, afterNode ) => after( afterNode, node ); + +addNodeElement( 'after', after ); +addNodeElement( 'before', before ); + +export default AfterNode; + diff --git a/src/renderers/common/RenderContexts.js b/src/renderers/common/RenderContexts.js index cdbfc8c26d8100..538d66befc16a0 100644 --- a/src/renderers/common/RenderContexts.js +++ b/src/renderers/common/RenderContexts.js @@ -1,6 +1,9 @@ +import NodeHandler from '../../nodes/core/NodeHandler.js'; import ChainMap from './ChainMap.js'; import RenderContext from './RenderContext.js'; +const defaultHander = new NodeHandler(); + class RenderContexts { constructor() { @@ -9,9 +12,9 @@ class RenderContexts { } - get( scene, camera, renderTarget = null ) { + get( scene, camera, handler, renderTarget = null ) { - const chainKey = [ scene, camera ]; + const chainKey = [ scene, camera, handler || defaultHander ]; let attachmentState; diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index 0c1af9a6339715..9e0b344bee9a9c 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -136,6 +136,8 @@ class Renderer { this.transparent = true; this.opaque = true; + this.nodeHandler = null; + this.shadowMap = { enabled: false, type: null @@ -232,7 +234,7 @@ class Renderer { if ( targetScene === null ) targetScene = scene; const renderTarget = this._renderTarget; - const renderContext = this._renderContexts.get( targetScene, camera, renderTarget ); + const renderContext = this._renderContexts.get( targetScene, camera, this.nodeHandler, renderTarget ); const activeMipmapLevel = this._activeMipmapLevel; const compilationPromises = []; @@ -538,7 +540,7 @@ class Renderer { // - const renderContext = this._renderContexts.get( scene, camera, renderTarget ); + const renderContext = this._renderContexts.get( scene, camera, this.nodeHandler, renderTarget ); this._currentRenderContext = renderContext; this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index ecefa2c05e9209..f1bf4591f56353 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -199,17 +199,6 @@ class WebGPUBackend extends Backend { renderTargetData.descriptors = descriptors; - } - - if ( renderTargetData.width !== renderTarget.width || - renderTargetData.height !== renderTarget.height || - renderTargetData.activeMipmapLevel !== renderTarget.activeMipmapLevel || - renderTargetData.samples !== renderTarget.samples || - descriptors.length !== renderTarget.textures.length - ) { - - descriptors.length = 0; - // dispose const onDispose = () => { @@ -224,6 +213,17 @@ class WebGPUBackend extends Backend { } + if ( renderTargetData.width !== renderTarget.width || + renderTargetData.height !== renderTarget.height || + renderTargetData.activeMipmapLevel !== renderTarget.activeMipmapLevel || + renderTargetData.samples !== renderTarget.samples || + descriptors.length !== renderTarget.textures.length + ) { + + descriptors.length = 0; + + } + let descriptor = descriptors[ renderContext.activeCubeFace ]; if ( descriptor === undefined ) {