Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 10 additions & 0 deletions examples/webgpu_postprocessing_ao.html
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@
model.position.set( 0, 1, 0 );
scene.add( model );

//

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

//

model.traverse( ( o ) => {

// Transparent objects (e.g. loaded via GLTFLoader) might have "depthWrite" set to "false".
Expand Down
244 changes: 244 additions & 0 deletions examples/webgpu_postprocessing_ao_prepass.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - ambient occlusion</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="example.css">
</head>
<body>

<div id="info">
<a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>

<div class="title-wrapper">
<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>AO</span>
</div>

<small>
Ambient Occlusion based on GTAO.<br />
<a href="https://skfb.ly/oCnNx" target="_blank" rel="noopener">Minimalistic Modern Bedroom</a> by
<a href="https://sketchfab.com/dylanheyes" target="_blank" rel="noopener">dylanheyes</a> is licensed under <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener">Creative Commons Attribution</a>.
</small>
</div>

<script type="importmap">
{
"imports": {
"three": "../src/three.webgpu.js",
"three/webgpu": "../src/three.webgpu.js",
"three/tsl": "../src/three.tsl.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three/webgpu';
import { sample, pass, mrt, globalContext, 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';

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

import { Inspector } from 'three/addons/inspector/Inspector.js';

let camera, scene, renderer, postProcessing, controls;

let aoPass, traaPass;

const params = {
samples: 16,
distanceExponent: 1,
distanceFallOff: 1,
radius: 0.25,
scale: 1,
thickness: 1,
aoOnly: false
};

init();

async function init() {

camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 50 );
camera.position.set( 1, 1.3, 5 );

scene = new THREE.Scene();

renderer = new THREE.WebGPURenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.inspector = new Inspector();
document.body.appendChild( renderer.domElement );

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 = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 0.5, - 1 );
controls.update();
controls.enablePan = false;
controls.enableDamping = true;
controls.minDistance = 2;
controls.maxDistance = 8;

//

postProcessing = new THREE.PostProcessing( renderer );

//

const prePass = pass( scene, camera ).toInspector( 'Normal', ( inspectNode ) => colorSpaceToWorking( inspectNode, THREE.SRGBColorSpace ) );
prePass.name = 'Pre-Pass';
prePass.setMRT( mrt( {
output: directionToColor( normalView ),
velocity: velocity
} ) );
prePass.transparent = false;

const prePassNormal = sample( ( uv ) => {

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

} );

// bandwidth optimization

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

//

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

const prePassDepth = prePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => {

return prePass.getLinearDepthNode();

} );
const scenePassVelocity = prePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' );

// ao

aoPass = ao( prePassDepth, prePassNormal, camera ).toInspector( 'GTAO', ( inspectNode ) => inspectNode.r );
aoPass.name = 'GTAO Pass';
aoPass.resolutionScale = .5; // running AO in half resolution is often sufficient
aoPass.useTemporalFiltering = true;

// global context

scenePass.globalContext = globalContext( {
ao: aoPass.getTextureNode().sample( screenUV ).r
} );

// traa

traaPass = traa( scenePass, prePassDepth, scenePassVelocity, camera );
postProcessing.outputNode = traaPass;

//

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath( 'jsm/libs/draco/' );
dracoLoader.setDecoderConfig( { type: 'js' } );
const loader = new GLTFLoader();
loader.setDRACOLoader( dracoLoader );
loader.setPath( 'models/gltf/' );

const gltf = await loader.loadAsync( 'minimalistic_modern_bedroom.glb' );

const model = gltf.scene;
model.position.set( 0, 1, 0 );
scene.add( model );

//

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

//

window.addEventListener( 'resize', onWindowResize );

//

const gui = renderer.inspector.createParameters( 'Settings' );
gui.add( params, 'samples', 4, 32, 1 ).onChange( updateParameters );
gui.add( params, 'distanceExponent', 1, 2 ).onChange( updateParameters );
gui.add( params, 'distanceFallOff', 0.01, 1 ).onChange( updateParameters );
gui.add( params, 'radius', 0.1, 1 ).onChange( updateParameters );
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( params, 'aoOnly' ).onChange( ( value ) => {

if ( value === true ) {

postProcessing.outputNode = vec4( vec3( aoPass.r ), 1 );

} else {


postProcessing.outputNode = traaPass;

}

postProcessing.needsUpdate = true;

} );

}

function updateParameters() {

aoPass.samples.value = params.samples;
aoPass.distanceExponent.value = params.distanceExponent;
aoPass.distanceFallOff.value = params.distanceFallOff;
aoPass.radius.value = params.radius;
aoPass.scale.value = params.scale;
aoPass.thickness.value = params.thickness;

}

function onWindowResize() {

const width = window.innerWidth;
const height = window.innerHeight;

camera.aspect = width / height;
camera.updateProjectionMatrix();

renderer.setSize( width, height );

}

function animate() {

controls.update();

postProcessing.render();

}

</script>
</body>
</html>
1 change: 1 addition & 0 deletions src/Three.TSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export const getShadowRenderObjectFunction = TSL.getShadowRenderObjectFunction;
export const getTextureIndex = TSL.getTextureIndex;
export const getViewPosition = TSL.getViewPosition;
export const globalId = TSL.globalId;
export const globalContext = TSL.globalContext;
export const glsl = TSL.glsl;
export const glslFn = TSL.glslFn;
export const grayscale = TSL.grayscale;
Expand Down
48 changes: 46 additions & 2 deletions src/materials/nodes/NodeMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { modelViewMatrix } from '../../nodes/accessors/ModelNode.js';
import { vertexColor } from '../../nodes/accessors/VertexColorNode.js';
import { premultiplyAlpha } from '../../nodes/display/BlendModes.js';
import { subBuild } from '../../nodes/core/SubBuildNode.js';
import { warn } from '../../utils.js';
import { error, warn } from '../../utils.js';

/**
* Base class for all node materials.
Expand Down Expand Up @@ -382,6 +382,14 @@ class NodeMaterial extends Material {
*/
this.vertexNode = null;

/**
* This node can be used as a global context management component for this material.
*
* @type {?GlobalContextNode}
* @default null
*/
this.contextNode = null;

// Deprecated properties

Object.defineProperty( this, 'shadowPositionNode', { // @deprecated, r176
Expand Down Expand Up @@ -489,6 +497,32 @@ class NodeMaterial extends Material {
const renderer = builder.renderer;
const renderTarget = renderer.getRenderTarget();

// < CONTEXT >

if ( renderer.globalContext.isGlobalContextNode === true ) {

builder.context = { ...builder.context, ...renderer.globalContext.value };

} else {

error( 'NodeMaterial: "renderer.globalContext" must be an instance of `globalContext()`.' );

}

if ( this.contextNode !== null ) {

if ( this.contextNode.isGlobalContextNode === true ) {

builder.context = { ...builder.context, ...this.contextNode.value };

} else {

error( 'NodeMaterial: "material.contextNode" must be an instance of `globalContext()`.' );

}

}

// < VERTEX STAGE >

builder.addStack();
Expand Down Expand Up @@ -1018,9 +1052,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 Expand Up @@ -1301,6 +1343,8 @@ class NodeMaterial extends Material {
this.fragmentNode = source.fragmentNode;
this.vertexNode = source.vertexNode;

this.contextNode = source.contextNode;

return super.copy( source );

}
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.renderer.overrideNodes.modelViewMatrix !== null || builder.renderer.overrideNodes.modelNormalViewMatrix !== null )
if ( builder.context.modelViewMatrix || builder.context.modelNormalViewMatrix )
return true;

return false;
Expand Down
1 change: 1 addition & 0 deletions src/nodes/Nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { default as BypassNode } from './core/BypassNode.js';
export { default as IsolateNode } from './core/IsolateNode.js';
export { default as ConstNode } from './core/ConstNode.js';
export { default as ContextNode } from './core/ContextNode.js';
export { default as GlobalContextNode } from './core/GlobalContextNode.js';
export { default as IndexNode } from './core/IndexNode.js';
export { default as LightingModel } from './core/LightingModel.js';
export { default as Node } from './core/Node.js';
Expand Down
1 change: 1 addition & 0 deletions src/nodes/TSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './core/AttributeNode.js';
export * from './core/BypassNode.js';
export * from './core/IsolateNode.js';
export * from './core/ContextNode.js';
export * from './core/GlobalContextNode.js';
export * from './core/IndexNode.js';
export * from './core/ParameterNode.js';
export * from './core/PropertyNode.js';
Expand Down
2 changes: 1 addition & 1 deletion src/nodes/accessors/ModelNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export const modelWorldMatrixInverse = /*@__PURE__*/ uniform( new Matrix4() ).on
*/
export const modelViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => {

return builder.renderer.overrideNodes.modelViewMatrix || mediumpModelViewMatrix;
return builder.context.modelViewMatrix || mediumpModelViewMatrix;

} ).once() )().toVar( 'modelViewMatrix' );

Expand Down
Loading
Loading