|
| 1 | +import * as THREE from 'three'; |
| 2 | +import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js'; |
| 3 | +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; |
| 4 | +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; |
| 5 | +import { PathTracingRenderer, PhysicalPathTracingMaterial, PhysicalCamera } from '../src/index.js'; |
| 6 | +import { PathTracingSceneWorker } from '../src/workers/PathTracingSceneWorker.js'; |
| 7 | +import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'; |
| 8 | + |
| 9 | +let renderer, controls, pathTracer, blitQuad, camera, scene, samplesEl; |
| 10 | + |
| 11 | +let tiles = 1; |
| 12 | +let resolutionScale = 1; |
| 13 | + |
| 14 | +// adjust performance parameters for mobile |
| 15 | +const aspectRatio = window.innerWidth / window.innerHeight; |
| 16 | +if ( aspectRatio < 0.65 ) { |
| 17 | + |
| 18 | + resolutionScale *= 0.5; |
| 19 | + tiles = 2; |
| 20 | + |
| 21 | +} |
| 22 | + |
| 23 | +init(); |
| 24 | + |
| 25 | +async function init() { |
| 26 | + |
| 27 | + samplesEl = document.getElementById( 'samples' ); |
| 28 | + |
| 29 | + // init renderer, camera, controls, scene |
| 30 | + renderer = new THREE.WebGLRenderer( { antialias: true } ); |
| 31 | + renderer.toneMapping = THREE.ACESFilmicToneMapping; |
| 32 | + renderer.outputEncoding = THREE.sRGBEncoding; |
| 33 | + renderer.setClearColor( 0, 0 ); |
| 34 | + document.body.appendChild( renderer.domElement ); |
| 35 | + |
| 36 | + camera = new PhysicalCamera( 75, 1, 0.025, 500 ); |
| 37 | + camera.position.set( 8, 9, 24 ); |
| 38 | + |
| 39 | + controls = new OrbitControls( camera, renderer.domElement ); |
| 40 | + controls.target.y = 10; |
| 41 | + controls.update(); |
| 42 | + |
| 43 | + scene = new THREE.Scene(); |
| 44 | + |
| 45 | + // init path tracer |
| 46 | + pathTracer = new PathTracingRenderer( renderer ); |
| 47 | + pathTracer.material = new PhysicalPathTracingMaterial(); |
| 48 | + pathTracer.material.filterGlossyFactor = 0.5; |
| 49 | + pathTracer.material.backgroundBlur = 0.05; |
| 50 | + pathTracer.tiles.set( tiles, tiles ); |
| 51 | + pathTracer.camera = camera; |
| 52 | + |
| 53 | + blitQuad = new FullScreenQuad( new THREE.MeshBasicMaterial( { |
| 54 | + map: pathTracer.target.texture, |
| 55 | + blending: THREE.CustomBlending, |
| 56 | + } ) ); |
| 57 | + |
| 58 | + controls.addEventListener( 'change', () => { |
| 59 | + |
| 60 | + pathTracer.reset(); |
| 61 | + |
| 62 | + } ); |
| 63 | + |
| 64 | + // load the envmap and model |
| 65 | + const envMapPromise = new RGBELoader() |
| 66 | + .loadAsync( 'https://raw.githubusercontent.com/gkjohnson/3d-demo-data/master/hdri/chinese_garden_1k.hdr' ) |
| 67 | + .then( texture => { |
| 68 | + |
| 69 | + texture.mapping = THREE.EquirectangularReflectionMapping; |
| 70 | + scene.background = texture; |
| 71 | + scene.environment = texture; |
| 72 | + pathTracer.material.envMapInfo.updateFrom( texture ); |
| 73 | + |
| 74 | + } ); |
| 75 | + |
| 76 | + const generator = new PathTracingSceneWorker(); |
| 77 | + const gltfPromise = new GLTFLoader() |
| 78 | + .loadAsync( 'https://raw.githubusercontent.com/gkjohnson/3d-demo-data/main/models/terrarium-robots/scene.gltf' ) |
| 79 | + .then( gltf => { |
| 80 | + |
| 81 | + return generator.generate( gltf.scene ); |
| 82 | + |
| 83 | + } ) |
| 84 | + .then( result => { |
| 85 | + |
| 86 | + scene.add( result.scene ); |
| 87 | + |
| 88 | + const { bvh, textures, materials } = result; |
| 89 | + const geometry = bvh.geometry; |
| 90 | + const material = pathTracer.material; |
| 91 | + |
| 92 | + material.bvh.updateFrom( bvh ); |
| 93 | + material.normalAttribute.updateFrom( geometry.attributes.normal ); |
| 94 | + material.tangentAttribute.updateFrom( geometry.attributes.tangent ); |
| 95 | + material.uvAttribute.updateFrom( geometry.attributes.uv ); |
| 96 | + material.materialIndexAttribute.updateFrom( geometry.attributes.materialIndex ); |
| 97 | + material.textures.setTextures( renderer, 2048, 2048, textures ); |
| 98 | + material.materials.updateFrom( materials, textures ); |
| 99 | + |
| 100 | + generator.dispose(); |
| 101 | + |
| 102 | + } ); |
| 103 | + |
| 104 | + // wait for the scene to be rady |
| 105 | + await Promise.all( [ gltfPromise, envMapPromise ] ); |
| 106 | + |
| 107 | + document.getElementById( 'loading' ).remove(); |
| 108 | + window.addEventListener( 'resize', onResize ); |
| 109 | + |
| 110 | + onResize(); |
| 111 | + animate(); |
| 112 | + |
| 113 | +} |
| 114 | + |
| 115 | +function onResize() { |
| 116 | + |
| 117 | + // update rendering resolution |
| 118 | + const w = window.innerWidth; |
| 119 | + const h = window.innerHeight; |
| 120 | + const scale = resolutionScale; |
| 121 | + const dpr = window.devicePixelRatio; |
| 122 | + |
| 123 | + pathTracer.setSize( w * scale * dpr, h * scale * dpr ); |
| 124 | + pathTracer.reset(); |
| 125 | + |
| 126 | + renderer.setSize( w, h ); |
| 127 | + renderer.setPixelRatio( window.devicePixelRatio * scale ); |
| 128 | + |
| 129 | + const aspect = w / h; |
| 130 | + camera.aspect = aspect; |
| 131 | + camera.updateProjectionMatrix(); |
| 132 | + |
| 133 | +} |
| 134 | + |
| 135 | +function animate() { |
| 136 | + |
| 137 | + requestAnimationFrame( animate ); |
| 138 | + |
| 139 | + camera.updateMatrixWorld(); |
| 140 | + pathTracer.update(); |
| 141 | + |
| 142 | + if ( pathTracer.samples < 1 ) { |
| 143 | + |
| 144 | + renderer.render( scene, camera ); |
| 145 | + |
| 146 | + } |
| 147 | + |
| 148 | + renderer.autoClear = false; |
| 149 | + blitQuad.material.map = pathTracer.target.texture; |
| 150 | + blitQuad.render( renderer ); |
| 151 | + renderer.autoClear = true; |
| 152 | + |
| 153 | + samplesEl.innerText = `Samples: ${ Math.floor( pathTracer.samples ) }`; |
| 154 | + |
| 155 | +} |
0 commit comments