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 ) {