Skip to content
Draft
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
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
279 changes: 279 additions & 0 deletions examples/webgpu_postprocessing_ao_advanced.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - ambient occlusion advanced (GTAO)</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="main.css">
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "../build/three.webgpu.js",
"three/tsl": "../build/three.webgpu.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three';
import { pass, uniform, transformedNormalView, texture, ao, denoise, viewportSharedTexture, viewportTopLeft } from 'three/tsl';

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';

import Stats from 'three/addons/libs/stats.module.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

let camera, scene, renderer, postProcessing, controls, clock, stats, mixer;

let aoPass, denoisePass, aoIntensity;
let handler;

const params = {
intensity: 1,
distanceExponent: 1,
distanceFallOff: 1,
radius: 0.25,
scale: 1,
thickness: 1,
denoised: true,
enabled: true,
denoiseRadius: 5,
lumaPhi: 5,
depthPhi: 5,
normalPhi: 5
};

init();

async function init() {

camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 100 );
camera.position.set( 5, 2, 8 );

scene = new THREE.Scene();
scene.background = new THREE.Color( 0xbfe3dd );

clock = new THREE.Clock();

const hdrloader = new RGBELoader();
const envMap = await hdrloader.loadAsync( 'textures/equirectangular/quarry_01_1k.hdr' );
envMap.mapping = THREE.EquirectangularReflectionMapping;

scene.environment = envMap;

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

controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 0.5, 0 );
controls.update();
controls.enablePan = false;
controls.enableDamping = true;

stats = new Stats();
document.body.appendChild( stats.dom );

//

const verticalRefractor = viewportSharedTexture();

const planeRefractor = new THREE.Mesh( new THREE.PlaneGeometry( 3, 3 ), new THREE.MeshStandardNodeMaterial( {
backdropNode: verticalRefractor.saturation( 0 )
} ) );
planeRefractor.material.transparent = true;
planeRefractor.position.z = 3;
planeRefractor.position.y = 1;
scene.add( planeRefractor );

//

const emissiveObject = new THREE.Mesh( new THREE.SphereGeometry( .5, 32, 32 ), new THREE.MeshStandardNodeMaterial( {
emissive: new THREE.Color( 0x00ff00 )
} ) );
emissiveObject.position.z = 1;
emissiveObject.position.y = 2;
emissiveObject.position.x = .5;
scene.add( emissiveObject );

// Post Processing

postProcessing = new THREE.PostProcessing( renderer );

// Opaque Pass

const normalPassMaterial = new THREE.NodeMaterial();
normalPassMaterial.lights = false;
normalPassMaterial.fog = false;
normalPassMaterial.colorNode = transformedNormalView;

const opaquePass = pass( scene, camera );

opaquePass.transparent = false;
opaquePass.overrideMaterial = normalPassMaterial;

// Textures

const normalView = opaquePass.getTextureNode();
const depth = opaquePass.getTextureNode( 'depth' );

// AO

aoPass = ao( depth, normalView, camera );

// denoise (optional)

const noiseTexture = texture( generateNoise() );
denoisePass = denoise( aoPass.getTextureNode(), depth, normalView, noiseTexture, camera );

const denoiseTexture = denoisePass.toTexture();
denoiseTexture.uvNode = viewportTopLeft;

// handler can manipulate materials at build time.

aoIntensity = uniform( 1 );

handler = new THREE.NodeHandler();
handler.onHandle( 'ao', ( node ) => {

const sceneAO = aoIntensity !== null ? aoIntensity.mix( 1, denoiseTexture ) : denoiseTexture;

return node !== null ? sceneAO.mul( node ) : sceneAO;

} );

// Final

const scenePass = pass( scene, camera );
scenePass.handler = handler;

postProcessing.outputNode = scenePass;

//

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( 'LittlestTokyo.glb' );

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

mixer = new THREE.AnimationMixer( model );
mixer.clipAction( gltf.animations[ 0 ] ).play();

window.addEventListener( 'resize', onWindowResize );

//

const gui = new GUI();
gui.title( 'AO settings' );
gui.add( params, 'intensity' ).min( 0 ).max( 1 ).onChange( updateParameters );
gui.add( params, 'distanceExponent' ).min( 1 ).max( 4 ).onChange( updateParameters );
gui.add( params, 'distanceFallOff' ).min( 0.01 ).max( 1 ).onChange( updateParameters );
gui.add( params, 'radius' ).min( 0.01 ).max( 1 ).onChange( updateParameters );
gui.add( params, 'scale' ).min( 0.01 ).max( 2 ).onChange( updateParameters );
gui.add( params, 'thickness' ).min( 0.01 ).max( 2 ).onChange( updateParameters );
const folder = gui.addFolder( 'Denoise settings' );
folder.add( params, 'denoiseRadius' ).min( 0.01 ).max( 10 ).name( 'radius' ).onChange( updateParameters );
folder.add( params, 'lumaPhi' ).min( 0.01 ).max( 10 ).onChange( updateParameters );
folder.add( params, 'depthPhi' ).min( 0.01 ).max( 10 ).onChange( updateParameters );
folder.add( params, 'normalPhi' ).min( 0.01 ).max( 10 ).onChange( updateParameters );

}

function updateParameters() {

aoIntensity.value = params.intensity;
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;

denoisePass.radius.value = params.denoiseRadius;
denoisePass.lumaPhi.value = params.lumaPhi;
denoisePass.depthPhi.value = params.depthPhi;
denoisePass.normalPhi.value = params.normalPhi;

}

function generateNoise( size = 64 ) {

const simplex = new SimplexNoise();

const arraySize = size * size * 4;
const data = new Uint8Array( arraySize );

for ( let i = 0; i < size; i ++ ) {

for ( let j = 0; j < size; j ++ ) {

const x = i;
const y = j;

data[ ( i * size + j ) * 4 ] = ( simplex.noise( x, y ) * 0.5 + 0.5 ) * 255;
data[ ( i * size + j ) * 4 + 1 ] = ( simplex.noise( x + size, y ) * 0.5 + 0.5 ) * 255;
data[ ( i * size + j ) * 4 + 2 ] = ( simplex.noise( x, y + size ) * 0.5 + 0.5 ) * 255;
data[ ( i * size + j ) * 4 + 3 ] = ( simplex.noise( x + size, y + size ) * 0.5 + 0.5 ) * 255;

}

}

const noiseTexture = new THREE.DataTexture( data, size, size );
noiseTexture.wrapS = THREE.RepeatWrapping;
noiseTexture.wrapT = THREE.RepeatWrapping;
noiseTexture.needsUpdate = true;

return noiseTexture;

}

function onWindowResize() {

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

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

renderer.setSize( width, height );

}

function animate() {

const delta = clock.getDelta();

if ( mixer ) {

mixer.update( delta );

}

controls.update();

postProcessing.render();
stats.update();

}

</script>
</body>
</html>
Loading