|
36 | 36 | <script type="module"> |
37 | 37 |
|
38 | 38 | import * as THREE from 'three/webgpu'; |
39 | | - import { pass, mrt, output, normalView, velocity, vec3, vec4, directionToColor, colorSpaceToWorking } from 'three/tsl'; |
| 39 | + import { sample, pass, mrt, context, screenUV, normalView, velocity, vec3, vec4, directionToColor, colorToDirection, colorSpaceToWorking } from 'three/tsl'; |
40 | 40 | import { ao } from 'three/addons/tsl/display/GTAONode.js'; |
41 | 41 | import { traa } from 'three/addons/tsl/display/TRAANode.js'; |
42 | 42 |
|
|
49 | 49 |
|
50 | 50 | let camera, scene, renderer, postProcessing, controls; |
51 | 51 |
|
52 | | - let aoPass, traaPass, blendPassAO, scenePassColor; |
| 52 | + let aoPass, traaPass; |
53 | 53 |
|
54 | 54 | const params = { |
55 | 55 | samples: 16, |
|
79 | 79 |
|
80 | 80 | await renderer.init(); |
81 | 81 |
|
82 | | - const environment = new RoomEnvironment(); |
83 | | - const pmremGenerator = new THREE.PMREMGenerator( renderer ); |
84 | | - |
85 | | - scene.background = new THREE.Color( 0x666666 ); |
86 | | - scene.environment = pmremGenerator.fromScene( environment ).texture; |
87 | | - environment.dispose(); |
88 | | - pmremGenerator.dispose(); |
89 | | - |
90 | | - // |
| 82 | + // controls |
91 | 83 |
|
92 | 84 | controls = new OrbitControls( camera, renderer.domElement ); |
93 | 85 | controls.target.set( 0, 0.5, - 1 ); |
|
97 | 89 | controls.minDistance = 2; |
98 | 90 | controls.maxDistance = 8; |
99 | 91 |
|
100 | | - // |
| 92 | + // environment |
| 93 | + |
| 94 | + const environment = new RoomEnvironment(); |
| 95 | + const pmremGenerator = new THREE.PMREMGenerator( renderer ); |
| 96 | + |
| 97 | + scene.background = new THREE.Color( 0x666666 ); |
| 98 | + scene.environment = pmremGenerator.fromScene( environment ).texture; |
| 99 | + environment.dispose(); |
| 100 | + pmremGenerator.dispose(); |
| 101 | + |
| 102 | + // post-processing |
101 | 103 |
|
102 | 104 | postProcessing = new THREE.PostProcessing( renderer ); |
103 | 105 |
|
104 | | - const scenePass = pass( scene, camera ); |
105 | | - scenePass.setMRT( mrt( { |
106 | | - output: output, |
107 | | - normal: normalView, |
| 106 | + // pre-pass |
| 107 | + |
| 108 | + const prePass = pass( scene, camera ).toInspector( 'Normal', ( inspectNode ) => colorSpaceToWorking( inspectNode, THREE.SRGBColorSpace ) ); |
| 109 | + prePass.name = 'Pre-Pass'; |
| 110 | + prePass.transparent = false; |
| 111 | + |
| 112 | + prePass.setMRT( mrt( { |
| 113 | + output: directionToColor( normalView ), |
108 | 114 | velocity: velocity |
109 | 115 | } ) ); |
110 | 116 |
|
111 | | - scenePassColor = scenePass.getTextureNode( 'output' ).toInspector( 'Color' ); |
112 | | - const scenePassDepth = scenePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => { |
| 117 | + const prePassNormal = sample( ( uv ) => { |
113 | 118 |
|
114 | | - return scenePass.getLinearDepthNode(); |
| 119 | + return colorToDirection( prePass.getTextureNode().sample( uv ) ); |
115 | 120 |
|
116 | 121 | } ); |
117 | | - const scenePassNormal = scenePass.getTextureNode( 'normal' ).toInspector( 'Normal', () => { |
118 | 122 |
|
119 | | - return colorSpaceToWorking( directionToColor( scenePassNormal ), THREE.SRGBColorSpace ); |
| 123 | + const prePassDepth = prePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => prePass.getLinearDepthNode() ); |
| 124 | + const prePassVelocity = prePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' ); |
120 | 125 |
|
121 | | - } ); |
122 | | - const scenePassVelocity = scenePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' ); |
| 126 | + // pre-pass - bandwidth optimization |
| 127 | + |
| 128 | + const normalTexture = prePass.getTexture( 'output' ); |
| 129 | + normalTexture.type = THREE.UnsignedByteType; |
| 130 | + |
| 131 | + // scene pass |
| 132 | + |
| 133 | + const scenePass = pass( scene, camera ).toInspector( 'Color' ); |
123 | 134 |
|
124 | 135 | // ao |
125 | 136 |
|
126 | | - aoPass = ao( scenePassDepth, scenePassNormal, camera ); |
| 137 | + aoPass = ao( prePassDepth, prePassNormal, camera ).toInspector( 'GTAO', ( inspectNode ) => inspectNode.r ); |
127 | 138 | aoPass.resolutionScale = 0.5; // running AO in half resolution is often sufficient |
128 | 139 | aoPass.useTemporalFiltering = true; |
129 | | - blendPassAO = vec4( scenePassColor.rgb.mul( aoPass.r.toInspector( 'AO' ) ), scenePassColor.a ); // the AO is stored only in the red channel |
130 | 140 |
|
131 | | - // traa |
| 141 | + const aoPassOutput = aoPass.getTextureNode( 'output' ); |
| 142 | + |
| 143 | + // scene context |
| 144 | + |
| 145 | + scenePass.contextNode = context( { |
| 146 | + ao: aoPassOutput.sample( screenUV ).r |
| 147 | + } ); |
| 148 | + |
| 149 | + // final output + traa |
| 150 | + |
| 151 | + traaPass = traa( scenePass, prePassDepth, prePassVelocity, camera ); |
132 | 152 |
|
133 | | - traaPass = traa( blendPassAO, scenePassDepth, scenePassVelocity, camera ); |
134 | 153 | postProcessing.outputNode = traaPass; |
135 | 154 |
|
136 | | - // |
| 155 | + // models |
137 | 156 |
|
138 | 157 | const dracoLoader = new DRACOLoader(); |
139 | 158 | dracoLoader.setDecoderPath( 'jsm/libs/draco/' ); |
|
148 | 167 | model.position.set( 0, 1, 0 ); |
149 | 168 | scene.add( model ); |
150 | 169 |
|
151 | | - model.traverse( ( o ) => { |
152 | | - |
153 | | - // Transparent objects (e.g. loaded via GLTFLoader) might have "depthWrite" set to "false". |
154 | | - // This is wanted when rendering the beauty pass however it produces wrong results when computing |
155 | | - // AO since depth and normal data are out of sync. Computing normals from depth by not using MRT |
156 | | - // can mitigate the issue although the depth information (and thus the normals) are not correct in |
157 | | - // first place. Besides, normal estimation is computationally more expensive than just sampling a |
158 | | - // normal texture. So depending on your scene, consider to enable "depthWrite" for all transparent objects. |
| 170 | + // |
159 | 171 |
|
160 | | - if ( o.material ) o.material.depthWrite = true; |
| 172 | + const transparentMesh = new THREE.Mesh( new THREE.PlaneGeometry( 1.8, 2 ), new THREE.MeshStandardNodeMaterial( { transparent: true, opacity: .1 } ) ); |
| 173 | + transparentMesh.material.transparent = true; |
| 174 | + transparentMesh.position.z = 0; |
| 175 | + transparentMesh.position.y = 0.5; |
| 176 | + transparentMesh.visible = false; |
| 177 | + scene.add( transparentMesh ); |
161 | 178 |
|
162 | | - } ); |
| 179 | + // events |
163 | 180 |
|
164 | 181 | window.addEventListener( 'resize', onWindowResize ); |
165 | 182 |
|
|
173 | 190 | gui.add( params, 'scale', 0.01, 2 ).onChange( updateParameters ); |
174 | 191 | gui.add( params, 'thickness', 0.01, 2 ).onChange( updateParameters ); |
175 | 192 | gui.add( aoPass, 'useTemporalFiltering' ).name( 'temporal filtering' ); |
| 193 | + gui.add( transparentMesh, 'visible' ).name( 'show transparent mesh' ); |
176 | 194 | gui.add( params, 'aoOnly' ).onChange( ( value ) => { |
177 | 195 |
|
178 | 196 | if ( value === true ) { |
|
0 commit comments