|
|
@@ -7,6 +7,13 @@
|
|
|
<link type="text/css" rel="stylesheet" href="main.css">
|
|
|
</head>
|
|
|
<body>
|
|
|
+
|
|
|
+ <div id="info">
|
|
|
+ <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgpu - postprocessing - ambient occlusion<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>.<br />
|
|
|
+ </div>
|
|
|
+
|
|
|
<script type="importmap">
|
|
|
{
|
|
|
"imports": {
|
|
|
@@ -20,20 +27,19 @@
|
|
|
<script type="module">
|
|
|
|
|
|
import * as THREE from 'three';
|
|
|
- import { pass, mrt, output, transformedNormalView, texture } from 'three/tsl';
|
|
|
+ import { pass, mrt, output, normalView } from 'three/tsl';
|
|
|
import { ao } from 'three/addons/tsl/display/GTAONode.js';
|
|
|
import { denoise } from 'three/addons/tsl/display/DenoiseNode.js';
|
|
|
|
|
|
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 { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.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 camera, scene, renderer, postProcessing, controls, stats;
|
|
|
|
|
|
let aoPass, denoisePass, blendPassAO, blendPassDenoise, scenePassColor;
|
|
|
|
|
|
@@ -43,7 +49,7 @@
|
|
|
radius: 0.25,
|
|
|
scale: 1,
|
|
|
thickness: 1,
|
|
|
- denoised: true,
|
|
|
+ denoised: false,
|
|
|
enabled: true,
|
|
|
denoiseRadius: 5,
|
|
|
lumaPhi: 5,
|
|
|
@@ -55,30 +61,36 @@
|
|
|
|
|
|
async function init() {
|
|
|
|
|
|
- camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 100 );
|
|
|
- camera.position.set( 5, 2, 8 );
|
|
|
+ camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 50 );
|
|
|
+ camera.position.set( 1, 1.3, 5 );
|
|
|
|
|
|
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.setPixelRatio( window.devicePixelRatio );
|
|
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
renderer.setAnimationLoop( animate );
|
|
|
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, 0 );
|
|
|
+ controls.target.set( 0, 0.5, - 1 );
|
|
|
controls.update();
|
|
|
controls.enablePan = false;
|
|
|
controls.enableDamping = true;
|
|
|
+ controls.minDistance = 2;
|
|
|
+ controls.maxDistance = 8;
|
|
|
|
|
|
stats = new Stats();
|
|
|
document.body.appendChild( stats.dom );
|
|
|
@@ -90,7 +102,7 @@
|
|
|
const scenePass = pass( scene, camera );
|
|
|
scenePass.setMRT( mrt( {
|
|
|
output: output,
|
|
|
- normal: transformedNormalView
|
|
|
+ normal: normalView
|
|
|
} ) );
|
|
|
|
|
|
scenePassColor = scenePass.getTextureNode( 'output' );
|
|
|
@@ -100,15 +112,15 @@
|
|
|
// ao
|
|
|
|
|
|
aoPass = ao( scenePassDepth, scenePassNormal, camera );
|
|
|
+ aoPass.resolutionScale = 0.5;
|
|
|
blendPassAO = aoPass.getTextureNode().mul( scenePassColor );
|
|
|
|
|
|
// denoise (optional)
|
|
|
|
|
|
- const noiseTexture = texture( generateNoise() );
|
|
|
- denoisePass = denoise( aoPass.getTextureNode(), scenePassDepth, scenePassNormal, noiseTexture, camera );
|
|
|
+ denoisePass = denoise( aoPass.getTextureNode(), scenePassDepth, scenePassNormal, camera );
|
|
|
blendPassDenoise = denoisePass.mul( scenePassColor );
|
|
|
|
|
|
- postProcessing.outputNode = blendPassDenoise;
|
|
|
+ postProcessing.outputNode = blendPassAO;
|
|
|
|
|
|
//
|
|
|
|
|
|
@@ -119,15 +131,24 @@
|
|
|
loader.setDRACOLoader( dracoLoader );
|
|
|
loader.setPath( 'models/gltf/' );
|
|
|
|
|
|
- const gltf = await loader.loadAsync( 'LittlestTokyo.glb' );
|
|
|
+ const gltf = await loader.loadAsync( 'minimalistic_modern_bedroom.glb' );
|
|
|
|
|
|
const model = gltf.scene;
|
|
|
- model.position.set( 1, 1, 0 );
|
|
|
- model.scale.set( 0.01, 0.01, 0.01 );
|
|
|
+ model.position.set( 0, 1, 0 );
|
|
|
scene.add( model );
|
|
|
|
|
|
- mixer = new THREE.AnimationMixer( model );
|
|
|
- mixer.clipAction( gltf.animations[ 0 ] ).play();
|
|
|
+ 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;
|
|
|
+
|
|
|
+ } );
|
|
|
|
|
|
window.addEventListener( 'resize', onWindowResize );
|
|
|
|
|
|
@@ -140,13 +161,13 @@
|
|
|
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 );
|
|
|
- gui.add( params, 'denoised' ).onChange( updatePassChain );
|
|
|
gui.add( params, 'enabled' ).onChange( updatePassChain );
|
|
|
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 );
|
|
|
+ folder.add( params, 'denoised' ).name( 'enabled' ).onChange( updatePassChain );
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -190,38 +211,6 @@
|
|
|
|
|
|
}
|
|
|
|
|
|
- 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;
|
|
|
@@ -236,14 +225,6 @@
|
|
|
|
|
|
function animate() {
|
|
|
|
|
|
- const delta = clock.getDelta();
|
|
|
-
|
|
|
- if ( mixer ) {
|
|
|
-
|
|
|
- mixer.update( delta );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
controls.update();
|
|
|
|
|
|
postProcessing.render();
|