Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/jsm/tsl/display/GTAONode.js
Original file line number Diff line number Diff line change
Expand Up @@ -568,4 +568,4 @@ function generateMagicSquare( size ) {
* @param {Camera} camera - The camera the scene is rendered with.
* @returns {GTAONode}
*/
export const ao = ( depthNode, normalNode, camera ) => nodeObject( new GTAONode( nodeObject( depthNode ), nodeObject( normalNode ), camera ) );
export const ao = ( depthNode, normalNode, camera ) => new GTAONode( nodeObject( depthNode ), nodeObject( normalNode ), camera );
94 changes: 56 additions & 38 deletions examples/webgpu_postprocessing_ao.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<script type="module">

import * as THREE from 'three/webgpu';
import { pass, mrt, output, normalView, velocity, vec3, vec4, directionToColor, colorSpaceToWorking } from 'three/tsl';
import { sample, pass, mrt, context, screenUV, normalView, velocity, vec3, vec4, directionToColor, colorToDirection, colorSpaceToWorking } from 'three/tsl';
import { ao } from 'three/addons/tsl/display/GTAONode.js';
import { traa } from 'three/addons/tsl/display/TRAANode.js';

Expand All @@ -49,7 +49,7 @@

let camera, scene, renderer, postProcessing, controls;

let aoPass, traaPass, blendPassAO, scenePassColor;
let aoPass, traaPass;

const params = {
samples: 16,
Expand Down Expand Up @@ -79,15 +79,7 @@

await renderer.init();

const environment = new RoomEnvironment();
const pmremGenerator = new THREE.PMREMGenerator( renderer );

scene.background = new THREE.Color( 0x666666 );
scene.environment = pmremGenerator.fromScene( environment ).texture;
environment.dispose();
pmremGenerator.dispose();

//
// controls

controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 0.5, - 1 );
Expand All @@ -97,43 +89,70 @@
controls.minDistance = 2;
controls.maxDistance = 8;

//
// environment

const environment = new RoomEnvironment();
const pmremGenerator = new THREE.PMREMGenerator( renderer );

scene.background = new THREE.Color( 0x666666 );
scene.environment = pmremGenerator.fromScene( environment ).texture;
environment.dispose();
pmremGenerator.dispose();

// post-processing

postProcessing = new THREE.PostProcessing( renderer );

const scenePass = pass( scene, camera );
scenePass.setMRT( mrt( {
output: output,
normal: normalView,
// pre-pass

const prePass = pass( scene, camera ).toInspector( 'Normal', ( inspectNode ) => colorSpaceToWorking( inspectNode, THREE.SRGBColorSpace ) );
prePass.name = 'Pre-Pass';
prePass.transparent = false;

prePass.setMRT( mrt( {
output: directionToColor( normalView ),
velocity: velocity
} ) );

scenePassColor = scenePass.getTextureNode( 'output' ).toInspector( 'Color' );
const scenePassDepth = scenePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => {
const prePassNormal = sample( ( uv ) => {

return scenePass.getLinearDepthNode();
return colorToDirection( prePass.getTextureNode().sample( uv ) );

} );
const scenePassNormal = scenePass.getTextureNode( 'normal' ).toInspector( 'Normal', () => {

return colorSpaceToWorking( directionToColor( scenePassNormal ), THREE.SRGBColorSpace );
const prePassDepth = prePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => prePass.getLinearDepthNode() );
const prePassVelocity = prePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' );

} );
const scenePassVelocity = scenePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' );
// pre-pass - bandwidth optimization

const normalTexture = prePass.getTexture( 'output' );
normalTexture.type = THREE.UnsignedByteType;

// scene pass

const scenePass = pass( scene, camera ).toInspector( 'Color' );

// ao

aoPass = ao( scenePassDepth, scenePassNormal, camera );
aoPass = ao( prePassDepth, prePassNormal, camera ).toInspector( 'GTAO', ( inspectNode ) => inspectNode.r );
aoPass.resolutionScale = 0.5; // running AO in half resolution is often sufficient
aoPass.useTemporalFiltering = true;
blendPassAO = vec4( scenePassColor.rgb.mul( aoPass.r.toInspector( 'AO' ) ), scenePassColor.a ); // the AO is stored only in the red channel

// traa
const aoPassOutput = aoPass.getTextureNode( 'output' );

// scene context

scenePass.contextNode = context( {
ao: aoPassOutput.sample( screenUV ).r
} );

// final output + traa

traaPass = traa( scenePass, prePassDepth, prePassVelocity, camera );

traaPass = traa( blendPassAO, scenePassDepth, scenePassVelocity, camera );
postProcessing.outputNode = traaPass;

//
// models

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath( 'jsm/libs/draco/' );
Expand All @@ -148,18 +167,16 @@
model.position.set( 0, 1, 0 );
scene.add( model );

model.traverse( ( o ) => {

// Transparent objects (e.g. loaded via GLTFLoader) might have "depthWrite" set to "false".
// This is wanted when rendering the beauty pass however it produces wrong results when computing
// AO since depth and normal data are out of sync. Computing normals from depth by not using MRT
// can mitigate the issue although the depth information (and thus the normals) are not correct in
// first place. Besides, normal estimation is computationally more expensive than just sampling a
// normal texture. So depending on your scene, consider to enable "depthWrite" for all transparent objects.
//

if ( o.material ) o.material.depthWrite = true;
const transparentMesh = new THREE.Mesh( new THREE.PlaneGeometry( 1.8, 2 ), new THREE.MeshStandardNodeMaterial( { transparent: true, opacity: .1 } ) );
transparentMesh.material.transparent = true;
transparentMesh.position.z = 0;
transparentMesh.position.y = 0.5;
transparentMesh.visible = false;
scene.add( transparentMesh );

} );
// events

window.addEventListener( 'resize', onWindowResize );

Expand All @@ -173,6 +190,7 @@
gui.add( params, 'scale', 0.01, 2 ).onChange( updateParameters );
gui.add( params, 'thickness', 0.01, 2 ).onChange( updateParameters );
gui.add( aoPass, 'useTemporalFiltering' ).name( 'temporal filtering' );
gui.add( transparentMesh, 'visible' ).name( 'show transparent mesh' );
gui.add( params, 'aoOnly' ).onChange( ( value ) => {

if ( value === true ) {
Expand Down
10 changes: 9 additions & 1 deletion src/materials/nodes/NodeMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -1060,9 +1060,17 @@ class NodeMaterial extends Material {

}

let aoNode = builder.context.ao || null;

if ( this.aoNode !== null || builder.material.aoMap ) {

const aoNode = this.aoNode !== null ? this.aoNode : materialAO;
const mtlAO = this.aoNode !== null ? this.aoNode : materialAO;

aoNode = aoNode !== null ? aoNode.mul( mtlAO ) : mtlAO;

}

if ( aoNode !== null ) {

materialLightsNode.push( new AONode( aoNode ) );

Expand Down
2 changes: 1 addition & 1 deletion src/materials/nodes/manager/NodeMaterialObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ class NodeMaterialObserver {

}

if ( builder.context.modelViewMatrix || builder.context.modelNormalViewMatrix )
if ( builder.context.modelViewMatrix || builder.context.modelNormalViewMatrix || builder.context.ao )
return true;

return false;
Expand Down
16 changes: 12 additions & 4 deletions src/nodes/core/NodeFrame.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,13 @@ class NodeFrame {

if ( nodeUpdateBeforeMap.frameId !== this.frameId ) {

if ( node.updateBefore( this ) !== false ) {
const previousFrameId = nodeUpdateBeforeMap.frameId;

nodeUpdateBeforeMap.frameId = this.frameId;
nodeUpdateBeforeMap.frameId = this.frameId;

if ( node.updateBefore( this ) === false ) {

nodeUpdateBeforeMap.frameId = previousFrameId;

}

Expand All @@ -169,9 +173,13 @@ class NodeFrame {

if ( nodeUpdateBeforeMap.renderId !== this.renderId ) {

if ( node.updateBefore( this ) !== false ) {
const previousRenderId = nodeUpdateBeforeMap.renderId;

nodeUpdateBeforeMap.renderId = this.renderId;

if ( node.updateBefore( this ) === false ) {

nodeUpdateBeforeMap.renderId = this.renderId;
nodeUpdateBeforeMap.renderId = previousRenderId;

}

Expand Down
39 changes: 38 additions & 1 deletion src/nodes/display/PassNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,29 @@ class PassNode extends TempNode {
*/
this.renderTarget = renderTarget;

/**
* An optional override material for the pass.
*
* @type {Material|null}
*/
this.overrideMaterial = null;

/**
* Whether the pass is transparent.
*
* @type {boolean}
* @default false
*/
this.transparent = true;

/**
* Whether the pass is opaque.
*
* @type {boolean}
* @default true
*/
this.opaque = true;

/**
* An optional global context for the pass.
*
Expand Down Expand Up @@ -754,8 +777,11 @@ class PassNode extends TempNode {
const currentRenderTarget = renderer.getRenderTarget();
const currentMRT = renderer.getMRT();
const currentAutoClear = renderer.autoClear;
const currentTransparent = renderer.transparent;
const currentOpaque = renderer.opaque;
const currentMask = camera.layers.mask;
const currentContextNode = renderer.contextNode;
const currentOverrideMaterial = scene.overrideMaterial;

this._cameraNear.value = camera.near;
this._cameraFar.value = camera.far;
Expand All @@ -772,17 +798,25 @@ class PassNode extends TempNode {

}

if ( this.overrideMaterial !== null ) {

scene.overrideMaterial = this.overrideMaterial;

}

renderer.setRenderTarget( this.renderTarget );
renderer.setMRT( this._mrt );
renderer.autoClear = true;
renderer.transparent = this.transparent;
renderer.opaque = this.opaque;

if ( this.contextNode !== null ) {

if ( this._contextNodeCache === null || this._contextNodeCache.version !== this.version ) {

this._contextNodeCache = {
version: this.version,
context: context( { ...renderer.contextNode.value, ...this.contextNode.getFlowContextData() } )
context: context( { ...renderer.contextNode.getFlowContextData(), ...this.contextNode.getFlowContextData() } )
};

}
Expand All @@ -798,10 +832,13 @@ class PassNode extends TempNode {
renderer.render( scene, camera );

scene.name = currentSceneName;
scene.overrideMaterial = currentOverrideMaterial;

renderer.setRenderTarget( currentRenderTarget );
renderer.setMRT( currentMRT );
renderer.autoClear = currentAutoClear;
renderer.transparent = currentTransparent;
renderer.opaque = currentOpaque;
renderer.contextNode = currentContextNode;

camera.layers.mask = currentMask;
Expand Down