three-gpu-pathtracer 0.0.1 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +678 -386
  3. package/build/index.module.js +3166 -1690
  4. package/build/index.module.js.map +1 -1
  5. package/build/index.umd.cjs +3176 -1692
  6. package/build/index.umd.cjs.map +1 -1
  7. package/package.json +60 -57
  8. package/src/core/DynamicPathTracingSceneGenerator.js +106 -0
  9. package/src/core/MaterialReducer.js +256 -256
  10. package/src/core/PathTracingRenderer.js +125 -28
  11. package/src/core/PathTracingSceneGenerator.js +52 -46
  12. package/src/core/PhysicalCamera.js +28 -0
  13. package/src/index.js +25 -21
  14. package/src/materials/AlphaDisplayMaterial.js +48 -0
  15. package/src/materials/AmbientOcclusionMaterial.js +197 -197
  16. package/src/materials/BlendMaterial.js +67 -0
  17. package/src/materials/LambertPathTracingMaterial.js +285 -285
  18. package/src/materials/MaterialBase.js +56 -56
  19. package/src/materials/PhysicalPathTracingMaterial.js +684 -370
  20. package/src/shader/shaderEnvMapSampling.js +67 -0
  21. package/src/shader/shaderGGXFunctions.js +108 -107
  22. package/src/shader/shaderMaterialSampling.js +345 -333
  23. package/src/shader/shaderStructs.js +131 -30
  24. package/src/shader/shaderUtils.js +246 -140
  25. package/src/uniforms/EquirectHdrInfoUniform.js +263 -0
  26. package/src/uniforms/MaterialsTexture.js +251 -0
  27. package/src/uniforms/PhysicalCameraUniform.js +36 -0
  28. package/src/uniforms/RenderTarget2DArray.js +93 -80
  29. package/src/utils/BlurredEnvMapGenerator.js +113 -0
  30. package/src/utils/GeometryPreparationUtils.js +194 -172
  31. package/src/utils/UVUnwrapper.js +101 -101
  32. package/src/workers/PathTracingSceneWorker.js +40 -0
  33. package/src/uniforms/EquirectPdfUniform.js +0 -132
  34. package/src/uniforms/MaterialStructArrayUniform.js +0 -18
  35. package/src/uniforms/MaterialStructUniform.js +0 -94
  36. package/src/viewers/PathTracingViewer.js +0 -259
@@ -1,25 +1,164 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three'), require('three/examples/jsm/postprocessing/Pass.js'), require('three-mesh-bvh'), require('three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js'), require('three/examples/jsm/utils/BufferGeometryUtils.js')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'three', 'three/examples/jsm/postprocessing/Pass.js', 'three-mesh-bvh', 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js', 'three/examples/jsm/utils/BufferGeometryUtils.js'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ThreePathTracer = global.ThreePathTracer || {}, global.THREE, global.THREE, global.MeshBVHLib, global.MeshBVHLib, global.THREE));
5
- })(this, (function (exports, three, Pass_js, threeMeshBvh, GenerateMeshBVHWorker_js, BufferGeometryUtils_js) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three'), require('three/examples/jsm/postprocessing/Pass.js'), require('three-mesh-bvh'), require('three/examples/jsm/utils/BufferGeometryUtils.js')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'three', 'three/examples/jsm/postprocessing/Pass.js', 'three-mesh-bvh', 'three/examples/jsm/utils/BufferGeometryUtils.js'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ThreePathTracer = global.ThreePathTracer || {}, global.THREE, global.THREE, global.MeshBVHLib, global.THREE));
5
+ })(this, (function (exports, three, Pass_js, threeMeshBvh, BufferGeometryUtils_js) { 'use strict';
6
+
7
+ class MaterialBase extends three.ShaderMaterial {
8
+
9
+ constructor( shader ) {
10
+
11
+ super( shader );
12
+
13
+ for ( const key in this.uniforms ) {
14
+
15
+ Object.defineProperty( this, key, {
16
+
17
+ get() {
18
+
19
+ return this.uniforms[ key ].value;
20
+
21
+ },
22
+
23
+ set( v ) {
24
+
25
+ this.uniforms[ key ].value = v;
26
+
27
+ }
28
+
29
+ } );
30
+
31
+ }
32
+
33
+ }
34
+
35
+ // sets the given named define value and sets "needsUpdate" to true if it's different
36
+ setDefine( name, value = undefined ) {
37
+
38
+ if ( value === undefined || value === null ) {
39
+
40
+ if ( name in this.defines ) {
41
+
42
+ delete this.defines[ name ];
43
+ this.needsUpdate = true;
44
+
45
+ }
46
+
47
+ } else {
48
+
49
+ if ( this.defines[ name ] !== value ) {
50
+
51
+ this.defines[ name ] = value;
52
+ this.needsUpdate = true;
53
+
54
+ }
55
+
56
+ }
57
+
58
+ }
59
+
60
+ }
61
+
62
+ class BlendMaterial extends MaterialBase {
63
+
64
+ constructor( parameters ) {
65
+
66
+ super( {
67
+
68
+ blending: three.NoBlending,
69
+
70
+ uniforms: {
71
+
72
+ target1: { value: null },
73
+ target2: { value: null },
74
+ opacity: { value: 1.0 },
75
+
76
+ },
77
+
78
+ vertexShader: /* glsl */`
79
+
80
+ varying vec2 vUv;
81
+
82
+ void main() {
83
+
84
+ vUv = uv;
85
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
86
+
87
+ }`,
88
+
89
+ fragmentShader: /* glsl */`
90
+
91
+ uniform float opacity;
92
+
93
+ uniform sampler2D target1;
94
+ uniform sampler2D target2;
95
+
96
+ varying vec2 vUv;
97
+
98
+ void main() {
99
+
100
+ vec4 color1 = texture2D( target1, vUv );
101
+ vec4 color2 = texture2D( target2, vUv );
102
+
103
+ float invOpacity = 1.0 - opacity;
104
+ float totalAlpha = color1.a * invOpacity + color2.a * opacity;
105
+
106
+ if ( color1.a != 0.0 || color2.a != 0.0 ) {
107
+
108
+ gl_FragColor.rgb = color1.rgb * ( invOpacity * color1.a / totalAlpha ) + color2.rgb * ( opacity * color2.a / totalAlpha );
109
+ gl_FragColor.a = totalAlpha;
110
+
111
+ } else {
112
+
113
+ gl_FragColor = vec4( 0.0 );
114
+
115
+ }
116
+
117
+ }`
118
+
119
+ } );
120
+
121
+ this.setValues( parameters );
122
+
123
+ }
124
+
125
+ }
6
126
 
7
127
  function* renderTask() {
8
128
 
9
- const { _fsQuad, _renderer, target, camera, material } = this;
129
+ const {
130
+ _renderer,
131
+ _fsQuad,
132
+ _blendQuad,
133
+ _primaryTarget,
134
+ _blendTargets,
135
+ alpha,
136
+ camera,
137
+ material,
138
+ } = this;
139
+
140
+ const blendMaterial = _blendQuad.material;
141
+ let [ blendTarget1, blendTarget2 ] = _blendTargets;
142
+
10
143
  while ( true ) {
11
144
 
12
- material.opacity = 1 / ( this.samples + 1 );
13
- material.seed ++;
145
+ if ( alpha ) {
146
+
147
+ blendMaterial.opacity = 1 / ( this.samples + 1 );
148
+ material.blending = three.NoBlending;
149
+ material.opacity = 1;
150
+
151
+ } else {
152
+
153
+ material.opacity = 1 / ( this.samples + 1 );
154
+ material.blending = three.NormalBlending;
155
+
156
+ }
14
157
 
15
- const w = target.width;
16
- const h = target.height;
17
- camera.setViewOffset(
18
- w, h,
19
- Math.random() - 0.5, Math.random() - 0.5,
20
- w, h,
21
- );
22
- camera.updateProjectionMatrix();
158
+ const w = _primaryTarget.width;
159
+ const h = _primaryTarget.height;
160
+ material.resolution.set( w, h );
161
+ material.seed ++;
23
162
 
24
163
  const tx = this.tiles.x || 1;
25
164
  const ty = this.tiles.y || 1;
@@ -31,12 +170,15 @@
31
170
 
32
171
  material.cameraWorldMatrix.copy( camera.matrixWorld );
33
172
  material.invProjectionMatrix.copy( camera.projectionMatrixInverse );
173
+ // An orthographic projection matrix will always have the bottom right element == 1
174
+ // And a perspective projection matrix will always have the bottom right element == 0
175
+ material.isOrthographicCamera = camera.projectionMatrix.elements[ 15 ] > 0;
34
176
 
35
177
  const ogRenderTarget = _renderer.getRenderTarget();
36
178
  const ogAutoClear = _renderer.autoClear;
37
179
 
38
180
  // three.js renderer takes values relative to the current pixel ratio
39
- _renderer.setRenderTarget( target );
181
+ _renderer.setRenderTarget( _primaryTarget );
40
182
  _renderer.setScissorTest( true );
41
183
  _renderer.setScissor(
42
184
  dprInv * Math.ceil( x * w / tx ),
@@ -50,6 +192,17 @@
50
192
  _renderer.setRenderTarget( ogRenderTarget );
51
193
  _renderer.autoClear = ogAutoClear;
52
194
 
195
+ if ( alpha ) {
196
+
197
+ blendMaterial.target1 = blendTarget1.texture;
198
+ blendMaterial.target2 = _primaryTarget.texture;
199
+
200
+ _renderer.setRenderTarget( blendTarget2 );
201
+ _blendQuad.render( _renderer );
202
+ _renderer.setRenderTarget( ogRenderTarget );
203
+
204
+ }
205
+
53
206
  this.samples += ( 1 / totalTiles );
54
207
 
55
208
  yield;
@@ -58,6 +211,8 @@
58
211
 
59
212
  }
60
213
 
214
+ [ blendTarget1, blendTarget2 ] = [ blendTarget2, blendTarget1 ];
215
+
61
216
  this.samples = Math.round( this.samples );
62
217
 
63
218
  }
@@ -79,43 +234,104 @@
79
234
 
80
235
  }
81
236
 
237
+ get target() {
238
+
239
+ return this._alpha ? this._blendTargets[ 1 ] : this._primaryTarget;
240
+
241
+ }
242
+
243
+ set alpha( v ) {
244
+
245
+ if ( ! v ) {
246
+
247
+ this._blendTargets[ 0 ].dispose();
248
+ this._blendTargets[ 1 ].dispose();
249
+
250
+ }
251
+
252
+ this._alpha = v;
253
+ this.reset();
254
+
255
+ }
256
+
257
+ get alpha() {
258
+
259
+ return this._alpha;
260
+
261
+ }
262
+
82
263
  constructor( renderer ) {
83
264
 
84
265
  this.camera = null;
85
266
  this.tiles = new three.Vector2( 1, 1 );
86
- this.target = new three.WebGLRenderTarget( 1, 1, {
87
- format: three.RGBAFormat,
88
- type: three.FloatType,
89
- } );
267
+
90
268
  this.samples = 0;
91
269
  this.stableNoise = false;
92
270
  this._renderer = renderer;
271
+ this._alpha = false;
93
272
  this._fsQuad = new Pass_js.FullScreenQuad( null );
273
+ this._blendQuad = new Pass_js.FullScreenQuad( new BlendMaterial() );
94
274
  this._task = null;
95
275
 
276
+ this._primaryTarget = new three.WebGLRenderTarget( 1, 1, {
277
+ format: three.RGBAFormat,
278
+ type: three.FloatType,
279
+ } );
280
+ this._blendTargets = [
281
+ new three.WebGLRenderTarget( 1, 1, {
282
+ format: three.RGBAFormat,
283
+ type: three.FloatType,
284
+ } ),
285
+ new three.WebGLRenderTarget( 1, 1, {
286
+ format: three.RGBAFormat,
287
+ type: three.FloatType,
288
+ } ),
289
+ ];
290
+
96
291
  }
97
292
 
98
293
  setSize( w, h ) {
99
294
 
100
- this.target.setSize( w, h );
295
+ this._primaryTarget.setSize( w, h );
296
+ this._blendTargets[ 0 ].setSize( w, h );
297
+ this._blendTargets[ 1 ].setSize( w, h );
101
298
  this.reset();
102
299
 
103
300
  }
104
301
 
302
+ dispose() {
303
+
304
+ this._primaryTarget.dispose();
305
+ this._blendTargets[ 0 ].dispose();
306
+ this._blendTargets[ 1 ].dispose();
307
+
308
+ this._fsQuad.dispose();
309
+ this._blendQuad.dispose();
310
+ this._task = null;
311
+
312
+ }
313
+
105
314
  reset() {
106
315
 
107
- const renderer = this._renderer;
108
- const target = this.target;
109
- const ogRenderTarget = renderer.getRenderTarget();
110
- const ogClearAlpha = renderer.getClearAlpha();
111
- renderer.getClearColor( ogClearColor );
316
+ const { _renderer, _primaryTarget, _blendTargets } = this;
317
+ const ogRenderTarget = _renderer.getRenderTarget();
318
+ const ogClearAlpha = _renderer.getClearAlpha();
319
+ _renderer.getClearColor( ogClearColor );
112
320
 
113
- renderer.setRenderTarget( target );
114
- renderer.setClearColor( 0, 0 );
115
- renderer.clearColor();
321
+ _renderer.setRenderTarget( _primaryTarget );
322
+ _renderer.setClearColor( 0, 0 );
323
+ _renderer.clearColor();
324
+
325
+ _renderer.setRenderTarget( _blendTargets[ 0 ] );
326
+ _renderer.setClearColor( 0, 0 );
327
+ _renderer.clearColor();
116
328
 
117
- renderer.setClearColor( ogClearColor, ogClearAlpha );
118
- renderer.setRenderTarget( ogRenderTarget );
329
+ _renderer.setRenderTarget( _blendTargets[ 1 ] );
330
+ _renderer.setClearColor( 0, 0 );
331
+ _renderer.clearColor();
332
+
333
+ _renderer.setClearColor( ogClearColor, ogClearAlpha );
334
+ _renderer.setRenderTarget( ogRenderTarget );
119
335
 
120
336
  this.samples = 0;
121
337
  this._task = null;
@@ -142,1697 +358,2965 @@
142
358
 
143
359
  }
144
360
 
145
- function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
146
-
147
- if ( ! Array.isArray( materials ) ) {
148
-
149
- materials = [ materials ];
150
-
151
- }
152
-
153
- const vertCount = geometry.attributes.position.count;
154
- const materialArray = new Uint8Array( vertCount );
155
- let groups = geometry.groups;
156
- if ( groups.length === 0 ) {
157
-
158
- groups = [ { count: vertCount, start: 0, materialIndex: 0 } ];
159
-
160
- }
161
-
162
- for ( let i = 0; i < groups.length; i ++ ) {
163
-
164
- const group = groups[ i ];
165
- const { count, start } = group;
166
- const endCount = Math.min( count, vertCount - start );
167
- const mat = materials[ group.materialIndex ];
168
- const materialIndex = allMaterials.indexOf( mat );
169
-
170
- for ( let j = 0; j < endCount; j ++ ) {
171
-
172
- materialArray[ start + j ] = materialIndex;
173
-
174
- }
175
-
176
- }
177
-
178
- return new three.BufferAttribute( materialArray, 1, false );
179
-
180
- }
181
-
182
- function mergeMeshes( meshes, options = {} ) {
183
-
184
- options = { attributes: null, cloneGeometry: true, ...options };
185
-
186
- const transformedGeometry = [];
187
- const materialSet = new Set();
188
- for ( let i = 0, l = meshes.length; i < l; i ++ ) {
189
-
190
- // save any materials
191
- const mesh = meshes[ i ];
192
- if ( mesh.visible === false ) continue;
193
-
194
- if ( Array.isArray( mesh.material ) ) {
195
-
196
- mesh.material.forEach( m => materialSet.add( m ) );
197
-
198
- } else {
199
-
200
- materialSet.add( mesh.material );
201
-
202
- }
203
-
204
- }
205
-
206
- const materials = Array.from( materialSet );
207
- for ( let i = 0, l = meshes.length; i < l; i ++ ) {
208
-
209
- // ensure the matrix world is up to date
210
- const mesh = meshes[ i ];
211
- if ( mesh.visible === false ) continue;
212
-
213
- mesh.updateMatrixWorld();
214
-
215
- // apply the matrix world to the geometry
216
- const originalGeometry = meshes[ i ].geometry;
217
- let geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
218
- geometry.applyMatrix4( mesh.matrixWorld );
219
-
220
- const attrs = options.attributes;
221
- if ( ! geometry.attributes.normal && ( attrs && attrs.includes( 'normal' ) ) ) {
222
-
223
- geometry.computeVertexNormals();
224
-
225
- }
226
-
227
- if ( ! geometry.attributes.uv && ( attrs && attrs.includes( 'uv' ) ) ) {
228
-
229
- const vertCount = geometry.attributes.position.count;
230
- geometry.setAttribute( 'uv', new three.BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
231
-
232
- }
233
-
234
- if ( ! geometry.attributes.tangent && ( attrs && attrs.includes( 'tangent' ) ) ) {
235
-
236
- if ( mesh.material.normalMap ) {
237
-
238
- // computeTangents requires an index buffer
239
- if ( geometry.index === null ) {
240
-
241
- geometry = BufferGeometryUtils_js.mergeVertices( geometry );
242
-
243
- }
244
-
245
- geometry.computeTangents();
246
-
247
- } else {
248
-
249
- const vertCount = geometry.attributes.position.count;
250
- geometry.setAttribute( 'tangent', new three.BufferAttribute( new Float32Array( vertCount * 4 ), 4, false ) );
251
-
252
- }
253
-
254
- }
255
-
256
- if ( ! geometry.index ) {
257
-
258
- // TODO: compute a typed array
259
- const indexCount = geometry.attributes.position.count;
260
- const array = new Array( indexCount );
261
- for ( let i = 0; i < indexCount; i ++ ) {
262
-
263
- array[ i ] = i;
264
-
265
- }
266
-
267
- geometry.setIndex( array );
268
-
269
- }
270
-
271
- // trim any unneeded attributes
272
- if ( options.attributes ) {
273
-
274
- for ( const key in geometry.attributes ) {
275
-
276
- if ( ! options.attributes.includes( key ) ) {
277
-
278
- geometry.deleteAttribute( key );
279
-
280
- }
281
-
282
- }
283
-
284
- }
285
-
286
- // create the material index attribute
287
- const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
288
- geometry.setAttribute( 'materialIndex', materialIndexAttribute );
289
-
290
- transformedGeometry.push( geometry );
291
-
292
- }
293
-
294
- const textureSet = new Set();
295
- materials.forEach( material => {
296
-
297
- for ( const key in material ) {
298
-
299
- const value = material[ key ];
300
- if ( value && value.isTexture ) {
301
-
302
- textureSet.add( value );
303
-
304
- }
305
-
306
- }
307
-
308
- } );
309
-
310
- const geometry = BufferGeometryUtils_js.mergeBufferGeometries( transformedGeometry, false );
311
- const textures = Array.from( textureSet );
312
- return { geometry, materials, textures };
313
-
314
- }
361
+ function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
315
362
 
316
- class PathTracingSceneGenerator {
317
-
318
- constructor() {
319
-
320
- this.bvhGenerator = new GenerateMeshBVHWorker_js.GenerateMeshBVHWorker();
321
-
322
- }
323
-
324
- async generate( scene, options = {} ) {
325
-
326
- const { bvhGenerator } = this;
327
- const meshes = [];
328
-
329
- scene.traverse( c => {
330
-
331
- if ( c.isMesh ) {
332
-
333
- meshes.push( c );
334
-
335
- }
336
-
337
- } );
338
-
339
- const { geometry, materials, textures } = mergeMeshes( meshes, { attributes: [ 'position', 'normal', 'tangent', 'uv' ] } );
340
- const bvhPromise = bvhGenerator.generate( geometry, { strategy: threeMeshBvh.SAH, ...options, maxLeafTris: 1 } );
341
-
342
- return {
343
- scene,
344
- materials,
345
- textures,
346
- bvh: await bvhPromise,
347
- };
348
-
349
- }
350
-
351
- dispose() {
352
-
353
- this.bvhGenerator.terminate();
354
-
355
- }
356
-
357
- }
363
+ const indexAttr = geometry.index;
364
+ const posAttr = geometry.attributes.position;
365
+ const vertCount = posAttr.count;
366
+ const materialArray = new Uint8Array( vertCount );
367
+ const totalCount = indexAttr ? indexAttr.count : vertCount;
368
+ let groups = geometry.groups;
369
+ if ( groups.length === 0 ) {
370
+
371
+ groups = [ { count: totalCount, start: 0, materialIndex: 0 } ];
372
+
373
+ }
374
+
375
+ for ( let i = 0; i < groups.length; i ++ ) {
376
+
377
+ const group = groups[ i ];
378
+ const start = group.start;
379
+ const count = group.count;
380
+ const endCount = Math.min( count, totalCount - start );
381
+
382
+ const mat = Array.isArray( materials ) ? materials[ group.materialIndex ] : materials;
383
+ const materialIndex = allMaterials.indexOf( mat );
384
+
385
+ for ( let j = 0; j < endCount; j ++ ) {
386
+
387
+ let index = start + j;
388
+ if ( indexAttr ) {
389
+
390
+ index = indexAttr.getX( index );
391
+
392
+ }
393
+
394
+ materialArray[ index ] = materialIndex;
395
+
396
+ }
397
+
398
+ }
399
+
400
+ return new three.BufferAttribute( materialArray, 1, false );
358
401
 
359
- // https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
360
-
361
- function isTypedArray( arr ) {
362
-
363
- return arr.buffer instanceof ArrayBuffer && 'BYTES_PER_ELEMENT' in arr;
364
-
365
- }
366
-
367
- class MaterialReducer {
368
-
369
- constructor() {
370
-
371
- const ignoreKeys = new Set();
372
- ignoreKeys.add( 'uuid' );
373
-
374
- this.ignoreKeys = ignoreKeys;
375
- this.shareTextures = true;
376
- this.textures = [];
377
- this.materials = [];
378
-
379
- }
380
-
381
- areEqual( objectA, objectB ) {
382
-
383
- const keySet = new Set();
384
- const traverseSet = new Set();
385
- const ignoreKeys = this.ignoreKeys;
386
-
387
- const traverse = ( a, b ) => {
388
-
389
- if ( a === b ) {
390
-
391
- return true;
392
-
393
- }
394
-
395
- if ( a && b && a instanceof Object && b instanceof Object ) {
396
-
397
- if ( traverseSet.has( a ) || traverseSet.has( b ) ) {
398
-
399
- throw new Error( 'MaterialReducer: Material is recursive.' );
400
-
401
- }
402
-
403
- const aIsElement = a instanceof Element;
404
- const bIsElement = b instanceof Element;
405
- if ( aIsElement || bIsElement ) {
406
-
407
- if ( aIsElement !== bIsElement || ! ( a instanceof Image ) || ! ( b instanceof Image ) ) {
408
-
409
- return false;
410
-
411
- }
412
-
413
- return a.src === b.src;
414
-
415
- }
416
-
417
- const aIsImageBitmap = a instanceof ImageBitmap;
418
- const bIsImageBitmap = b instanceof ImageBitmap;
419
- if ( aIsImageBitmap || bIsImageBitmap ) {
420
-
421
- return false;
422
-
423
- }
424
-
425
- if ( a.equals ) {
426
-
427
- return a.equals( b );
428
-
429
- }
430
-
431
- const aIsTypedArray = isTypedArray( a );
432
- const bIsTypedArray = isTypedArray( b );
433
- if ( aIsTypedArray || bIsTypedArray ) {
434
-
435
- if ( aIsTypedArray !== bIsTypedArray || a.constructor !== b.constructor || a.length !== b.length ) {
436
-
437
- return false;
438
-
439
- }
440
-
441
- for ( let i = 0, l = a.length; i < l; i ++ ) {
442
-
443
- if ( a[ i ] !== b[ i ] ) return false;
444
-
445
- }
446
-
447
- return true;
448
-
449
- }
450
-
451
- traverseSet.add( a );
452
- traverseSet.add( b );
453
-
454
- keySet.clear();
455
- for ( const key in a ) {
456
-
457
- if ( ! a.hasOwnProperty( key ) || a[ key ] instanceof Function || ignoreKeys.has( key ) ) {
458
-
459
- continue;
460
-
461
- }
462
-
463
- keySet.add( key );
464
-
465
- }
466
-
467
- for ( const key in b ) {
468
-
469
- if ( ! b.hasOwnProperty( key ) || b[ key ] instanceof Function || ignoreKeys.has( key ) ) {
470
-
471
- continue;
472
-
473
- }
474
-
475
- keySet.add( key );
476
-
477
- }
478
-
479
- const keys = Array.from( keySet.values() );
480
- let result = true;
481
- for ( const i in keys ) {
482
-
483
- const key = keys[ i ];
484
- if ( ignoreKeys.has( key ) ) {
485
-
486
- continue;
487
-
488
- }
489
-
490
- result = traverse( a[ key ], b[ key ] );
491
- if ( ! result ) {
492
-
493
- break;
494
-
495
- }
496
-
497
- }
498
-
499
- traverseSet.delete( a );
500
- traverseSet.delete( b );
501
- return result;
502
-
503
- }
504
-
505
- return false;
506
-
507
- };
508
-
509
- return traverse( objectA, objectB );
510
-
511
- }
512
-
513
- process( object ) {
514
-
515
- const { textures, materials } = this;
516
- let replaced = 0;
517
-
518
- const processMaterial = material => {
519
-
520
- // Check if another material matches this one
521
- let foundMaterial = null;
522
- for ( const i in materials ) {
523
-
524
- const otherMaterial = materials[ i ];
525
- if ( this.areEqual( material, otherMaterial ) ) {
526
-
527
- foundMaterial = otherMaterial;
528
-
529
- }
530
-
531
- }
532
-
533
- if ( foundMaterial ) {
534
-
535
- replaced ++;
536
- return foundMaterial;
537
-
538
- } else {
539
-
540
- materials.push( material );
541
-
542
- if ( this.shareTextures ) {
543
-
544
- // See if there's another texture that matches the ones on this material
545
- for ( const key in material ) {
546
-
547
- if ( ! material.hasOwnProperty( key ) ) continue;
548
-
549
- const value = material[ key ];
550
- if ( value && value.isTexture && value.image instanceof Image ) {
551
-
552
- let foundTexture = null;
553
- for ( const i in textures ) {
554
-
555
- const texture = textures[ i ];
556
- if ( this.areEqual( texture, value ) ) {
557
-
558
- foundTexture = texture;
559
- break;
560
-
561
- }
562
-
563
- }
564
-
565
- if ( foundTexture ) {
566
-
567
- material[ key ] = foundTexture;
568
-
569
- } else {
570
-
571
- textures.push( value );
572
-
573
- }
574
-
575
- }
576
-
577
- }
578
-
579
- }
580
-
581
- return material;
582
-
583
- }
584
-
585
- };
586
-
587
- object.traverse( c => {
588
-
589
- if ( c.isMesh && c.material ) {
590
-
591
- const material = c.material;
592
- if ( Array.isArray( material ) ) {
593
-
594
- for ( let i = 0; i < material.length; i ++ ) {
595
-
596
- material[ i ] = processMaterial( material[ i ] );
597
-
598
- }
599
-
600
- } else {
601
-
602
- c.material = processMaterial( material );
603
-
604
- }
605
-
606
- }
607
-
608
- } );
609
-
610
- return { replaced, retained: materials.length };
611
-
612
- }
613
-
614
402
  }
615
403
 
616
- class MaterialStructUniform {
617
-
618
- constructor() {
619
-
620
- this.init();
621
-
622
- }
623
-
624
- init() {
625
-
626
- this.color = new three.Color( 0xffffff );
627
- this.map = - 1;
628
-
629
- this.metalness = 1.0;
630
- this.metalnessMap = - 1;
631
-
632
- this.roughness = 1.0;
633
- this.roughnessMap = - 1;
634
-
635
- this.ior = 1.0;
636
- this.transmission = 0.0;
637
- this.transmissionMap = - 1;
638
-
639
- this.emissive = new three.Color( 0 );
640
- this.emissiveIntensity = 1.0;
641
- this.emissiveMap = - 1;
642
-
643
- this.normalMap = - 1;
644
- this.normalScale = new three.Vector2( 1, 1 );
645
-
646
- this.opacity = 1.0;
647
- this.alphaTest = 0.0;
648
-
649
- // TODO: Clearcoat
650
-
651
- // TODO: Sheen
652
-
653
- }
654
-
655
- updateFrom( material, textures = [] ) {
656
-
657
- this.init();
658
-
659
- // color
660
- if ( 'color' in material ) this.color.copy( material.color );
661
- else material.color.set( 0xffffff );
662
-
663
- this.map = textures.indexOf( material.map );
664
-
665
- // metalness
666
- if ( 'metalness' in material ) this.metalness = material.metalness;
667
- else this.metalness = 1.0;
668
-
669
- this.metalnessMap = textures.indexOf( material.metalnessMap );
670
-
671
- // roughness
672
- if ( 'roughness' in material ) this.roughness = material.roughness;
673
- else this.roughness = 1.0;
674
-
675
- this.roughnessMap = textures.indexOf( material.roughnessMap );
676
-
677
- // transmission
678
- if ( 'ior' in material ) this.ior = material.ior;
679
- else this.ior = 1.0;
680
-
681
- if ( 'transmission' in material ) this.transmission = material.transmission;
682
- else this.transmission = 0.0;
683
-
684
- if ( 'transmissionMap' in material ) this.transmissionMap = textures.indexOf( material.transmissionMap );
685
-
686
- // emission
687
- if ( 'emissive' in material ) this.emissive.copy( material.emissive );
688
- else this.emissive.set( 0 );
689
-
690
- if ( 'emissiveIntensity' in material ) this.emissiveIntensity = material.emissiveIntensity;
691
- else this.emissiveIntensity = 1.0;
692
-
693
- this.emissiveMap = textures.indexOf( material.emissiveMap );
694
-
695
- // normals
696
- this.normalMap = textures.indexOf( material.normalMap );
697
- if ( 'normalScale' in material ) this.normalScale.copy( material.normalScale );
698
- else this.normalScale.set( 1, 1 );
699
-
700
- // opacity
701
- this.opacity = material.opacity;
702
-
703
- // alpha test
704
- this.alphaTest = material.alphaTest;
705
-
706
- }
707
-
404
+ function trimToAttributes( geometry, attributes ) {
405
+
406
+ // trim any unneeded attributes
407
+ if ( attributes ) {
408
+
409
+ for ( const key in geometry.attributes ) {
410
+
411
+ if ( ! attributes.includes( key ) ) {
412
+
413
+ geometry.deleteAttribute( key );
414
+
415
+ }
416
+
417
+ }
418
+
419
+ }
420
+
708
421
  }
709
422
 
710
- class MaterialStructArrayUniform extends Array {
711
-
712
- updateFrom( materials, textures ) {
713
-
714
- while ( this.length > materials.length ) this.pop();
715
- while ( this.length < materials.length ) this.push( new MaterialStructUniform() );
716
-
717
- for ( let i = 0, l = this.length; i < l; i ++ ) {
718
-
719
- this[ i ].updateFrom( materials[ i ], textures );
720
-
721
- }
722
-
723
- }
724
-
423
+ function setCommonAttributes( geometry, options ) {
424
+
425
+ const { attributes = [], normalMapRequired = false } = options;
426
+
427
+ if ( ! geometry.attributes.normal && ( attributes && attributes.includes( 'normal' ) ) ) {
428
+
429
+ geometry.computeVertexNormals();
430
+
431
+ }
432
+
433
+ if ( ! geometry.attributes.uv && ( attributes && attributes.includes( 'uv' ) ) ) {
434
+
435
+ const vertCount = geometry.attributes.position.count;
436
+ geometry.setAttribute( 'uv', new three.BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
437
+
438
+ }
439
+
440
+ if ( ! geometry.attributes.tangent && ( attributes && attributes.includes( 'tangent' ) ) ) {
441
+
442
+ if ( normalMapRequired ) {
443
+
444
+ // computeTangents requires an index buffer
445
+ if ( geometry.index === null ) {
446
+
447
+ geometry = BufferGeometryUtils_js.mergeVertices( geometry );
448
+
449
+ }
450
+
451
+ geometry.computeTangents();
452
+
453
+ } else {
454
+
455
+ const vertCount = geometry.attributes.position.count;
456
+ geometry.setAttribute( 'tangent', new three.BufferAttribute( new Float32Array( vertCount * 4 ), 4, false ) );
457
+
458
+ }
459
+
460
+ }
461
+
462
+ if ( ! geometry.index ) {
463
+
464
+ // TODO: compute a typed array
465
+ const indexCount = geometry.attributes.position.count;
466
+ const array = new Array( indexCount );
467
+ for ( let i = 0; i < indexCount; i ++ ) {
468
+
469
+ array[ i ] = i;
470
+
471
+ }
472
+
473
+ geometry.setIndex( array );
474
+
475
+ }
476
+
725
477
  }
726
478
 
727
- const prevColor = new three.Color();
728
- class RenderTarget2DArray extends three.WebGLArrayRenderTarget {
729
-
730
- constructor( ...args ) {
731
-
732
- super( ...args );
733
-
734
- const tex = this.texture;
735
- tex.format = three.RGBAFormat;
736
- tex.type = three.UnsignedByteType;
737
- tex.minFilter = three.LinearFilter;
738
- tex.magFilter = three.LinearFilter;
739
- tex.wrapS = three.RepeatWrapping;
740
- tex.wrapT = three.RepeatWrapping;
741
- tex.setTextures = ( ...args ) => {
742
-
743
- this.setTextures( ...args );
744
-
745
- };
746
-
747
- const fsQuad = new Pass_js.FullScreenQuad( new three.MeshBasicMaterial() );
748
- this.fsQuad = fsQuad;
749
-
750
- }
751
-
752
- setTextures( renderer, width, height, textures ) {
753
-
754
- // save previous renderer state
755
- const prevRenderTarget = renderer.getRenderTarget();
756
- const prevToneMapping = renderer.toneMapping;
757
- const prevAlpha = renderer.getClearAlpha();
758
- renderer.getClearColor( prevColor );
759
-
760
- // resize the render target
761
- const depth = textures.length;
762
- this.setSize( width, height, depth );
763
- renderer.setClearColor( 0, 0 );
764
- renderer.toneMapping = three.NoToneMapping;
765
-
766
- // render each texture into each layer of the target
767
- const fsQuad = this.fsQuad;
768
- for ( let i = 0, l = depth; i < l; i ++ ) {
769
-
770
- const texture = textures[ i ];
771
- fsQuad.material.map = texture;
772
- fsQuad.material.transparent = true;
773
-
774
- renderer.setRenderTarget( this, i );
775
- fsQuad.render( renderer );
776
-
777
- }
778
-
779
- // reset the renderer
780
- fsQuad.material.map = null;
781
- renderer.setClearColor( prevColor, prevAlpha );
782
- renderer.setRenderTarget( prevRenderTarget );
783
- renderer.toneMapping = prevToneMapping;
784
-
785
- }
786
-
787
- dispose() {
788
-
789
- super.dispose();
790
- this.fsQuad.dispose();
791
-
792
- }
793
-
479
+ function mergeMeshes( meshes, options = {} ) {
480
+
481
+ options = { attributes: null, cloneGeometry: true, ...options };
482
+
483
+ const transformedGeometry = [];
484
+ const materialSet = new Set();
485
+ for ( let i = 0, l = meshes.length; i < l; i ++ ) {
486
+
487
+ // save any materials
488
+ const mesh = meshes[ i ];
489
+ if ( mesh.visible === false ) continue;
490
+
491
+ if ( Array.isArray( mesh.material ) ) {
492
+
493
+ mesh.material.forEach( m => materialSet.add( m ) );
494
+
495
+ } else {
496
+
497
+ materialSet.add( mesh.material );
498
+
499
+ }
500
+
501
+ }
502
+
503
+ const materials = Array.from( materialSet );
504
+ for ( let i = 0, l = meshes.length; i < l; i ++ ) {
505
+
506
+ // ensure the matrix world is up to date
507
+ const mesh = meshes[ i ];
508
+ if ( mesh.visible === false ) continue;
509
+
510
+ mesh.updateMatrixWorld();
511
+
512
+ // apply the matrix world to the geometry
513
+ const originalGeometry = meshes[ i ].geometry;
514
+ const geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
515
+ geometry.applyMatrix4( mesh.matrixWorld );
516
+
517
+ // ensure our geometry has common attributes
518
+ setCommonAttributes( geometry, {
519
+ attributes: options.attributes,
520
+ normalMapRequired: ! ! mesh.material.normalMap,
521
+ } );
522
+ trimToAttributes( geometry, options.attributes );
523
+
524
+ // create the material index attribute
525
+ const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
526
+ geometry.setAttribute( 'materialIndex', materialIndexAttribute );
527
+
528
+ transformedGeometry.push( geometry );
529
+
530
+ }
531
+
532
+ const textureSet = new Set();
533
+ materials.forEach( material => {
534
+
535
+ for ( const key in material ) {
536
+
537
+ const value = material[ key ];
538
+ if ( value && value.isTexture ) {
539
+
540
+ textureSet.add( value );
541
+
542
+ }
543
+
544
+ }
545
+
546
+ } );
547
+
548
+ const geometry = BufferGeometryUtils_js.mergeBufferGeometries( transformedGeometry, false );
549
+ const textures = Array.from( textureSet );
550
+ return { geometry, materials, textures };
551
+
794
552
  }
795
553
 
796
- class MaterialBase extends three.ShaderMaterial {
797
-
798
- constructor( shader ) {
799
-
800
- super( shader );
801
-
802
- for ( const key in this.uniforms ) {
803
-
804
- Object.defineProperty( this, key, {
805
-
806
- get() {
807
-
808
- return this.uniforms[ key ].value;
809
-
810
- },
811
-
812
- set( v ) {
813
-
814
- this.uniforms[ key ].value = v;
815
-
816
- }
817
-
818
- } );
819
-
820
- }
821
-
822
- }
823
-
824
- // sets the given named define value and sets "needsUpdate" to true if it's different
825
- setDefine( name, value = undefined ) {
826
-
827
- if ( value === undefined || value === null ) {
828
-
829
- if ( name in this.defines ) {
830
-
831
- delete this.defines[ name ];
832
- this.needsUpdate = true;
833
-
834
- }
835
-
836
- } else {
837
-
838
- if ( this.defines[ name ] !== value ) {
839
-
840
- this.defines[ name ] = value;
841
- this.needsUpdate = true;
842
-
843
- }
844
-
845
- }
846
-
847
- }
848
-
554
+ class PathTracingSceneGenerator {
555
+
556
+ prepScene( scene ) {
557
+
558
+ const meshes = [];
559
+ scene.traverse( c => {
560
+
561
+ if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
562
+
563
+ const generator = new threeMeshBvh.StaticGeometryGenerator( c );
564
+ generator.applyWorldTransforms = false;
565
+ const mesh = new three.Mesh(
566
+ generator.generate(),
567
+ c.material,
568
+ );
569
+ mesh.matrixWorld.copy( c.matrixWorld );
570
+ mesh.matrix.copy( c.matrixWorld );
571
+ mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
572
+ meshes.push( mesh );
573
+
574
+ } else if ( c.isMesh ) {
575
+
576
+ meshes.push( c );
577
+
578
+ }
579
+
580
+ } );
581
+
582
+ return mergeMeshes( meshes, {
583
+ attributes: [ 'position', 'normal', 'tangent', 'uv' ],
584
+ } );
585
+
586
+ }
587
+
588
+ generate( scene, options = {} ) {
589
+
590
+ const { materials, textures, geometry } = this.prepScene( scene );
591
+ const bvhOptions = { strategy: threeMeshBvh.SAH, ...options, maxLeafTris: 1 };
592
+ return {
593
+ scene,
594
+ materials,
595
+ textures,
596
+ bvh: new threeMeshBvh.MeshBVH( geometry, bvhOptions ),
597
+ };
598
+
599
+ }
600
+
849
601
  }
850
602
 
851
- const shaderMaterialStructs = /* glsl */ `
852
-
853
- struct Material {
854
-
855
- vec3 color;
856
- int map;
857
-
858
- float metalness;
859
- int metalnessMap;
860
-
861
- float roughness;
862
- int roughnessMap;
863
-
864
- float ior;
865
- float transmission;
866
- int transmissionMap;
867
-
868
- vec3 emissive;
869
- float emissiveIntensity;
870
- int emissiveMap;
871
-
872
- int normalMap;
873
- vec2 normalScale;
874
-
875
- float opacity;
876
- float alphaTest;
877
-
878
- };
879
-
880
- `;
603
+ class DynamicPathTracingSceneGenerator {
881
604
 
882
- const shaderGGXFunctions = /* glsl */`
883
- // The GGX functions provide sampling and distribution information for normals as output so
884
- // in order to get probability of scatter direction the half vector must be computed and provided.
885
- // [0] https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
886
- // [1] https://hal.archives-ouvertes.fr/hal-01509746/document
887
- // [2] http://jcgt.org/published/0007/04/01/
888
- // [4] http://jcgt.org/published/0003/02/03/
889
-
890
- // trowbridge-reitz === GGX === GTR
891
-
892
- vec3 ggxDirection( vec3 incidentDir, float roughnessX, float roughnessY, float random1, float random2 ) {
893
-
894
- // TODO: try GGXVNDF implementation from reference [2], here. Needs to update ggxDistribution
895
- // function below, as well
896
-
897
- // Implementation from reference [1]
898
- // stretch view
899
- vec3 V = normalize( vec3( roughnessX * incidentDir.x, roughnessY * incidentDir.y, incidentDir.z ) );
900
-
901
- // orthonormal basis
902
- vec3 T1 = ( V.z < 0.9999 ) ? normalize( cross( V, vec3( 0.0, 0.0, 1.0 ) ) ) : vec3( 1.0, 0.0, 0.0 );
903
- vec3 T2 = cross( T1, V );
904
-
905
- // sample point with polar coordinates (r, phi)
906
- float a = 1.0 / ( 1.0 + V.z );
907
- float r = sqrt( random1 );
908
- float phi = ( random2 < a ) ? random2 / a * PI : PI + ( random2 - a ) / ( 1.0 - a ) * PI;
909
- float P1 = r * cos( phi );
910
- float P2 = r * sin( phi ) * ( ( random2 < a ) ? 1.0 : V.z );
911
-
912
- // compute normal
913
- vec3 N = P1 * T1 + P2 * T2 + V * sqrt( max( 0.0, 1.0 - P1 * P1 - P2 * P2 ) );
914
-
915
- // unstretch
916
- N = normalize( vec3( roughnessX * N.x, roughnessY * N.y, max( 0.0, N.z ) ) );
917
-
918
- return N;
919
-
920
- }
921
-
922
- // Below are PDF and related functions for use in a Monte Carlo path tracer
923
- // as specified in Appendix B of the following paper
924
- // See equation (2) from reference [2]
925
- float ggxLamda( float theta, float roughness ) {
926
-
927
- float tanTheta = tan( theta );
928
- float tanTheta2 = tanTheta * tanTheta;
929
- float alpha2 = roughness * roughness;
930
-
931
- float numerator = - 1.0 + sqrt( 1.0 + alpha2 * tanTheta2 );
932
- return numerator / 2.0;
933
-
934
- }
935
-
936
- // See equation (2) from reference [2]
937
- float ggxShadowMaskG1( float theta, float roughness ) {
938
-
939
- return 1.0 / ( 1.0 + ggxLamda( theta, roughness ) );
940
-
941
- }
942
-
943
- // See equation (125) from reference [4]
944
- float ggxShadowMaskG2( vec3 wi, vec3 wo, float roughness ) {
945
-
946
- float incidentTheta = acos( wi.z );
947
- float scatterTheta = acos( wo.z );
948
- return 1.0 / ( 1.0 + ggxLamda( incidentTheta, roughness ) + ggxLamda( scatterTheta, roughness ) );
949
-
950
- }
951
-
952
- float ggxDistribution( vec3 halfVector, float roughness ) {
953
-
954
- // See equation (33) from reference [0]
955
- float a2 = roughness * roughness;
956
- float cosTheta = halfVector.z;
957
- float cosTheta4 = pow( cosTheta, 4.0 );
958
-
959
- if ( cosTheta == 0.0 ) return 0.0;
960
-
961
- float theta = acos( halfVector.z );
962
- float tanTheta = tan( theta );
963
- float tanTheta2 = pow( tanTheta, 2.0 );
964
-
965
- float denom = PI * cosTheta4 * pow( a2 + tanTheta2, 2.0 );
966
- return a2 / denom;
967
-
968
- // See equation (1) from reference [2]
969
- // const { x, y, z } = halfVector;
970
- // const a2 = roughness * roughness;
971
- // const mult = x * x / a2 + y * y / a2 + z * z;
972
- // const mult2 = mult * mult;
973
-
974
- // return 1.0 / Math.PI * a2 * mult2;
975
-
976
- }
977
-
978
- // See equation (3) from reference [2]
979
- float ggxPDF( vec3 wi, vec3 halfVector, float roughness ) {
980
-
981
- float incidentTheta = acos( wi.z );
982
- float D = ggxDistribution( halfVector, roughness );
983
- float G1 = ggxShadowMaskG1( incidentTheta, roughness );
984
-
985
- return D * G1 * max( 0.0, dot( wi, halfVector ) ) / wi.z;
986
-
987
- }
988
- `;
605
+ get initialized() {
989
606
 
990
- const shaderMaterialSampling = /* glsl */`
991
-
992
- struct SurfaceRec {
993
- vec3 normal;
994
- vec3 faceNormal;
995
- bool frontFace;
996
- float roughness;
997
- float filteredRoughness;
998
- float metalness;
999
- vec3 color;
1000
- vec3 emission;
1001
- float transmission;
1002
- float ior;
1003
- };
1004
-
1005
- struct SampleRec {
1006
- float pdf;
1007
- vec3 direction;
1008
- vec3 color;
1009
- };
1010
-
1011
- ${ shaderGGXFunctions }
1012
-
1013
- // diffuse
1014
- float diffusePDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
1015
-
1016
- // https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html#lightscattering/thescatteringpdf
1017
- float cosValue = wi.z;
1018
- return cosValue / PI;
1019
-
1020
- }
1021
-
1022
- vec3 diffuseDirection( vec3 wo, SurfaceRec surf ) {
1023
-
1024
- vec3 lightDirection = randDirection();
1025
- lightDirection.z += 1.0;
1026
- lightDirection = normalize( lightDirection );
1027
-
1028
- return lightDirection;
1029
-
1030
- }
1031
-
1032
- vec3 diffuseColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
1033
-
1034
- // TODO: scale by 1 - F here
1035
- // note on division by PI
1036
- // https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
1037
- float metalFactor = ( 1.0 - surf.metalness ) * wi.z / ( PI * PI );
1038
- float transmissionFactor = 1.0 - surf.transmission;
1039
- return surf.color * metalFactor * transmissionFactor;
1040
-
1041
- }
1042
-
1043
- // specular
1044
- float specularPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
1045
-
1046
- // See equation (17) in http://jcgt.org/published/0003/02/03/
1047
- float filteredRoughness = surf.filteredRoughness;
1048
- vec3 halfVector = getHalfVector( wi, wo );
1049
- return ggxPDF( wi, halfVector, filteredRoughness ) / ( 4.0 * dot( wi, halfVector ) );
1050
-
1051
- }
1052
-
1053
- vec3 specularDirection( vec3 wo, SurfaceRec surf ) {
1054
-
1055
- // sample ggx vndf distribution which gives a new normal
1056
- float filteredRoughness = surf.filteredRoughness;
1057
- vec3 halfVector = ggxDirection(
1058
- wo,
1059
- filteredRoughness,
1060
- filteredRoughness,
1061
- rand(),
1062
- rand()
1063
- );
1064
-
1065
- // apply to new ray by reflecting off the new normal
1066
- return - reflect( wo, halfVector );
1067
-
1068
- }
1069
-
1070
- vec3 specularColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
1071
-
1072
- // if roughness is set to 0 then D === NaN which results in black pixels
1073
- float metalness = surf.metalness;
1074
- float ior = surf.ior;
1075
- bool frontFace = surf.frontFace;
1076
- float filteredRoughness = surf.filteredRoughness;
1077
-
1078
- vec3 halfVector = getHalfVector( wo, wi );
1079
- float iorRatio = frontFace ? 1.0 / ior : ior;
1080
- float G = ggxShadowMaskG2( wi, wo, filteredRoughness );
1081
- float D = ggxDistribution( halfVector, filteredRoughness );
1082
-
1083
- float F = schlickFresnelFromIor( dot( wi, halfVector ), iorRatio );
1084
- float cosTheta = min( wo.z, 1.0 );
1085
- float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
1086
- bool cannotRefract = iorRatio * sinTheta > 1.0;
1087
- if ( cannotRefract ) {
1088
-
1089
- F = 1.0;
1090
-
1091
- }
1092
-
1093
- vec3 color = mix( vec3( 1.0 ), surf.color, metalness );
1094
- color = mix( color, vec3( 1.0 ), F );
1095
- color *= G * D / ( 4.0 * abs( wi.z * wo.z ) );
1096
- color *= mix( F, 1.0, metalness );
1097
- color *= wi.z; // scale the light by the direction the light is coming in from
1098
-
1099
- return color;
1100
-
1101
- }
1102
-
1103
- /*
1104
- // transmission
1105
- function transmissionPDF( wo, wi, material, surf ) {
1106
-
1107
- // See section 4.2 in https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
1108
-
1109
- const { roughness, ior } = material;
1110
- const { frontFace } = hit;
1111
- const ratio = frontFace ? ior : 1 / ior;
1112
- const minRoughness = Math.max( roughness, MIN_ROUGHNESS );
1113
-
1114
- halfVector.set( 0, 0, 0 ).addScaledVector( wi, ratio ).addScaledVector( wo, 1.0 ).normalize().multiplyScalar( - 1 );
1115
-
1116
- const denom = Math.pow( ratio * halfVector.dot( wi ) + 1.0 * halfVector.dot( wo ), 2.0 );
1117
- return ggxPDF( wo, halfVector, minRoughness ) / denom;
1118
-
1119
- }
1120
-
1121
- function transmissionDirection( wo, hit, material, lightDirection ) {
1122
-
1123
- const { roughness, ior } = material;
1124
- const { frontFace } = hit;
1125
- const ratio = frontFace ? 1 / ior : ior;
1126
- const minRoughness = Math.max( roughness, MIN_ROUGHNESS );
1127
-
1128
- // sample ggx vndf distribution which gives a new normal
1129
- ggxDirection(
1130
- wo,
1131
- minRoughness,
1132
- minRoughness,
1133
- Math.random(),
1134
- Math.random(),
1135
- halfVector,
1136
- );
1137
-
1138
- // apply to new ray by reflecting off the new normal
1139
- tempDir.copy( wo ).multiplyScalar( - 1 );
1140
- refract( tempDir, halfVector, ratio, lightDirection );
1141
-
1142
- }
1143
-
1144
- function transmissionColor( wo, wi, material, hit, colorTarget ) {
1145
-
1146
- const { metalness, transmission } = material;
1147
- colorTarget
1148
- .copy( material.color )
1149
- .multiplyScalar( ( 1.0 - metalness ) * wo.z )
1150
- .multiplyScalar( transmission );
1151
-
1152
- }
1153
- */
1154
-
1155
- // TODO: This is just using a basic cosine-weighted specular distribution with an
1156
- // incorrect PDF value at the moment. Update it to correctly use a GGX distribution
1157
- float transmissionPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
1158
-
1159
- float ior = surf.ior;
1160
- bool frontFace = surf.frontFace;
1161
-
1162
- float ratio = frontFace ? 1.0 / ior : ior;
1163
- float cosTheta = min( wo.z, 1.0 );
1164
- float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
1165
- float reflectance = schlickFresnelFromIor( cosTheta, ratio );
1166
- bool cannotRefract = ratio * sinTheta > 1.0;
1167
- if ( cannotRefract ) {
1168
-
1169
- return 0.0;
1170
-
1171
- }
1172
-
1173
- return 1.0 / ( 1.0 - reflectance );
1174
-
1175
- }
1176
-
1177
- vec3 transmissionDirection( vec3 wo, SurfaceRec surf ) {
1178
-
1179
- float roughness = surf.roughness;
1180
- float ior = surf.ior;
1181
- bool frontFace = surf.frontFace;
1182
- float ratio = frontFace ? 1.0 / ior : ior;
1183
-
1184
- vec3 lightDirection = refract( - wo, vec3( 0.0, 0.0, 1.0 ), ratio );
1185
- lightDirection += randDirection() * roughness;
1186
- return normalize( lightDirection );
1187
-
1188
- }
1189
-
1190
- vec3 transmissionColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
1191
-
1192
- float metalness = surf.metalness;
1193
- float transmission = surf.transmission;
1194
-
1195
- vec3 color = surf.color;
1196
- color *= ( 1.0 - metalness );
1197
- color *= transmission;
1198
-
1199
- return color;
1200
-
1201
- }
1202
-
1203
- float bsdfPdf( vec3 wo, vec3 wi, SurfaceRec surf ) {
1204
-
1205
- float ior = surf.ior;
1206
- float metalness = surf.metalness;
1207
- float transmission = surf.transmission;
1208
- bool frontFace = surf.frontFace;
1209
-
1210
- float ratio = frontFace ? 1.0 / ior : ior;
1211
- float cosTheta = min( wo.z, 1.0 );
1212
- float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
1213
- float reflectance = schlickFresnelFromIor( cosTheta, ratio );
1214
- bool cannotRefract = ratio * sinTheta > 1.0;
1215
- if ( cannotRefract ) {
1216
-
1217
- reflectance = 1.0;
1218
-
1219
- }
1220
-
1221
- float spdf = 0.0;
1222
- float dpdf = 0.0;
1223
- float tpdf = 0.0;
1224
-
1225
- if ( wi.z < 0.0 ) {
1226
-
1227
- tpdf = transmissionPDF( wo, wi, surf );
1228
-
1229
- } else {
1230
-
1231
- spdf = specularPDF( wo, wi, surf );
1232
- dpdf = diffusePDF( wo, wi, surf );
1233
-
1234
- }
1235
-
1236
- float transSpecularProb = mix( reflectance, 1.0, metalness );
1237
- float diffSpecularProb = 0.5 + 0.5 * metalness;
1238
- float pdf =
1239
- spdf * transmission * transSpecularProb
1240
- + tpdf * transmission * ( 1.0 - transSpecularProb )
1241
- + spdf * ( 1.0 - transmission ) * diffSpecularProb
1242
- + dpdf * ( 1.0 - transmission ) * ( 1.0 - diffSpecularProb );
1243
-
1244
- return pdf;
1245
-
1246
- }
1247
-
1248
- vec3 bsdfColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
1249
-
1250
- vec3 color = vec3( 0.0 );
1251
- if ( wi.z < 0.0 ) {
1252
-
1253
- color = transmissionColor( wo, wi, surf );
1254
-
1255
- } else {
1256
-
1257
- color = diffuseColor( wo, wi, surf );
1258
- color *= 1.0 - surf.transmission;
1259
-
1260
- color += specularColor( wo, wi, surf );
1261
-
1262
- }
1263
-
1264
- return color;
1265
-
1266
- }
1267
-
1268
- SampleRec bsdfSample( vec3 wo, SurfaceRec surf ) {
1269
-
1270
- float ior = surf.ior;
1271
- float metalness = surf.metalness;
1272
- float transmission = surf.transmission;
1273
- bool frontFace = surf.frontFace;
1274
-
1275
- float ratio = frontFace ? 1.0 / ior : ior;
1276
- float cosTheta = min( wo.z, 1.0 );
1277
- float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
1278
- float reflectance = schlickFresnelFromIor( cosTheta, ratio );
1279
- bool cannotRefract = ratio * sinTheta > 1.0;
1280
- if ( cannotRefract ) {
1281
-
1282
- reflectance = 1.0;
1283
-
1284
- }
1285
-
1286
- SampleRec result;
1287
- if ( rand() < transmission ) {
1288
-
1289
- float specularProb = mix( reflectance, 1.0, metalness );
1290
- if ( rand() < specularProb ) {
1291
-
1292
- result.direction = specularDirection( wo, surf );
1293
-
1294
- } else {
1295
-
1296
- result.direction = transmissionDirection( wo, surf );
1297
-
1298
- }
1299
-
1300
- } else {
1301
-
1302
- float specularProb = 0.5 + 0.5 * metalness;
1303
- if ( rand() < specularProb ) {
1304
-
1305
- result.direction = specularDirection( wo, surf );
1306
-
1307
- } else {
1308
-
1309
- result.direction = diffuseDirection( wo, surf );
1310
-
1311
- }
1312
-
1313
- }
1314
-
1315
- result.pdf = bsdfPdf( wo, result.direction, surf );
1316
- result.color = bsdfColor( wo, result.direction, surf );
1317
- return result;
1318
-
1319
- }
1320
- `;
607
+ return Boolean( this.bvh );
1321
608
 
1322
- const shaderUtils = /* glsl */`
1323
-
1324
- // https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
1325
- float schlickFresnel( float cosine, float f0 ) {
1326
-
1327
- return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
1328
-
1329
- }
1330
-
1331
- // https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
1332
- float schlickFresnelFromIor( float cosine, float iorRatio ) {
1333
-
1334
- // Schlick approximation
1335
- float r_0 = pow( ( 1.0 - iorRatio ) / ( 1.0 + iorRatio ), 2.0 );
1336
- return schlickFresnel( cosine, r_0 );
1337
-
1338
- }
1339
-
1340
- // forms a basis with the normal vector as Z
1341
- mat3 getBasisFromNormal( vec3 normal ) {
1342
-
1343
- vec3 other;
1344
- if ( abs( normal.x ) > 0.5 ) {
1345
-
1346
- other = vec3( 0.0, 1.0, 0.0 );
1347
-
1348
- } else {
1349
-
1350
- other = vec3( 1.0, 0.0, 0.0 );
1351
-
1352
- }
1353
-
1354
- vec3 ortho = normalize( cross( normal, other ) );
1355
- vec3 ortho2 = normalize( cross( normal, ortho ) );
1356
- return mat3( ortho2, ortho, normal );
1357
-
1358
- }
1359
-
1360
- vec3 getHalfVector( vec3 a, vec3 b ) {
1361
-
1362
- return normalize( a + b );
1363
-
1364
- }
1365
-
1366
- // The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
1367
- // is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
1368
- // we find a ray like that we ignore it to avoid artifacts.
1369
- // This function returns if the direction is on the same side of both planes.
1370
- bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
1371
-
1372
- bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
1373
- bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
1374
- return aboveSurfaceNormal == aboveGeometryNormal;
1375
-
1376
- }
1377
-
1378
- vec3 getHemisphereSample( vec3 n, vec2 uv ) {
1379
-
1380
- // https://www.rorydriscoll.com/2009/01/07/better-sampling/
1381
- // https://graphics.pixar.com/library/OrthonormalB/paper.pdf
1382
- float sign = n.z == 0.0 ? 1.0 : sign( n.z );
1383
- float a = - 1.0 / ( sign + n.z );
1384
- float b = n.x * n.y * a;
1385
- vec3 b1 = vec3( 1.0 + sign * n.x * n.x * a, sign * b, - sign * n.x );
1386
- vec3 b2 = vec3( b, sign + n.y * n.y * a, - n.y );
1387
-
1388
- float r = sqrt( uv.x );
1389
- float theta = 2.0 * PI * uv.y;
1390
- float x = r * cos( theta );
1391
- float y = r * sin( theta );
1392
- return x * b1 + y * b2 + sqrt( 1.0 - uv.x ) * n;
1393
-
1394
- }
1395
-
1396
- // https://www.shadertoy.com/view/wltcRS
1397
- uvec4 s0;
1398
-
1399
- void rng_initialize(vec2 p, int frame) {
1400
-
1401
- // white noise seed
1402
- s0 = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
1403
-
1404
- }
1405
-
1406
- // https://www.pcg-random.org/
1407
- void pcg4d( inout uvec4 v ) {
1408
-
1409
- v = v * 1664525u + 1013904223u;
1410
- v.x += v.y * v.w;
1411
- v.y += v.z * v.x;
1412
- v.z += v.x * v.y;
1413
- v.w += v.y * v.z;
1414
- v = v ^ ( v >> 16u );
1415
- v.x += v.y*v.w;
1416
- v.y += v.z*v.x;
1417
- v.z += v.x*v.y;
1418
- v.w += v.y*v.z;
1419
-
1420
- }
1421
-
1422
- float rand() {
1423
-
1424
- pcg4d(s0);
1425
- return float( s0.x ) / float( 0xffffffffu );
1426
-
1427
- }
1428
-
1429
- vec2 rand2() {
1430
-
1431
- pcg4d( s0 );
1432
- return vec2( s0.xy ) / float(0xffffffffu);
1433
-
1434
- }
1435
-
1436
- vec3 rand3() {
1437
-
1438
- pcg4d(s0);
1439
- return vec3( s0.xyz ) / float( 0xffffffffu );
1440
-
1441
- }
1442
-
1443
- vec4 rand4() {
1444
-
1445
- pcg4d(s0);
1446
- return vec4(s0)/float(0xffffffffu);
1447
-
1448
- }
1449
-
1450
- // https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js#L724
1451
- vec3 randDirection() {
1452
-
1453
- vec2 r = rand2();
1454
- float u = ( r.x - 0.5 ) * 2.0;
1455
- float t = r.y * PI * 2.0;
1456
- float f = sqrt( 1.0 - u * u );
1457
-
1458
- return vec3( f * cos( t ), f * sin( t ), u );
1459
-
1460
- }
1461
- `;
609
+ }
610
+
611
+ constructor( scene ) {
612
+
613
+ this.scene = scene;
614
+ this.bvh = null;
615
+ this.geometry = new three.BufferGeometry();
616
+ this.materials = null;
617
+ this.textures = null;
618
+ this.staticGeometryGenerator = new threeMeshBvh.StaticGeometryGenerator( scene );
619
+
620
+ }
621
+
622
+ reset() {
623
+
624
+ this.bvh = null;
625
+ this.geometry.dispose();
626
+ this.geometry = new three.BufferGeometry();
627
+ this.materials = null;
628
+ this.textures = null;
629
+ this.staticGeometryGenerator = new threeMeshBvh.StaticGeometryGenerator( this.scene );
630
+
631
+ }
632
+
633
+ dispose() {}
634
+
635
+ generate() {
636
+
637
+ const { scene, staticGeometryGenerator, geometry } = this;
638
+ if ( this.bvh === null ) {
639
+
640
+ const attributes = [ 'position', 'normal', 'tangent', 'uv' ];
641
+ scene.traverse( c => {
642
+
643
+ if ( c.isMesh ) {
644
+
645
+ const normalMapRequired = ! ! c.material.normalMap;
646
+ setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
647
+
648
+ }
649
+
650
+ } );
651
+
652
+ const textureSet = new Set();
653
+ const materials = staticGeometryGenerator.getMaterials();
654
+ materials.forEach( material => {
655
+
656
+ for ( const key in material ) {
657
+
658
+ const value = material[ key ];
659
+ if ( value && value.isTexture ) {
660
+
661
+ textureSet.add( value );
662
+
663
+ }
664
+
665
+ }
666
+
667
+ } );
668
+
669
+ staticGeometryGenerator.attributes = attributes;
670
+ staticGeometryGenerator.generate( geometry );
671
+
672
+ const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, materials, materials );
673
+ geometry.setAttribute( 'materialIndex', materialIndexAttribute );
674
+ geometry.clearGroups();
675
+
676
+ this.bvh = new threeMeshBvh.MeshBVH( geometry );
677
+ this.materials = materials;
678
+ this.textures = Array.from( textureSet );
679
+
680
+ return {
681
+ bvh: this.bvh,
682
+ materials: this.materials,
683
+ textures: this.textures,
684
+ scene,
685
+ };
686
+
687
+ } else {
688
+
689
+ const { bvh } = this;
690
+ staticGeometryGenerator.generate( geometry );
691
+ bvh.refit();
692
+ return {
693
+ bvh: this.bvh,
694
+ materials: this.materials,
695
+ textures: this.textures,
696
+ scene,
697
+ };
698
+
699
+ }
700
+
701
+ }
702
+
703
+
704
+ }
705
+
706
+ // https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
707
+
708
+ function isTypedArray( arr ) {
709
+
710
+ return arr.buffer instanceof ArrayBuffer && 'BYTES_PER_ELEMENT' in arr;
711
+
712
+ }
713
+
714
+ class MaterialReducer {
715
+
716
+ constructor() {
717
+
718
+ const ignoreKeys = new Set();
719
+ ignoreKeys.add( 'uuid' );
720
+
721
+ this.ignoreKeys = ignoreKeys;
722
+ this.shareTextures = true;
723
+ this.textures = [];
724
+ this.materials = [];
725
+
726
+ }
727
+
728
+ areEqual( objectA, objectB ) {
729
+
730
+ const keySet = new Set();
731
+ const traverseSet = new Set();
732
+ const ignoreKeys = this.ignoreKeys;
733
+
734
+ const traverse = ( a, b ) => {
735
+
736
+ if ( a === b ) {
737
+
738
+ return true;
739
+
740
+ }
741
+
742
+ if ( a && b && a instanceof Object && b instanceof Object ) {
743
+
744
+ if ( traverseSet.has( a ) || traverseSet.has( b ) ) {
745
+
746
+ throw new Error( 'MaterialReducer: Material is recursive.' );
747
+
748
+ }
749
+
750
+ const aIsElement = a instanceof Element;
751
+ const bIsElement = b instanceof Element;
752
+ if ( aIsElement || bIsElement ) {
753
+
754
+ if ( aIsElement !== bIsElement || ! ( a instanceof Image ) || ! ( b instanceof Image ) ) {
755
+
756
+ return false;
757
+
758
+ }
759
+
760
+ return a.src === b.src;
761
+
762
+ }
763
+
764
+ const aIsImageBitmap = a instanceof ImageBitmap;
765
+ const bIsImageBitmap = b instanceof ImageBitmap;
766
+ if ( aIsImageBitmap || bIsImageBitmap ) {
767
+
768
+ return false;
769
+
770
+ }
771
+
772
+ if ( a.equals ) {
773
+
774
+ return a.equals( b );
775
+
776
+ }
777
+
778
+ const aIsTypedArray = isTypedArray( a );
779
+ const bIsTypedArray = isTypedArray( b );
780
+ if ( aIsTypedArray || bIsTypedArray ) {
781
+
782
+ if ( aIsTypedArray !== bIsTypedArray || a.constructor !== b.constructor || a.length !== b.length ) {
783
+
784
+ return false;
785
+
786
+ }
787
+
788
+ for ( let i = 0, l = a.length; i < l; i ++ ) {
789
+
790
+ if ( a[ i ] !== b[ i ] ) return false;
791
+
792
+ }
793
+
794
+ return true;
795
+
796
+ }
797
+
798
+ traverseSet.add( a );
799
+ traverseSet.add( b );
800
+
801
+ keySet.clear();
802
+ for ( const key in a ) {
803
+
804
+ if ( ! a.hasOwnProperty( key ) || a[ key ] instanceof Function || ignoreKeys.has( key ) ) {
805
+
806
+ continue;
807
+
808
+ }
809
+
810
+ keySet.add( key );
811
+
812
+ }
813
+
814
+ for ( const key in b ) {
815
+
816
+ if ( ! b.hasOwnProperty( key ) || b[ key ] instanceof Function || ignoreKeys.has( key ) ) {
817
+
818
+ continue;
819
+
820
+ }
821
+
822
+ keySet.add( key );
823
+
824
+ }
825
+
826
+ const keys = Array.from( keySet.values() );
827
+ let result = true;
828
+ for ( const i in keys ) {
829
+
830
+ const key = keys[ i ];
831
+ if ( ignoreKeys.has( key ) ) {
832
+
833
+ continue;
834
+
835
+ }
836
+
837
+ result = traverse( a[ key ], b[ key ] );
838
+ if ( ! result ) {
839
+
840
+ break;
841
+
842
+ }
843
+
844
+ }
845
+
846
+ traverseSet.delete( a );
847
+ traverseSet.delete( b );
848
+ return result;
849
+
850
+ }
851
+
852
+ return false;
853
+
854
+ };
855
+
856
+ return traverse( objectA, objectB );
857
+
858
+ }
859
+
860
+ process( object ) {
861
+
862
+ const { textures, materials } = this;
863
+ let replaced = 0;
864
+
865
+ const processMaterial = material => {
866
+
867
+ // Check if another material matches this one
868
+ let foundMaterial = null;
869
+ for ( const i in materials ) {
870
+
871
+ const otherMaterial = materials[ i ];
872
+ if ( this.areEqual( material, otherMaterial ) ) {
873
+
874
+ foundMaterial = otherMaterial;
875
+
876
+ }
877
+
878
+ }
879
+
880
+ if ( foundMaterial ) {
881
+
882
+ replaced ++;
883
+ return foundMaterial;
884
+
885
+ } else {
886
+
887
+ materials.push( material );
888
+
889
+ if ( this.shareTextures ) {
890
+
891
+ // See if there's another texture that matches the ones on this material
892
+ for ( const key in material ) {
893
+
894
+ if ( ! material.hasOwnProperty( key ) ) continue;
895
+
896
+ const value = material[ key ];
897
+ if ( value && value.isTexture && value.image instanceof Image ) {
898
+
899
+ let foundTexture = null;
900
+ for ( const i in textures ) {
901
+
902
+ const texture = textures[ i ];
903
+ if ( this.areEqual( texture, value ) ) {
904
+
905
+ foundTexture = texture;
906
+ break;
907
+
908
+ }
909
+
910
+ }
911
+
912
+ if ( foundTexture ) {
913
+
914
+ material[ key ] = foundTexture;
915
+
916
+ } else {
917
+
918
+ textures.push( value );
919
+
920
+ }
921
+
922
+ }
923
+
924
+ }
925
+
926
+ }
927
+
928
+ return material;
929
+
930
+ }
931
+
932
+ };
933
+
934
+ object.traverse( c => {
935
+
936
+ if ( c.isMesh && c.material ) {
937
+
938
+ const material = c.material;
939
+ if ( Array.isArray( material ) ) {
940
+
941
+ for ( let i = 0; i < material.length; i ++ ) {
942
+
943
+ material[ i ] = processMaterial( material[ i ] );
944
+
945
+ }
946
+
947
+ } else {
948
+
949
+ c.material = processMaterial( material );
950
+
951
+ }
952
+
953
+ }
954
+
955
+ } );
956
+
957
+ return { replaced, retained: materials.length };
958
+
959
+ }
960
+
961
+ }
962
+
963
+ class PhysicalCamera extends three.PerspectiveCamera {
964
+
965
+ set bokehSize( size ) {
966
+
967
+ this.fStop = this.getFocalLength() / size;
968
+
969
+ }
970
+
971
+ get bokehSize() {
972
+
973
+ return this.getFocalLength() / this.fStop;
974
+
975
+ }
976
+
977
+ constructor( ...args ) {
978
+
979
+ super( ...args );
980
+ this.fStop = 1.4;
981
+ this.apertureBlades = 0;
982
+ this.apertureRotation = 0;
983
+ this.focusDistance = 25;
984
+ this.anamorphicRatio = 1;
985
+
986
+ }
987
+
988
+ }
989
+
990
+ const MATERIAL_PIXELS = 19;
991
+ const MATERIAL_STRIDE = MATERIAL_PIXELS * 4;
992
+
993
+ class MaterialsTexture extends three.DataTexture {
994
+
995
+ constructor() {
996
+
997
+ super( new Float32Array( 4 ), 1, 1 );
998
+
999
+ this.format = three.RGBAFormat;
1000
+ this.type = three.FloatType;
1001
+ this.wrapS = three.ClampToEdgeWrapping;
1002
+ this.wrapT = three.ClampToEdgeWrapping;
1003
+ this.generateMipmaps = false;
1004
+
1005
+ }
1006
+
1007
+ setCastShadow( materialIndex, cast ) {
1008
+
1009
+ // invert the shadow value so we default to "true" when initializing a material
1010
+ const array = this.image.data;
1011
+ const index = materialIndex * MATERIAL_STRIDE + 6 * 4 + 0;
1012
+ array[ index ] = ! cast ? 1 : 0;
1013
+
1014
+ }
1015
+
1016
+ getCastShadow( materialIndex ) {
1017
+
1018
+ const array = this.image.data;
1019
+ const index = materialIndex * MATERIAL_STRIDE + 6 * 4 + 0;
1020
+ return ! Boolean( array[ index ] );
1021
+
1022
+ }
1023
+
1024
+ setSide( materialIndex, side ) {
1025
+
1026
+ const array = this.image.data;
1027
+ const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 2;
1028
+ switch ( side ) {
1029
+
1030
+ case three.FrontSide:
1031
+ array[ index ] = 1;
1032
+ break;
1033
+ case three.BackSide:
1034
+ array[ index ] = - 1;
1035
+ break;
1036
+ case three.DoubleSide:
1037
+ array[ index ] = 0;
1038
+ break;
1039
+
1040
+ }
1041
+
1042
+ }
1043
+
1044
+ getSide( materialIndex ) {
1045
+
1046
+ const array = this.image.data;
1047
+ const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 2;
1048
+ switch ( array[ index ] ) {
1049
+
1050
+ case 0:
1051
+ return three.DoubleSide;
1052
+ case 1:
1053
+ return three.FrontSide;
1054
+ case - 1:
1055
+ return three.BackSide;
1056
+
1057
+ }
1058
+
1059
+ return 0;
1060
+
1061
+ }
1062
+
1063
+ setMatte( materialIndex, matte ) {
1064
+
1065
+ const array = this.image.data;
1066
+ const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 3;
1067
+ array[ index ] = matte ? 1 : 0;
1068
+
1069
+ }
1070
+
1071
+ getMatte( materialIndex ) {
1072
+
1073
+ const array = this.image.data;
1074
+ const index = materialIndex * MATERIAL_STRIDE + 5 * 4 + 3;
1075
+ return Boolean( array[ index ] );
1076
+
1077
+ }
1078
+
1079
+ updateFrom( materials, textures ) {
1080
+
1081
+ function getTexture( material, key, def = - 1 ) {
1082
+
1083
+ return key in material ? textures.indexOf( material[ key ] ) : def;
1084
+
1085
+ }
1086
+
1087
+ function getField( material, key, def ) {
1088
+
1089
+ return key in material ? material[ key ] : def;
1090
+
1091
+ }
1092
+
1093
+ /**
1094
+ *
1095
+ * @param {Object} material
1096
+ * @param {string} textureKey
1097
+ * @param {Float32Array} array
1098
+ * @param {number} offset
1099
+ * @returns {8} number of floats occupied by texture transform matrix
1100
+ */
1101
+ function writeTextureMatrixToArray( material, textureKey, array, offset ) {
1102
+
1103
+ // check if texture exists
1104
+ if ( material[ textureKey ] && material[ textureKey ].isTexture ) {
1105
+
1106
+ const elements = material[ textureKey ].matrix.elements;
1107
+
1108
+ let i = 0;
1109
+
1110
+ // first row
1111
+ array[ offset + i ++ ] = elements[ 0 ];
1112
+ array[ offset + i ++ ] = elements[ 3 ];
1113
+ array[ offset + i ++ ] = elements[ 6 ];
1114
+ i ++;
1115
+
1116
+ // second row
1117
+ array[ offset + i ++ ] = elements[ 1 ];
1118
+ array[ offset + i ++ ] = elements[ 4 ];
1119
+ array[ offset + i ++ ] = elements[ 7 ];
1120
+ i ++;
1121
+
1122
+ }
1123
+
1124
+ return 8;
1125
+
1126
+ }
1127
+
1128
+ let index = 0;
1129
+ const pixelCount = materials.length * MATERIAL_PIXELS;
1130
+ const dimension = Math.ceil( Math.sqrt( pixelCount ) );
1131
+
1132
+ if ( this.image.width !== dimension ) {
1133
+
1134
+ this.dispose();
1135
+
1136
+ this.image.data = new Float32Array( dimension * dimension * 4 );
1137
+ this.image.width = dimension;
1138
+ this.image.height = dimension;
1139
+
1140
+ }
1141
+
1142
+ const floatArray = this.image.data;
1143
+
1144
+ // on some devices (Google Pixel 6) the "floatBitsToInt" function does not work correctly so we
1145
+ // can't encode texture ids that way.
1146
+ // const intArray = new Int32Array( floatArray.buffer );
1147
+
1148
+ for ( let i = 0, l = materials.length; i < l; i ++ ) {
1149
+
1150
+ const m = materials[ i ];
1151
+
1152
+ // color
1153
+ floatArray[ index ++ ] = m.color.r;
1154
+ floatArray[ index ++ ] = m.color.g;
1155
+ floatArray[ index ++ ] = m.color.b;
1156
+ floatArray[ index ++ ] = getTexture( m, 'map' );
1157
+
1158
+ // metalness & roughness
1159
+ floatArray[ index ++ ] = getField( m, 'metalness', 0.0 );
1160
+ floatArray[ index ++ ] = textures.indexOf( m.metalnessMap );
1161
+ floatArray[ index ++ ] = getField( m, 'roughness', 0.0 );
1162
+ floatArray[ index ++ ] = textures.indexOf( m.roughnessMap );
1163
+
1164
+ // transmission & emissiveIntensity
1165
+ floatArray[ index ++ ] = getField( m, 'ior', 1.0 );
1166
+ floatArray[ index ++ ] = getField( m, 'transmission', 0.0 );
1167
+ floatArray[ index ++ ] = getTexture( m, 'transmissionMap' );
1168
+ floatArray[ index ++ ] = getField( m, 'emissiveIntensity', 0.0 );
1169
+
1170
+ // emission
1171
+ if ( 'emissive' in m ) {
1172
+
1173
+ floatArray[ index ++ ] = m.emissive.r;
1174
+ floatArray[ index ++ ] = m.emissive.g;
1175
+ floatArray[ index ++ ] = m.emissive.b;
1176
+
1177
+ } else {
1178
+
1179
+ floatArray[ index ++ ] = 0.0;
1180
+ floatArray[ index ++ ] = 0.0;
1181
+ floatArray[ index ++ ] = 0.0;
1182
+
1183
+ }
1184
+
1185
+ floatArray[ index ++ ] = getTexture( m, 'emissiveMap' );
1186
+
1187
+ // normals
1188
+ floatArray[ index ++ ] = getTexture( m, 'normalMap' );
1189
+ if ( 'normalScale' in m ) {
1190
+
1191
+ floatArray[ index ++ ] = m.normalScale.x;
1192
+ floatArray[ index ++ ] = m.normalScale.y;
1193
+
1194
+ } else {
1195
+
1196
+ floatArray[ index ++ ] = 1;
1197
+ floatArray[ index ++ ] = 1;
1198
+
1199
+ }
1200
+
1201
+ floatArray[ index ++ ] = getTexture( m, 'alphaMap' );
1202
+
1203
+ // side & matte
1204
+ floatArray[ index ++ ] = m.opacity;
1205
+ floatArray[ index ++ ] = m.alphaTest;
1206
+ index ++; // side
1207
+ index ++; // matte
1208
+
1209
+ index ++; // shadow
1210
+ index ++;
1211
+ index ++;
1212
+ index ++;
1213
+
1214
+ // map transform
1215
+ index += writeTextureMatrixToArray( m, 'map', floatArray, index );
1216
+
1217
+ // metalnessMap transform
1218
+ index += writeTextureMatrixToArray( m, 'metalnessMap', floatArray, index );
1219
+
1220
+ // roughnessMap transform
1221
+ index += writeTextureMatrixToArray( m, 'roughnessMap', floatArray, index );
1222
+
1223
+ // transmissionMap transform
1224
+ index += writeTextureMatrixToArray( m, 'transmissionMap', floatArray, index );
1225
+
1226
+ // emissiveMap transform
1227
+ index += writeTextureMatrixToArray( m, 'emissiveMap', floatArray, index );
1228
+
1229
+ // normalMap transform
1230
+ index += writeTextureMatrixToArray( m, 'normalMap', floatArray, index );
1231
+
1232
+ }
1233
+
1234
+ this.needsUpdate = true;
1235
+
1236
+ }
1237
+
1238
+ }
1239
+
1240
+ const prevColor = new three.Color();
1241
+ class RenderTarget2DArray extends three.WebGLArrayRenderTarget {
1242
+
1243
+ constructor( ...args ) {
1244
+
1245
+ super( ...args );
1246
+
1247
+ const tex = this.texture;
1248
+ tex.format = three.RGBAFormat;
1249
+ tex.type = three.UnsignedByteType;
1250
+ tex.minFilter = three.LinearFilter;
1251
+ tex.magFilter = three.LinearFilter;
1252
+ tex.wrapS = three.RepeatWrapping;
1253
+ tex.wrapT = three.RepeatWrapping;
1254
+ tex.setTextures = ( ...args ) => {
1255
+
1256
+ this.setTextures( ...args );
1257
+
1258
+ };
1259
+
1260
+ const fsQuad = new Pass_js.FullScreenQuad( new three.MeshBasicMaterial() );
1261
+ this.fsQuad = fsQuad;
1262
+
1263
+ }
1264
+
1265
+ setTextures( renderer, width, height, textures ) {
1266
+
1267
+ // save previous renderer state
1268
+ const prevRenderTarget = renderer.getRenderTarget();
1269
+ const prevToneMapping = renderer.toneMapping;
1270
+ const prevAlpha = renderer.getClearAlpha();
1271
+ renderer.getClearColor( prevColor );
1272
+
1273
+ // resize the render target and ensure we don't have an empty texture
1274
+ // render target depth must be >= 1 to avoid unbound texture error on android devices
1275
+ const depth = textures.length || 1;
1276
+ this.setSize( width, height, depth );
1277
+ renderer.setClearColor( 0, 0 );
1278
+ renderer.toneMapping = three.NoToneMapping;
1279
+
1280
+ // render each texture into each layer of the target
1281
+ const fsQuad = this.fsQuad;
1282
+ for ( let i = 0, l = depth; i < l; i ++ ) {
1283
+
1284
+ const texture = textures[ i ];
1285
+ if ( texture ) {
1286
+
1287
+ // revert to default texture transform before rendering
1288
+ texture.matrixAutoUpdate = false;
1289
+ texture.matrix.identity();
1290
+
1291
+ fsQuad.material.map = texture;
1292
+ fsQuad.material.transparent = true;
1293
+
1294
+ renderer.setRenderTarget( this, i );
1295
+ fsQuad.render( renderer );
1296
+
1297
+ // restore custom texture transform
1298
+ texture.updateMatrix();
1299
+ texture.matrixAutoUpdate = true;
1300
+
1301
+ }
1302
+
1303
+ }
1304
+
1305
+ // reset the renderer
1306
+ fsQuad.material.map = null;
1307
+ renderer.setClearColor( prevColor, prevAlpha );
1308
+ renderer.setRenderTarget( prevRenderTarget );
1309
+ renderer.toneMapping = prevToneMapping;
1310
+
1311
+ }
1312
+
1313
+ dispose() {
1314
+
1315
+ super.dispose();
1316
+ this.fsQuad.dispose();
1317
+
1318
+ }
1319
+
1320
+ }
1321
+
1322
+ function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
1323
+
1324
+ let lower = 0;
1325
+ let upper = count;
1326
+ while ( lower < upper ) {
1327
+
1328
+ const mid = ~ ~ ( 0.5 * upper + 0.5 * lower );
1329
+
1330
+
1331
+ // check if the middle array value is above or below the target and shift
1332
+ // which half of the array we're looking at
1333
+ if ( array[ offset + mid ] < targetValue ) {
1334
+
1335
+ lower = mid + 1;
1336
+
1337
+ } else {
1338
+
1339
+ upper = mid;
1340
+
1341
+ }
1342
+
1343
+ }
1344
+
1345
+ return lower;
1346
+
1347
+ }
1348
+
1349
+ function colorToLuminance( r, g, b ) {
1350
+
1351
+ // https://en.wikipedia.org/wiki/Relative_luminance
1352
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
1353
+
1354
+ }
1355
+
1356
+ // ensures the data is all floating point values and flipY is false
1357
+ function preprocessEnvMap( envMap ) {
1358
+
1359
+ const map = envMap.clone();
1360
+ map.source = new three.Source( { ...map.image } );
1361
+ const { width, height, data } = map.image;
1362
+
1363
+ // TODO: is there a simple way to avoid cloning and adjusting the env map data here?
1364
+ // convert the data from half float uint 16 arrays to float arrays for cdf computation
1365
+ let newData = data;
1366
+ if ( map.type === three.HalfFloatType ) {
1367
+
1368
+ newData = new Float32Array( data.length );
1369
+ for ( const i in data ) {
1370
+
1371
+ newData[ i ] = three.DataUtils.fromHalfFloat( data[ i ] );
1372
+
1373
+ }
1374
+
1375
+ map.image.data = newData;
1376
+ map.type = three.FloatType;
1377
+
1378
+ }
1379
+
1380
+ // remove any y flipping for cdf computation
1381
+ if ( map.flipY ) {
1382
+
1383
+ const ogData = newData;
1384
+ newData = newData.slice();
1385
+ for ( let y = 0; y < height; y ++ ) {
1386
+
1387
+ for ( let x = 0; x < width; x ++ ) {
1388
+
1389
+ const newY = height - y - 1;
1390
+ const ogIndex = 4 * ( y * width + x );
1391
+ const newIndex = 4 * ( newY * width + x );
1392
+
1393
+ newData[ newIndex + 0 ] = ogData[ ogIndex + 0 ];
1394
+ newData[ newIndex + 1 ] = ogData[ ogIndex + 1 ];
1395
+ newData[ newIndex + 2 ] = ogData[ ogIndex + 2 ];
1396
+ newData[ newIndex + 3 ] = ogData[ ogIndex + 3 ];
1397
+
1398
+ }
1399
+
1400
+ }
1401
+
1402
+ map.flipY = false;
1403
+ map.image.data = newData;
1404
+
1405
+ }
1406
+
1407
+ return map;
1408
+
1409
+ }
1410
+
1411
+ class EquirectHdrInfoUniform {
1412
+
1413
+ constructor() {
1414
+
1415
+ // Stores a map of [0, 1] value -> cumulative importance row & pdf
1416
+ // used to sampling a random value to a relevant row to sample from
1417
+ const marginalWeights = new three.DataTexture();
1418
+ marginalWeights.type = three.FloatType;
1419
+ marginalWeights.format = three.RedFormat;
1420
+ marginalWeights.minFilter = three.LinearFilter;
1421
+ marginalWeights.magFilter = three.LinearFilter;
1422
+ marginalWeights.generateMipmaps = false;
1423
+
1424
+ // Stores a map of [0, 1] value -> cumulative importance column & pdf
1425
+ // used to sampling a random value to a relevant pixel to sample from
1426
+ const conditionalWeights = new three.DataTexture();
1427
+ conditionalWeights.type = three.FloatType;
1428
+ conditionalWeights.format = three.RedFormat;
1429
+ conditionalWeights.minFilter = three.LinearFilter;
1430
+ conditionalWeights.magFilter = three.LinearFilter;
1431
+ conditionalWeights.generateMipmaps = false;
1432
+
1433
+ // store the total sum in a 1x1 tex since some android mobile devices have issues
1434
+ // storing large values in structs.
1435
+ const totalSumTex = new three.DataTexture();
1436
+ totalSumTex.type = three.FloatType;
1437
+ totalSumTex.format = three.RedFormat;
1438
+ totalSumTex.minFilter = three.LinearFilter;
1439
+ totalSumTex.magFilter = three.LinearFilter;
1440
+ totalSumTex.generateMipmaps = false;
1441
+
1442
+ this.marginalWeights = marginalWeights;
1443
+ this.conditionalWeights = conditionalWeights;
1444
+ this.totalSum = totalSumTex;
1445
+ this.map = null;
1446
+
1447
+ }
1448
+
1449
+ dispose() {
1450
+
1451
+ this.marginalWeights.dispose();
1452
+ this.conditionalWeights.dispose();
1453
+ this.totalSum.dispose();
1454
+ if ( this.map ) this.map.dispose();
1455
+
1456
+ }
1457
+
1458
+ updateFrom( hdr ) {
1459
+
1460
+ // https://github.com/knightcrawler25/GLSL-PathTracer/blob/3c6fd9b6b3da47cd50c527eeb45845eef06c55c3/src/loaders/hdrloader.cpp
1461
+ // https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
1462
+ const map = preprocessEnvMap( hdr );
1463
+ map.wrapS = three.RepeatWrapping;
1464
+ map.wrapT = three.RepeatWrapping;
1465
+
1466
+ const { width, height, data } = map.image;
1467
+
1468
+ // "conditional" = "pixel relative to row pixels sum"
1469
+ // "marginal" = "row relative to row sum"
1470
+
1471
+ // track the importance of any given pixel in the image by tracking its weight relative to other pixels in the image
1472
+ const pdfConditional = new Float32Array( width * height );
1473
+ const cdfConditional = new Float32Array( width * height );
1474
+
1475
+ const pdfMarginal = new Float32Array( height );
1476
+ const cdfMarginal = new Float32Array( height );
1477
+
1478
+ let totalSumValue = 0.0;
1479
+ let cumulativeWeightMarginal = 0.0;
1480
+ for ( let y = 0; y < height; y ++ ) {
1481
+
1482
+ let cumulativeRowWeight = 0.0;
1483
+ for ( let x = 0; x < width; x ++ ) {
1484
+
1485
+ const i = y * width + x;
1486
+ const r = data[ 4 * i + 0 ];
1487
+ const g = data[ 4 * i + 1 ];
1488
+ const b = data[ 4 * i + 2 ];
1489
+
1490
+ // the probability of the pixel being selected in this row is the
1491
+ // scale of the luminance relative to the rest of the pixels.
1492
+ // TODO: this should also account for the solid angle of the pixel when sampling
1493
+ const weight = colorToLuminance( r, g, b );
1494
+ cumulativeRowWeight += weight;
1495
+ totalSumValue += weight;
1496
+
1497
+ pdfConditional[ i ] = weight;
1498
+ cdfConditional[ i ] = cumulativeRowWeight;
1499
+
1500
+ }
1501
+
1502
+ // can happen if the row is all black
1503
+ if ( cumulativeRowWeight !== 0 ) {
1504
+
1505
+ // scale the pdf and cdf to [0.0, 1.0]
1506
+ for ( let i = y * width, l = y * width + width; i < l; i ++ ) {
1507
+
1508
+ pdfConditional[ i ] /= cumulativeRowWeight;
1509
+ cdfConditional[ i ] /= cumulativeRowWeight;
1510
+
1511
+ }
1512
+
1513
+ }
1514
+
1515
+ cumulativeWeightMarginal += cumulativeRowWeight;
1516
+
1517
+ // compute the marginal pdf and cdf along the height of the map.
1518
+ pdfMarginal[ y ] = cumulativeRowWeight;
1519
+ cdfMarginal[ y ] = cumulativeWeightMarginal;
1520
+
1521
+ }
1522
+
1523
+ // can happen if the texture is all black
1524
+ if ( cumulativeWeightMarginal !== 0 ) {
1525
+
1526
+ // scale the marginal pdf and cdf to [0.0, 1.0]
1527
+ for ( let i = 0, l = pdfMarginal.length; i < l; i ++ ) {
1528
+
1529
+ pdfMarginal[ i ] /= cumulativeWeightMarginal;
1530
+ cdfMarginal[ i ] /= cumulativeWeightMarginal;
1531
+
1532
+ }
1533
+
1534
+ }
1535
+
1536
+ // compute a sorted index of distributions and the probabilities along them for both
1537
+ // the marginal and conditional data. These will be used to sample with a random number
1538
+ // to retrieve a uv value to sample in the environment map.
1539
+ // These values continually increase so it's okay to interpolate between them.
1540
+ const marginalDataArray = new Float32Array( height );
1541
+ const conditionalDataArray = new Float32Array( width * height );
1542
+
1543
+ for ( let i = 0; i < height; i ++ ) {
1544
+
1545
+ const dist = ( i + 1 ) / height;
1546
+ const row = binarySearchFindClosestIndexOf( cdfMarginal, dist );
1547
+
1548
+ marginalDataArray[ i ] = row / height;
1549
+
1550
+ }
1551
+
1552
+ for ( let y = 0; y < height; y ++ ) {
1553
+
1554
+ for ( let x = 0; x < width; x ++ ) {
1555
+
1556
+ const i = y * width + x;
1557
+ const dist = ( x + 1 ) / width;
1558
+ const col = binarySearchFindClosestIndexOf( cdfConditional, dist, y * width, width );
1559
+
1560
+ conditionalDataArray[ i ] = col / width;
1561
+
1562
+ }
1563
+
1564
+ }
1565
+
1566
+ this.dispose();
1567
+
1568
+ const { marginalWeights, conditionalWeights, totalSum } = this;
1569
+ marginalWeights.image = { width: height, height: 1, data: marginalDataArray };
1570
+ marginalWeights.needsUpdate = true;
1571
+
1572
+ conditionalWeights.image = { width, height, data: conditionalDataArray };
1573
+ conditionalWeights.needsUpdate = true;
1574
+
1575
+ totalSum.image = { width: 1, height: 1, data: new Float32Array( [ totalSumValue ] ) };
1576
+ totalSum.needsUpdate = true;
1577
+
1578
+ this.map = map;
1579
+
1580
+ }
1581
+
1582
+ }
1583
+
1584
+ class PhysicalCameraUniform {
1585
+
1586
+ constructor() {
1587
+
1588
+ this.bokehSize = 0;
1589
+ this.apertureBlades = 0;
1590
+ this.apertureRotation = 0;
1591
+ this.focusDistance = 10;
1592
+ this.anamorphicRatio = 1;
1593
+
1594
+ }
1595
+
1596
+ updateFrom( camera ) {
1597
+
1598
+ if ( camera instanceof PhysicalCamera ) {
1599
+
1600
+ this.bokehSize = camera.bokehSize;
1601
+ this.apertureBlades = camera.apertureBlades;
1602
+ this.apertureRotation = camera.apertureRotation;
1603
+ this.focusDistance = camera.focusDistance;
1604
+ this.anamorphicRatio = camera.anamorphicRatio;
1605
+
1606
+ } else {
1607
+
1608
+ this.bokehSize = 0;
1609
+ this.apertureRotation = 0;
1610
+ this.apertureBlades = 0;
1611
+ this.focusDistance = 10;
1612
+ this.anamorphicRatio = 1;
1613
+
1614
+ }
1615
+
1616
+ }
1617
+
1618
+ }
1619
+
1620
+ const shaderUtils = /* glsl */`
1621
+
1622
+ // https://google.github.io/filament/Filament.md.html#materialsystem/diffusebrdf
1623
+ float schlickFresnel( float cosine, float f0 ) {
1624
+
1625
+ return f0 + ( 1.0 - f0 ) * pow( 1.0 - cosine, 5.0 );
1626
+
1627
+ }
1628
+
1629
+ // https://raytracing.github.io/books/RayTracingInOneWeekend.html#dielectrics/schlickapproximation
1630
+ float schlickFresnelFromIor( float cosine, float iorRatio ) {
1631
+
1632
+ // Schlick approximation
1633
+ float r_0 = pow( ( 1.0 - iorRatio ) / ( 1.0 + iorRatio ), 2.0 );
1634
+ return schlickFresnel( cosine, r_0 );
1635
+
1636
+ }
1637
+
1638
+ // forms a basis with the normal vector as Z
1639
+ mat3 getBasisFromNormal( vec3 normal ) {
1640
+
1641
+ vec3 other;
1642
+ if ( abs( normal.x ) > 0.5 ) {
1643
+
1644
+ other = vec3( 0.0, 1.0, 0.0 );
1645
+
1646
+ } else {
1647
+
1648
+ other = vec3( 1.0, 0.0, 0.0 );
1649
+
1650
+ }
1651
+
1652
+ vec3 ortho = normalize( cross( normal, other ) );
1653
+ vec3 ortho2 = normalize( cross( normal, ortho ) );
1654
+ return mat3( ortho2, ortho, normal );
1655
+
1656
+ }
1657
+
1658
+ vec3 getHalfVector( vec3 a, vec3 b ) {
1659
+
1660
+ return normalize( a + b );
1661
+
1662
+ }
1663
+
1664
+ // The discrepancy between interpolated surface normal and geometry normal can cause issues when a ray
1665
+ // is cast that is on the top side of the geometry normal plane but below the surface normal plane. If
1666
+ // we find a ray like that we ignore it to avoid artifacts.
1667
+ // This function returns if the direction is on the same side of both planes.
1668
+ bool isDirectionValid( vec3 direction, vec3 surfaceNormal, vec3 geometryNormal ) {
1669
+
1670
+ bool aboveSurfaceNormal = dot( direction, surfaceNormal ) > 0.0;
1671
+ bool aboveGeometryNormal = dot( direction, geometryNormal ) > 0.0;
1672
+ return aboveSurfaceNormal == aboveGeometryNormal;
1673
+
1674
+ }
1675
+
1676
+ vec3 getHemisphereSample( vec3 n, vec2 uv ) {
1677
+
1678
+ // https://www.rorydriscoll.com/2009/01/07/better-sampling/
1679
+ // https://graphics.pixar.com/library/OrthonormalB/paper.pdf
1680
+ float sign = n.z == 0.0 ? 1.0 : sign( n.z );
1681
+ float a = - 1.0 / ( sign + n.z );
1682
+ float b = n.x * n.y * a;
1683
+ vec3 b1 = vec3( 1.0 + sign * n.x * n.x * a, sign * b, - sign * n.x );
1684
+ vec3 b2 = vec3( b, sign + n.y * n.y * a, - n.y );
1685
+
1686
+ float r = sqrt( uv.x );
1687
+ float theta = 2.0 * PI * uv.y;
1688
+ float x = r * cos( theta );
1689
+ float y = r * sin( theta );
1690
+ return x * b1 + y * b2 + sqrt( 1.0 - uv.x ) * n;
1691
+
1692
+ }
1693
+
1694
+ // https://www.shadertoy.com/view/wltcRS
1695
+ uvec4 s0;
1696
+
1697
+ void rng_initialize(vec2 p, int frame) {
1698
+
1699
+ // white noise seed
1700
+ s0 = uvec4( p, uint( frame ), uint( p.x ) + uint( p.y ) );
1701
+
1702
+ }
1703
+
1704
+ // https://www.pcg-random.org/
1705
+ void pcg4d( inout uvec4 v ) {
1706
+
1707
+ v = v * 1664525u + 1013904223u;
1708
+ v.x += v.y * v.w;
1709
+ v.y += v.z * v.x;
1710
+ v.z += v.x * v.y;
1711
+ v.w += v.y * v.z;
1712
+ v = v ^ ( v >> 16u );
1713
+ v.x += v.y*v.w;
1714
+ v.y += v.z*v.x;
1715
+ v.z += v.x*v.y;
1716
+ v.w += v.y*v.z;
1717
+
1718
+ }
1719
+
1720
+ // returns [ 0, 1 ]
1721
+ float rand() {
1722
+
1723
+ pcg4d(s0);
1724
+ return float( s0.x ) / float( 0xffffffffu );
1725
+
1726
+ }
1727
+
1728
+ vec2 rand2() {
1729
+
1730
+ pcg4d( s0 );
1731
+ return vec2( s0.xy ) / float(0xffffffffu);
1732
+
1733
+ }
1734
+
1735
+ vec3 rand3() {
1736
+
1737
+ pcg4d(s0);
1738
+ return vec3( s0.xyz ) / float( 0xffffffffu );
1739
+
1740
+ }
1741
+
1742
+ vec4 rand4() {
1743
+
1744
+ pcg4d(s0);
1745
+ return vec4(s0)/float(0xffffffffu);
1746
+
1747
+ }
1748
+
1749
+ // https://github.com/mrdoob/three.js/blob/dev/src/math/Vector3.js#L724
1750
+ vec3 randDirection() {
1751
+
1752
+ vec2 r = rand2();
1753
+ float u = ( r.x - 0.5 ) * 2.0;
1754
+ float t = r.y * PI * 2.0;
1755
+ float f = sqrt( 1.0 - u * u );
1756
+
1757
+ return vec3( f * cos( t ), f * sin( t ), u );
1758
+
1759
+ }
1760
+
1761
+ vec2 triangleSample( vec2 a, vec2 b, vec2 c ) {
1762
+
1763
+ // get the edges of the triangle and the diagonal across the
1764
+ // center of the parallelogram
1765
+ vec2 e1 = a - b;
1766
+ vec2 e2 = c - b;
1767
+ vec2 diag = normalize( e1 + e2 );
1768
+
1769
+ // pick a random point in the parallelogram
1770
+ vec2 r = rand2();
1771
+ if ( r.x + r.y > 1.0 ) {
1772
+
1773
+ r = vec2( 1.0 ) - r;
1774
+
1775
+ }
1776
+
1777
+ return e1 * r.x + e2 * r.y;
1778
+
1779
+ }
1780
+
1781
+ // samples an aperture shape with the given number of sides. 0 means circle
1782
+ vec2 sampleAperture( int blades ) {
1783
+
1784
+ if ( blades == 0 ) {
1785
+
1786
+ vec2 r = rand2();
1787
+ float angle = 2.0 * PI * r.x;
1788
+ float radius = sqrt( rand() );
1789
+ return vec2( cos( angle ), sin( angle ) ) * radius;
1790
+
1791
+ } else {
1792
+
1793
+ blades = max( blades, 3 );
1794
+
1795
+ vec3 r = rand3();
1796
+ float anglePerSegment = 2.0 * PI / float( blades );
1797
+ float segment = floor( float( blades ) * r.x );
1798
+
1799
+ float angle1 = anglePerSegment * segment;
1800
+ float angle2 = angle1 + anglePerSegment;
1801
+ vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
1802
+ vec2 b = vec2( 0.0, 0.0 );
1803
+ vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
1804
+
1805
+ return triangleSample( a, b, c );
1806
+
1807
+ }
1808
+
1809
+ }
1810
+
1811
+ float colorToLuminance( vec3 color ) {
1812
+
1813
+ // https://en.wikipedia.org/wiki/Relative_luminance
1814
+ return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
1815
+
1816
+ }
1817
+
1818
+ // ray sampling x and z are swapped to align with expected background view
1819
+ vec2 equirectDirectionToUv( vec3 direction ) {
1820
+
1821
+ // from Spherical.setFromCartesianCoords
1822
+ vec2 uv = vec2( atan( direction.z, direction.x ), acos( direction.y ) );
1823
+ uv /= vec2( 2.0 * PI, PI );
1824
+
1825
+ // apply adjustments to get values in range [0, 1] and y right side up
1826
+ uv.x += 0.5;
1827
+ uv.y = 1.0 - uv.y;
1828
+ return uv;
1829
+
1830
+ }
1831
+
1832
+ vec3 equirectUvToDirection( vec2 uv ) {
1833
+
1834
+ // undo above adjustments
1835
+ uv.x -= 0.5;
1836
+ uv.y = 1.0 - uv.y;
1837
+
1838
+ // from Vector3.setFromSphericalCoords
1839
+ float theta = uv.x * 2.0 * PI;
1840
+ float phi = uv.y * PI;
1841
+
1842
+ float sinPhi = sin( phi );
1843
+
1844
+ return vec3( sinPhi * cos( theta ), cos( phi ), sinPhi * sin( theta ) );
1845
+
1846
+ }
1847
+
1848
+ // Fast arccos approximation used to remove banding artifacts caused by numerical errors in acos.
1849
+ // This is a cubic Lagrange interpolating polynomial for x = [-1, -1/2, 0, 1/2, 1].
1850
+ // For more information see: https://github.com/gkjohnson/three-gpu-pathtracer/pull/171#issuecomment-1152275248
1851
+ float acosApprox( float x ) {
1852
+
1853
+ x = clamp( x, -1.0, 1.0 );
1854
+ return ( - 0.69813170079773212 * x * x - 0.87266462599716477 ) * x + 1.5707963267948966;
1855
+
1856
+ }
1857
+
1858
+ // An acos with input values bound to the range [-1, 1].
1859
+ float acosSafe( float x ) {
1860
+
1861
+ return acos( clamp( x, -1.0, 1.0 ) );
1862
+
1863
+ }
1864
+
1865
+ `;
1866
+
1867
+ class PMREMCopyMaterial extends MaterialBase {
1868
+
1869
+ constructor() {
1870
+
1871
+ super( {
1872
+
1873
+ uniforms: {
1874
+
1875
+ envMap: { value: null },
1876
+ blur: { value: 0 },
1877
+
1878
+ },
1879
+
1880
+ vertexShader: /* glsl */`
1881
+
1882
+ varying vec2 vUv;
1883
+ void main() {
1884
+ vUv = uv;
1885
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
1886
+ }
1887
+
1888
+ `,
1889
+
1890
+ fragmentShader: /* glsl */`
1891
+
1892
+ #include <common>
1893
+ #include <cube_uv_reflection_fragment>
1894
+
1895
+ ${ shaderUtils }
1896
+
1897
+ uniform sampler2D envMap;
1898
+ uniform float blur;
1899
+ varying vec2 vUv;
1900
+ void main() {
1901
+
1902
+ vec3 rayDirection = equirectUvToDirection( vUv );
1903
+ gl_FragColor = textureCubeUV( envMap, rayDirection, blur );
1904
+
1905
+ }
1906
+
1907
+ `,
1908
+
1909
+ } );
1910
+
1911
+ }
1912
+
1913
+ }
1914
+
1915
+ class BlurredEnvMapGenerator {
1916
+
1917
+ constructor( renderer ) {
1918
+
1919
+ this.renderer = renderer;
1920
+ this.pmremGenerator = new three.PMREMGenerator( renderer );
1921
+ this.copyQuad = new Pass_js.FullScreenQuad( new PMREMCopyMaterial() );
1922
+ this.renderTarget = new three.WebGLRenderTarget( 1, 1, { type: three.FloatType, format: three.RGBAFormat } );
1923
+
1924
+ }
1925
+
1926
+ dispose() {
1927
+
1928
+ this.pmremGenerator.dispose();
1929
+ this.copyQuad.dispose();
1930
+ this.renderTarget.dispose();
1931
+
1932
+ }
1933
+
1934
+ generate( texture, blur ) {
1935
+
1936
+ const { pmremGenerator, renderTarget, copyQuad, renderer } = this;
1937
+
1938
+ // get the pmrem target
1939
+ const pmremTarget = pmremGenerator.fromEquirectangular( texture );
1940
+
1941
+ // set up the material
1942
+ const { width, height } = texture.image;
1943
+ renderTarget.setSize( width, height );
1944
+ copyQuad.material.envMap = pmremTarget.texture;
1945
+ copyQuad.material.blur = blur;
1946
+
1947
+ // render
1948
+ const prevRenderTarget = renderer.getRenderTarget();
1949
+ const prevClear = renderer.autoClear;
1950
+
1951
+ renderer.setRenderTarget( renderTarget );
1952
+ renderer.autoClear = true;
1953
+ copyQuad.render( renderer );
1954
+
1955
+ renderer.setRenderTarget( prevRenderTarget );
1956
+ renderer.autoClear = prevClear;
1957
+
1958
+ // read the data back
1959
+ const buffer = new Float32Array( width * height * 4 );
1960
+ renderer.readRenderTargetPixels( renderTarget, 0, 0, width, height, buffer );
1961
+
1962
+ const result = new three.DataTexture( buffer, width, height, three.RGBAFormat, three.FloatType );
1963
+ result.minFilter = texture.minFilter;
1964
+ result.magFilter = texture.magFilter;
1965
+ result.wrapS = texture.wrapS;
1966
+ result.wrapT = texture.wrapT;
1967
+ result.mapping = three.EquirectangularReflectionMapping;
1968
+ result.needsUpdate = true;
1969
+
1970
+ return result;
1971
+
1972
+ }
1973
+
1974
+ }
1975
+
1976
+ const shaderMaterialStructs = /* glsl */ `
1977
+
1978
+ struct PhysicalCamera {
1979
+
1980
+ float focusDistance;
1981
+ float anamorphicRatio;
1982
+ float bokehSize;
1983
+ int apertureBlades;
1984
+ float apertureRotation;
1985
+
1986
+ };
1987
+
1988
+ struct EquirectHdrInfo {
1989
+
1990
+ sampler2D marginalWeights;
1991
+ sampler2D conditionalWeights;
1992
+ sampler2D map;
1993
+ sampler2D totalSum;
1994
+
1995
+ };
1996
+
1997
+ struct Material {
1998
+
1999
+ vec3 color;
2000
+ int map;
2001
+
2002
+ float metalness;
2003
+ int metalnessMap;
2004
+
2005
+ float roughness;
2006
+ int roughnessMap;
2007
+
2008
+ float ior;
2009
+ float transmission;
2010
+ int transmissionMap;
2011
+
2012
+ float emissiveIntensity;
2013
+ vec3 emissive;
2014
+ int emissiveMap;
2015
+
2016
+ int normalMap;
2017
+ vec2 normalScale;
2018
+
2019
+ int alphaMap;
2020
+
2021
+ bool castShadow;
2022
+ float opacity;
2023
+ float alphaTest;
2024
+
2025
+ float side;
2026
+ bool matte;
2027
+
2028
+ mat3 mapTransform;
2029
+ mat3 metalnessMapTransform;
2030
+ mat3 roughnessMapTransform;
2031
+ mat3 transmissionMapTransform;
2032
+ mat3 emissiveMapTransform;
2033
+ mat3 normalMapTransform;
2034
+
2035
+ };
2036
+
2037
+ mat3 readTextureTransform( sampler2D tex, uint index ) {
2038
+
2039
+ mat3 textureTransform;
2040
+
2041
+ vec4 row1 = texelFetch1D( tex, index );
2042
+ vec4 row2 = texelFetch1D( tex, index + 1u );
2043
+
2044
+ textureTransform[0] = vec3(row1.r, row2.r, 0.0);
2045
+ textureTransform[1] = vec3(row1.g, row2.g, 0.0);
2046
+ textureTransform[2] = vec3(row1.b, row2.b, 1.0);
2047
+
2048
+ return textureTransform;
2049
+
2050
+ }
2051
+
2052
+ Material readMaterialInfo( sampler2D tex, uint index ) {
2053
+
2054
+ uint i = index * 19u;
2055
+
2056
+ vec4 s0 = texelFetch1D( tex, i + 0u );
2057
+ vec4 s1 = texelFetch1D( tex, i + 1u );
2058
+ vec4 s2 = texelFetch1D( tex, i + 2u );
2059
+ vec4 s3 = texelFetch1D( tex, i + 3u );
2060
+ vec4 s4 = texelFetch1D( tex, i + 4u );
2061
+ vec4 s5 = texelFetch1D( tex, i + 5u );
2062
+ vec4 s6 = texelFetch1D( tex, i + 6u );
2063
+
2064
+ Material m;
2065
+ m.color = s0.rgb;
2066
+ m.map = int( round( s0.a ) );
2067
+
2068
+ m.metalness = s1.r;
2069
+ m.metalnessMap = int( round( s1.g ) );
2070
+ m.roughness = s1.b;
2071
+ m.roughnessMap = int( round( s1.a ) );
2072
+
2073
+ m.ior = s2.r;
2074
+ m.transmission = s2.g;
2075
+ m.transmissionMap = int( round( s2.b ) );
2076
+ m.emissiveIntensity = s2.a;
2077
+
2078
+ m.emissive = s3.rgb;
2079
+ m.emissiveMap = int( round( s3.a ) );
2080
+
2081
+ m.normalMap = int( round( s4.r ) );
2082
+ m.normalScale = s4.gb;
2083
+
2084
+ m.alphaMap = int( round( s4.a ) );
2085
+
2086
+ m.opacity = s5.r;
2087
+ m.alphaTest = s5.g;
2088
+ m.side = s5.b;
2089
+ m.matte = bool( s5.a );
2090
+
2091
+ m.castShadow = ! bool( s6.r );
2092
+
2093
+ uint firstTextureTransformIdx = i + 7u;
2094
+
2095
+ m.mapTransform = m.map == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx);
2096
+ m.metalnessMapTransform = m.metalnessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 2u );
2097
+ m.roughnessMapTransform = m.roughnessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 4u );
2098
+ m.transmissionMapTransform = m.transmissionMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 6u );
2099
+ m.emissiveMapTransform = m.emissiveMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 8u );
2100
+ m.normalMapTransform = m.normalMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 10u );
2101
+
2102
+ return m;
2103
+
2104
+ }
2105
+
2106
+ `;
2107
+
2108
+ const shaderGGXFunctions = /* glsl */`
2109
+ // The GGX functions provide sampling and distribution information for normals as output so
2110
+ // in order to get probability of scatter direction the half vector must be computed and provided.
2111
+ // [0] https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
2112
+ // [1] https://hal.archives-ouvertes.fr/hal-01509746/document
2113
+ // [2] http://jcgt.org/published/0007/04/01/
2114
+ // [4] http://jcgt.org/published/0003/02/03/
2115
+
2116
+ // trowbridge-reitz === GGX === GTR
2117
+
2118
+ vec3 ggxDirection( vec3 incidentDir, float roughnessX, float roughnessY, float random1, float random2 ) {
2119
+
2120
+ // TODO: try GGXVNDF implementation from reference [2], here. Needs to update ggxDistribution
2121
+ // function below, as well
2122
+
2123
+ // Implementation from reference [1]
2124
+ // stretch view
2125
+ vec3 V = normalize( vec3( roughnessX * incidentDir.x, roughnessY * incidentDir.y, incidentDir.z ) );
2126
+
2127
+ // orthonormal basis
2128
+ vec3 T1 = ( V.z < 0.9999 ) ? normalize( cross( V, vec3( 0.0, 0.0, 1.0 ) ) ) : vec3( 1.0, 0.0, 0.0 );
2129
+ vec3 T2 = cross( T1, V );
2130
+
2131
+ // sample point with polar coordinates (r, phi)
2132
+ float a = 1.0 / ( 1.0 + V.z );
2133
+ float r = sqrt( random1 );
2134
+ float phi = ( random2 < a ) ? random2 / a * PI : PI + ( random2 - a ) / ( 1.0 - a ) * PI;
2135
+ float P1 = r * cos( phi );
2136
+ float P2 = r * sin( phi ) * ( ( random2 < a ) ? 1.0 : V.z );
2137
+
2138
+ // compute normal
2139
+ vec3 N = P1 * T1 + P2 * T2 + V * sqrt( max( 0.0, 1.0 - P1 * P1 - P2 * P2 ) );
2140
+
2141
+ // unstretch
2142
+ N = normalize( vec3( roughnessX * N.x, roughnessY * N.y, max( 0.0, N.z ) ) );
2143
+
2144
+ return N;
2145
+
2146
+ }
2147
+
2148
+ // Below are PDF and related functions for use in a Monte Carlo path tracer
2149
+ // as specified in Appendix B of the following paper
2150
+ // See equation (2) from reference [2]
2151
+ float ggxLamda( float theta, float roughness ) {
2152
+
2153
+ float tanTheta = tan( theta );
2154
+ float tanTheta2 = tanTheta * tanTheta;
2155
+ float alpha2 = roughness * roughness;
2156
+
2157
+ float numerator = - 1.0 + sqrt( 1.0 + alpha2 * tanTheta2 );
2158
+ return numerator / 2.0;
2159
+
2160
+ }
2161
+
2162
+ // See equation (2) from reference [2]
2163
+ float ggxShadowMaskG1( float theta, float roughness ) {
2164
+
2165
+ return 1.0 / ( 1.0 + ggxLamda( theta, roughness ) );
2166
+
2167
+ }
2168
+
2169
+ // See equation (125) from reference [4]
2170
+ float ggxShadowMaskG2( vec3 wi, vec3 wo, float roughness ) {
2171
+
2172
+ float incidentTheta = acos( wi.z );
2173
+ float scatterTheta = acos( wo.z );
2174
+ return 1.0 / ( 1.0 + ggxLamda( incidentTheta, roughness ) + ggxLamda( scatterTheta, roughness ) );
2175
+
2176
+ }
2177
+
2178
+ float ggxDistribution( vec3 halfVector, float roughness ) {
2179
+
2180
+ // See equation (33) from reference [0]
2181
+ float a2 = roughness * roughness;
2182
+ a2 = max( EPSILON, a2 );
2183
+ float cosTheta = halfVector.z;
2184
+ float cosTheta4 = pow( cosTheta, 4.0 );
2185
+
2186
+ if ( cosTheta == 0.0 ) return 0.0;
2187
+
2188
+ float theta = acosSafe( halfVector.z );
2189
+ float tanTheta = tan( theta );
2190
+ float tanTheta2 = pow( tanTheta, 2.0 );
2191
+
2192
+ float denom = PI * cosTheta4 * pow( a2 + tanTheta2, 2.0 );
2193
+ return ( a2 / denom );
2194
+
2195
+ // See equation (1) from reference [2]
2196
+ // const { x, y, z } = halfVector;
2197
+ // const a2 = roughness * roughness;
2198
+ // const mult = x * x / a2 + y * y / a2 + z * z;
2199
+ // const mult2 = mult * mult;
2200
+
2201
+ // return 1.0 / Math.PI * a2 * mult2;
2202
+
2203
+ }
2204
+
2205
+ // See equation (3) from reference [2]
2206
+ float ggxPDF( vec3 wi, vec3 halfVector, float roughness ) {
2207
+
2208
+ float incidentTheta = acos( wi.z );
2209
+ float D = ggxDistribution( halfVector, roughness );
2210
+ float G1 = ggxShadowMaskG1( incidentTheta, roughness );
2211
+
2212
+ return D * G1 * max( 0.0, dot( wi, halfVector ) ) / wi.z;
2213
+
2214
+ }
2215
+ `;
2216
+
2217
+ const shaderMaterialSampling = /* glsl */`
2218
+
2219
+ struct SurfaceRec {
2220
+ vec3 normal;
2221
+ vec3 faceNormal;
2222
+ bool frontFace;
2223
+ float roughness;
2224
+ float filteredRoughness;
2225
+ float metalness;
2226
+ vec3 color;
2227
+ vec3 emission;
2228
+ float transmission;
2229
+ float ior;
2230
+ };
2231
+
2232
+ struct SampleRec {
2233
+ float specularPdf;
2234
+ float pdf;
2235
+ vec3 direction;
2236
+ vec3 color;
2237
+ };
2238
+
2239
+ ${ shaderGGXFunctions }
2240
+
2241
+ // diffuse
2242
+ float diffusePDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
2243
+
2244
+ // https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html#lightscattering/thescatteringpdf
2245
+ float cosValue = wi.z;
2246
+ return cosValue / PI;
2247
+
2248
+ }
2249
+
2250
+ vec3 diffuseDirection( vec3 wo, SurfaceRec surf ) {
2251
+
2252
+ vec3 lightDirection = randDirection();
2253
+ lightDirection.z += 1.0;
2254
+ lightDirection = normalize( lightDirection );
2255
+
2256
+ return lightDirection;
2257
+
2258
+ }
2259
+
2260
+ vec3 diffuseColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
2261
+
2262
+ // TODO: scale by 1 - F here
2263
+ // note on division by PI
2264
+ // https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
2265
+ float metalFactor = ( 1.0 - surf.metalness ) * wi.z / ( PI * PI );
2266
+ float transmissionFactor = 1.0 - surf.transmission;
2267
+ return surf.color * metalFactor * transmissionFactor;
2268
+
2269
+ }
2270
+
2271
+ // specular
2272
+ float specularPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
2273
+
2274
+ // See equation (17) in http://jcgt.org/published/0003/02/03/
2275
+ float filteredRoughness = surf.filteredRoughness;
2276
+ vec3 halfVector = getHalfVector( wi, wo );
2277
+ return ggxPDF( wi, halfVector, filteredRoughness ) / ( 4.0 * dot( wi, halfVector ) );
2278
+
2279
+ }
2280
+
2281
+ vec3 specularDirection( vec3 wo, SurfaceRec surf ) {
2282
+
2283
+ // sample ggx vndf distribution which gives a new normal
2284
+ float filteredRoughness = surf.filteredRoughness;
2285
+ vec3 halfVector = ggxDirection(
2286
+ wo,
2287
+ filteredRoughness,
2288
+ filteredRoughness,
2289
+ rand(),
2290
+ rand()
2291
+ );
2292
+
2293
+ // apply to new ray by reflecting off the new normal
2294
+ return - reflect( wo, halfVector );
2295
+
2296
+ }
2297
+
2298
+ vec3 specularColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
2299
+
2300
+ // if roughness is set to 0 then D === NaN which results in black pixels
2301
+ float metalness = surf.metalness;
2302
+ float ior = surf.ior;
2303
+ bool frontFace = surf.frontFace;
2304
+ float filteredRoughness = surf.filteredRoughness;
2305
+
2306
+ vec3 halfVector = getHalfVector( wo, wi );
2307
+ float iorRatio = frontFace ? 1.0 / ior : ior;
2308
+ float G = ggxShadowMaskG2( wi, wo, filteredRoughness );
2309
+ float D = ggxDistribution( halfVector, filteredRoughness );
2310
+
2311
+ float F = schlickFresnelFromIor( dot( wi, halfVector ), iorRatio );
2312
+ float cosTheta = min( wo.z, 1.0 );
2313
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
2314
+ bool cannotRefract = iorRatio * sinTheta > 1.0;
2315
+ if ( cannotRefract ) {
2316
+
2317
+ F = 1.0;
2318
+
2319
+ }
2320
+
2321
+ vec3 color = mix( vec3( 1.0 ), surf.color, metalness );
2322
+ color = mix( color, vec3( 1.0 ), F );
2323
+ color *= G * D / ( 4.0 * abs( wi.z * wo.z ) );
2324
+ color *= mix( F, 1.0, metalness );
2325
+ color *= wi.z; // scale the light by the direction the light is coming in from
2326
+
2327
+ return color;
2328
+
2329
+ }
2330
+
2331
+ /*
2332
+ // transmission
2333
+ function transmissionPDF( wo, wi, material, surf ) {
2334
+
2335
+ // See section 4.2 in https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
2336
+
2337
+ const { roughness, ior } = material;
2338
+ const { frontFace } = hit;
2339
+ const ratio = frontFace ? ior : 1 / ior;
2340
+ const minRoughness = Math.max( roughness, MIN_ROUGHNESS );
2341
+
2342
+ halfVector.set( 0, 0, 0 ).addScaledVector( wi, ratio ).addScaledVector( wo, 1.0 ).normalize().multiplyScalar( - 1 );
2343
+
2344
+ const denom = Math.pow( ratio * halfVector.dot( wi ) + 1.0 * halfVector.dot( wo ), 2.0 );
2345
+ return ggxPDF( wo, halfVector, minRoughness ) / denom;
2346
+
2347
+ }
2348
+
2349
+ function transmissionDirection( wo, hit, material, lightDirection ) {
2350
+
2351
+ const { roughness, ior } = material;
2352
+ const { frontFace } = hit;
2353
+ const ratio = frontFace ? 1 / ior : ior;
2354
+ const minRoughness = Math.max( roughness, MIN_ROUGHNESS );
2355
+
2356
+ // sample ggx vndf distribution which gives a new normal
2357
+ ggxDirection(
2358
+ wo,
2359
+ minRoughness,
2360
+ minRoughness,
2361
+ Math.random(),
2362
+ Math.random(),
2363
+ halfVector,
2364
+ );
2365
+
2366
+ // apply to new ray by reflecting off the new normal
2367
+ tempDir.copy( wo ).multiplyScalar( - 1 );
2368
+ refract( tempDir, halfVector, ratio, lightDirection );
2369
+
2370
+ }
2371
+
2372
+ function transmissionColor( wo, wi, material, hit, colorTarget ) {
2373
+
2374
+ const { metalness, transmission } = material;
2375
+ colorTarget
2376
+ .copy( material.color )
2377
+ .multiplyScalar( ( 1.0 - metalness ) * wo.z )
2378
+ .multiplyScalar( transmission );
2379
+
2380
+ }
2381
+ */
2382
+
2383
+ // TODO: This is just using a basic cosine-weighted specular distribution with an
2384
+ // incorrect PDF value at the moment. Update it to correctly use a GGX distribution
2385
+ float transmissionPDF( vec3 wo, vec3 wi, SurfaceRec surf ) {
2386
+
2387
+ float ior = surf.ior;
2388
+ bool frontFace = surf.frontFace;
2389
+
2390
+ float ratio = frontFace ? 1.0 / ior : ior;
2391
+ float cosTheta = min( wo.z, 1.0 );
2392
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
2393
+ float reflectance = schlickFresnelFromIor( cosTheta, ratio );
2394
+ bool cannotRefract = ratio * sinTheta > 1.0;
2395
+ if ( cannotRefract ) {
2396
+
2397
+ return 0.0;
2398
+
2399
+ }
2400
+
2401
+ return 1.0 / ( 1.0 - reflectance );
2402
+
2403
+ }
2404
+
2405
+ vec3 transmissionDirection( vec3 wo, SurfaceRec surf ) {
2406
+
2407
+ float roughness = surf.roughness;
2408
+ float ior = surf.ior;
2409
+ bool frontFace = surf.frontFace;
2410
+ float ratio = frontFace ? 1.0 / ior : ior;
2411
+
2412
+ vec3 lightDirection = refract( - wo, vec3( 0.0, 0.0, 1.0 ), ratio );
2413
+ lightDirection += randDirection() * roughness;
2414
+ return normalize( lightDirection );
2415
+
2416
+ }
2417
+
2418
+ vec3 transmissionColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
2419
+
2420
+ float metalness = surf.metalness;
2421
+ float transmission = surf.transmission;
2422
+
2423
+ vec3 color = surf.color;
2424
+ color *= ( 1.0 - metalness );
2425
+ color *= transmission;
2426
+
2427
+ return color;
2428
+
2429
+ }
2430
+
2431
+ float bsdfPdf( vec3 wo, vec3 wi, SurfaceRec surf, out float specularPdf ) {
2432
+
2433
+ float ior = surf.ior;
2434
+ float metalness = surf.metalness;
2435
+ float transmission = surf.transmission;
2436
+ bool frontFace = surf.frontFace;
2437
+
2438
+ float ratio = frontFace ? 1.0 / ior : ior;
2439
+ float cosTheta = min( wo.z, 1.0 );
2440
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
2441
+ float reflectance = schlickFresnelFromIor( cosTheta, ratio );
2442
+ bool cannotRefract = ratio * sinTheta > 1.0;
2443
+ if ( cannotRefract ) {
2444
+
2445
+ reflectance = 1.0;
2446
+
2447
+ }
2448
+
2449
+ float spdf = 0.0;
2450
+ float dpdf = 0.0;
2451
+ float tpdf = 0.0;
2452
+
2453
+ if ( wi.z < 0.0 ) {
2454
+
2455
+ tpdf = transmissionPDF( wo, wi, surf );
2456
+
2457
+ } else {
2458
+
2459
+ spdf = specularPDF( wo, wi, surf );
2460
+ dpdf = diffusePDF( wo, wi, surf );
2461
+
2462
+ }
2463
+
2464
+ float transSpecularProb = mix( reflectance, 1.0, metalness );
2465
+ float diffSpecularProb = 0.5 + 0.5 * metalness;
2466
+ float pdf =
2467
+ spdf * transmission * transSpecularProb
2468
+ + tpdf * transmission * ( 1.0 - transSpecularProb )
2469
+ + spdf * ( 1.0 - transmission ) * diffSpecularProb
2470
+ + dpdf * ( 1.0 - transmission ) * ( 1.0 - diffSpecularProb );
2471
+
2472
+ // retrieve specular rays for the shadows flag
2473
+ specularPdf = spdf * transmission * transSpecularProb + spdf * ( 1.0 - transmission ) * diffSpecularProb;
2474
+
2475
+ return pdf;
2476
+
2477
+ }
2478
+
2479
+ vec3 bsdfColor( vec3 wo, vec3 wi, SurfaceRec surf ) {
2480
+
2481
+ vec3 color = vec3( 0.0 );
2482
+ if ( wi.z < 0.0 ) {
2483
+
2484
+ color = transmissionColor( wo, wi, surf );
2485
+
2486
+ } else {
2487
+
2488
+ color = diffuseColor( wo, wi, surf );
2489
+ color *= 1.0 - surf.transmission;
2490
+
2491
+ color += specularColor( wo, wi, surf );
2492
+
2493
+ }
2494
+
2495
+ return color;
2496
+
2497
+ }
2498
+
2499
+ float bsdfResult( vec3 wo, vec3 wi, SurfaceRec surf, out vec3 color ) {
2500
+
2501
+ float specularPdf;
2502
+ color = bsdfColor( wo, wi, surf );
2503
+ return bsdfPdf( wo, wi, surf, specularPdf );
2504
+
2505
+ }
2506
+
2507
+ SampleRec bsdfSample( vec3 wo, SurfaceRec surf ) {
2508
+
2509
+ float ior = surf.ior;
2510
+ float metalness = surf.metalness;
2511
+ float transmission = surf.transmission;
2512
+ bool frontFace = surf.frontFace;
2513
+
2514
+ float ratio = frontFace ? 1.0 / ior : ior;
2515
+ float cosTheta = min( wo.z, 1.0 );
2516
+ float sinTheta = sqrt( 1.0 - cosTheta * cosTheta );
2517
+ float reflectance = schlickFresnelFromIor( cosTheta, ratio );
2518
+ bool cannotRefract = ratio * sinTheta > 1.0;
2519
+ if ( cannotRefract ) {
2520
+
2521
+ reflectance = 1.0;
2522
+
2523
+ }
2524
+
2525
+ SampleRec result;
2526
+ if ( rand() < transmission ) {
2527
+
2528
+ float specularProb = mix( reflectance, 1.0, metalness );
2529
+ if ( rand() < specularProb ) {
2530
+
2531
+ result.direction = specularDirection( wo, surf );
2532
+
2533
+ } else {
2534
+
2535
+ result.direction = transmissionDirection( wo, surf );
2536
+
2537
+ }
2538
+
2539
+ } else {
2540
+
2541
+ float specularProb = 0.5 + 0.5 * metalness;
2542
+ if ( rand() < specularProb ) {
2543
+
2544
+ result.direction = specularDirection( wo, surf );
2545
+
2546
+ } else {
2547
+
2548
+ result.direction = diffuseDirection( wo, surf );
2549
+
2550
+ }
2551
+
2552
+ }
2553
+
2554
+ result.pdf = bsdfPdf( wo, result.direction, surf, result.specularPdf );
2555
+ result.color = bsdfColor( wo, result.direction, surf );
2556
+ return result;
2557
+
2558
+ }
2559
+ `;
2560
+
2561
+ const shaderEnvMapSampling = /* glsl */`
2562
+
2563
+ vec3 sampleEquirectEnvMapColor( vec3 direction, sampler2D map ) {
2564
+
2565
+ return texture2D( map, equirectDirectionToUv( direction ) ).rgb;
2566
+
2567
+ }
2568
+
2569
+ float envMapDirectionPdf( vec3 direction ) {
2570
+
2571
+ vec2 uv = equirectDirectionToUv( direction );
2572
+ float theta = uv.y * PI;
2573
+ float sinTheta = sin( theta );
2574
+ if ( sinTheta == 0.0 ) {
2575
+
2576
+ return 0.0;
2577
+
2578
+ }
2579
+
2580
+ return 1.0 / ( 2.0 * PI * PI * sinTheta );
2581
+
2582
+ }
2583
+
2584
+ float envMapSample( vec3 direction, EquirectHdrInfo info, out vec3 color ) {
2585
+
2586
+ vec2 uv = equirectDirectionToUv( direction );
2587
+ color = texture2D( info.map, uv ).rgb;
2588
+
2589
+ float totalSum = texture2D( info.totalSum, vec2( 0.0 ) ).r;
2590
+ float lum = colorToLuminance( color );
2591
+ ivec2 resolution = textureSize( info.map, 0 );
2592
+ float pdf = lum / totalSum;
2593
+
2594
+ return float( resolution.x * resolution.y ) * pdf * envMapDirectionPdf( direction );
2595
+
2596
+ }
2597
+
2598
+ float randomEnvMapSample( EquirectHdrInfo info, out vec3 color, out vec3 direction ) {
2599
+
2600
+ // sample env map cdf
2601
+ vec2 r = rand2();
2602
+ float v = texture2D( info.marginalWeights, vec2( r.x, 0.0 ) ).x;
2603
+ float u = texture2D( info.conditionalWeights, vec2( r.y, v ) ).x;
2604
+ vec2 uv = vec2( u, v );
2605
+
2606
+ vec3 derivedDirection = equirectUvToDirection( uv );
2607
+ direction = derivedDirection;
2608
+ color = texture2D( info.map, uv ).rgb;
2609
+
2610
+ float totalSum = texture2D( info.totalSum, vec2( 0.0 ) ).r;
2611
+ float lum = colorToLuminance( color );
2612
+ ivec2 resolution = textureSize( info.map, 0 );
2613
+ float pdf = lum / totalSum;
2614
+
2615
+ return float( resolution.x * resolution.y ) * pdf * envMapDirectionPdf( direction );
2616
+
2617
+ }
2618
+
2619
+ float misHeuristic( float a, float b ) {
2620
+
2621
+ float aa = a * a;
2622
+ float bb = a * b;
2623
+ return aa / ( bb + aa );
2624
+
2625
+ }
2626
+
2627
+ `;
2628
+
2629
+ class PhysicalPathTracingMaterial extends MaterialBase {
2630
+
2631
+ onBeforeRender() {
2632
+
2633
+ this.setDefine( 'FEATURE_DOF', this.physicalCamera.bokehSize === 0 ? 0 : 1 );
2634
+
2635
+ }
2636
+
2637
+ constructor( parameters ) {
2638
+
2639
+ super( {
2640
+
2641
+ transparent: true,
2642
+ depthWrite: false,
2643
+
2644
+ defines: {
2645
+ FEATURE_MIS: 1,
2646
+ FEATURE_DOF: 1,
2647
+ FEATURE_GRADIENT_BG: 0,
2648
+ TRANSPARENT_TRAVERSALS: 5,
2649
+ },
2650
+
2651
+ uniforms: {
2652
+ resolution: { value: new three.Vector2() },
2653
+
2654
+ bounces: { value: 3 },
2655
+ physicalCamera: { value: new PhysicalCameraUniform() },
2656
+
2657
+ bvh: { value: new threeMeshBvh.MeshBVHUniformStruct() },
2658
+ normalAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
2659
+ tangentAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
2660
+ uvAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
2661
+ materialIndexAttribute: { value: new threeMeshBvh.UIntVertexAttributeTexture() },
2662
+ materials: { value: new MaterialsTexture() },
2663
+ textures: { value: new RenderTarget2DArray().texture },
2664
+ cameraWorldMatrix: { value: new three.Matrix4() },
2665
+ invProjectionMatrix: { value: new three.Matrix4() },
2666
+ isOrthographicCamera: { value: true },
2667
+ backgroundBlur: { value: 0.0 },
2668
+ environmentIntensity: { value: 2.0 },
2669
+ environmentRotation: { value: new three.Matrix3() },
2670
+ envMapInfo: { value: new EquirectHdrInfoUniform() },
2671
+
2672
+ seed: { value: 0 },
2673
+ opacity: { value: 1 },
2674
+ filterGlossyFactor: { value: 0.0 },
2675
+
2676
+ bgGradientTop: { value: new three.Color( 0x111111 ) },
2677
+ bgGradientBottom: { value: new three.Color( 0x000000 ) },
2678
+ backgroundAlpha: { value: 1.0 },
2679
+ },
2680
+
2681
+ vertexShader: /* glsl */`
2682
+
2683
+ varying vec2 vUv;
2684
+ void main() {
2685
+
2686
+ vec4 mvPosition = vec4( position, 1.0 );
2687
+ mvPosition = modelViewMatrix * mvPosition;
2688
+ gl_Position = projectionMatrix * mvPosition;
2689
+
2690
+ vUv = uv;
2691
+
2692
+ }
2693
+
2694
+ `,
2695
+
2696
+ fragmentShader: /* glsl */`
2697
+ #define RAY_OFFSET 1e-4
2698
+
2699
+ precision highp isampler2D;
2700
+ precision highp usampler2D;
2701
+ precision highp sampler2DArray;
2702
+ vec4 envMapTexelToLinear( vec4 a ) { return a; }
2703
+ #include <common>
2704
+
2705
+ ${ threeMeshBvh.shaderStructs }
2706
+ ${ threeMeshBvh.shaderIntersectFunction }
2707
+ ${ shaderMaterialStructs }
2708
+
2709
+ ${ shaderUtils }
2710
+ ${ shaderMaterialSampling }
2711
+ ${ shaderEnvMapSampling }
2712
+
2713
+ uniform mat3 environmentRotation;
2714
+ uniform float backgroundBlur;
2715
+ uniform float backgroundAlpha;
2716
+
2717
+ #if FEATURE_GRADIENT_BG
2718
+
2719
+ uniform vec3 bgGradientTop;
2720
+ uniform vec3 bgGradientBottom;
2721
+
2722
+ #endif
2723
+
2724
+ #if FEATURE_DOF
2725
+
2726
+ uniform PhysicalCamera physicalCamera;
2727
+
2728
+ #endif
2729
+
2730
+ uniform vec2 resolution;
2731
+ uniform int bounces;
2732
+ uniform mat4 cameraWorldMatrix;
2733
+ uniform mat4 invProjectionMatrix;
2734
+ uniform bool isOrthographicCamera;
2735
+ uniform sampler2D normalAttribute;
2736
+ uniform sampler2D tangentAttribute;
2737
+ uniform sampler2D uvAttribute;
2738
+ uniform usampler2D materialIndexAttribute;
2739
+ uniform BVH bvh;
2740
+ uniform float environmentIntensity;
2741
+ uniform float filterGlossyFactor;
2742
+ uniform int seed;
2743
+ uniform float opacity;
2744
+ uniform sampler2D materials;
2745
+
2746
+ uniform EquirectHdrInfo envMapInfo;
2747
+
2748
+ uniform sampler2DArray textures;
2749
+ varying vec2 vUv;
2750
+
2751
+ vec3 sampleBackground( vec3 direction ) {
2752
+
2753
+ #if FEATURE_GRADIENT_BG
2754
+
2755
+ direction = normalize( direction + randDirection() * 0.05 );
2756
+
2757
+ float value = ( direction.y + 1.0 ) / 2.0;
2758
+ value = pow( value, 2.0 );
2759
+
2760
+ return mix( bgGradientBottom, bgGradientTop, value );
2761
+
2762
+ #else
2763
+
2764
+ vec3 sampleDir = normalize( direction + getHemisphereSample( direction, rand2() ) * 0.5 * backgroundBlur );
2765
+ return environmentIntensity * sampleEquirectEnvMapColor( sampleDir, envMapInfo.map );
2766
+
2767
+ #endif
2768
+
2769
+ }
2770
+
2771
+ // step through multiple surface hits and accumulate color attenuation based on transmissive surfaces
2772
+ bool attenuateHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection, int traversals, bool isShadowRay, out vec3 color ) {
2773
+
2774
+ // hit results
2775
+ uvec4 faceIndices = uvec4( 0u );
2776
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
2777
+ vec3 barycoord = vec3( 0.0 );
2778
+ float side = 1.0;
2779
+ float dist = 0.0;
2780
+
2781
+ color = vec3( 1.0 );
2782
+
2783
+ for ( int i = 0; i < traversals; i ++ ) {
2784
+
2785
+ if ( bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
2786
+
2787
+ // TODO: attenuate the contribution based on the PDF of the resulting ray including refraction values
2788
+ // Should be able to work using the material BSDF functions which will take into account specularity, etc.
2789
+ // TODO: should we account for emissive surfaces here?
2790
+
2791
+ vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
2792
+ uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
2793
+ Material material = readMaterialInfo( materials, materialIndex );
2794
+
2795
+ // adjust the ray to the new surface
2796
+ bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
2797
+ vec3 point = rayOrigin + rayDirection * dist;
2798
+ vec3 absPoint = abs( point );
2799
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
2800
+ rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
2801
+
2802
+ if ( ! material.castShadow && isShadowRay ) {
2803
+
2804
+ continue;
2805
+
2806
+ }
2807
+
2808
+ // Opacity Test
2809
+
2810
+ // albedo
2811
+ vec4 albedo = vec4( material.color, material.opacity );
2812
+ if ( material.map != - 1 ) {
2813
+
2814
+ vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
2815
+ albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
2816
+
2817
+ }
2818
+
2819
+ // alphaMap
2820
+ if ( material.alphaMap != -1 ) {
2821
+
2822
+ albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
2823
+
2824
+ }
2825
+
2826
+ // transmission
2827
+ float transmission = material.transmission;
2828
+ if ( material.transmissionMap != - 1 ) {
2829
+
2830
+ vec3 uvPrime = material.transmissionMapTransform * vec3( uv, 1 );
2831
+ transmission *= texture2D( textures, vec3( uvPrime.xy, material.transmissionMap ) ).r;
2832
+
2833
+ }
2834
+
2835
+ // metalness
2836
+ float metalness = material.metalness;
2837
+ if ( material.metalnessMap != - 1 ) {
2838
+
2839
+ vec3 uvPrime = material.metalnessMapTransform * vec3( uv, 1 );
2840
+ metalness *= texture2D( textures, vec3( uvPrime.xy, material.metalnessMap ) ).b;
2841
+
2842
+ }
2843
+
2844
+ float alphaTest = material.alphaTest;
2845
+ bool useAlphaTest = alphaTest != 0.0;
2846
+ float transmissionFactor = ( 1.0 - metalness ) * transmission;
2847
+ if (
2848
+ transmissionFactor < rand() && ! (
2849
+ // material sidedness
2850
+ material.side != 0.0 && side == material.side
2851
+
2852
+ // alpha test
2853
+ || useAlphaTest && albedo.a < alphaTest
2854
+
2855
+ // opacity
2856
+ || ! useAlphaTest && albedo.a < rand()
2857
+ )
2858
+ ) {
2859
+
2860
+ return true;
2861
+
2862
+ }
2863
+
2864
+ // only attenuate on the way in
2865
+ if ( isBelowSurface ) {
2866
+
2867
+ color *= mix( vec3( 1.0 ), albedo.rgb, transmissionFactor );
2868
+
2869
+ }
2870
+
2871
+ } else {
2872
+
2873
+ return false;
2874
+
2875
+ }
2876
+
2877
+ }
2878
+
2879
+ return true;
2880
+
2881
+ }
2882
+
2883
+ // returns whether the ray hit anything, not just the first surface. Could be optimized to not check the full hierarchy.
2884
+ bool anyHit( BVH bvh, vec3 rayOrigin, vec3 rayDirection ) {
2885
+
2886
+ uvec4 faceIndices = uvec4( 0u );
2887
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
2888
+ vec3 barycoord = vec3( 0.0 );
2889
+ float side = 1.0;
2890
+ float dist = 0.0;
2891
+ return bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist );
2892
+
2893
+ }
2894
+
2895
+ // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
2896
+ // erichlof/THREE.js-PathTracing-Renderer/
2897
+ float tentFilter( float x ) {
2898
+
2899
+ return x < 0.5 ? sqrt( 2.0 * x ) - 1.0 : 1.0 - sqrt( 2.0 - ( 2.0 * x ) );
2900
+
2901
+ }
2902
+
2903
+ vec3 ndcToRayOrigin( vec2 coord ) {
2904
+
2905
+ vec4 rayOrigin4 = cameraWorldMatrix * invProjectionMatrix * vec4( coord, - 1.0, 1.0 );
2906
+ return rayOrigin4.xyz / rayOrigin4.w;
2907
+ }
2908
+
2909
+ void main() {
2910
+
2911
+ rng_initialize( gl_FragCoord.xy, seed );
2912
+
2913
+ // get [-1, 1] normalized device coordinates
2914
+ vec2 ndc = 2.0 * vUv - vec2( 1.0 );
2915
+
2916
+ vec3 ss00 = ndcToRayOrigin( vec2( - 1.0, - 1.0 ) );
2917
+ vec3 ss01 = ndcToRayOrigin( vec2( - 1.0, 1.0 ) );
2918
+ vec3 ss10 = ndcToRayOrigin( vec2( 1.0, - 1.0 ) );
2919
+
2920
+ vec3 ssdX = ( ss10 - ss00 ) / resolution.x;
2921
+ vec3 ssdY = ( ss01 - ss00 ) / resolution.y;
2922
+
2923
+ // Jitter the camera ray by finding a new subpixel point to point to from the camera origin
2924
+ // This is better than just jittering the camera position since it actually results in divergent
2925
+ // rays providing better coverage for the pixel
2926
+ vec3 rayOrigin = ndcToRayOrigin( ndc ) + tentFilter( rand() ) * ssdX + tentFilter( rand() ) * ssdY;
2927
+
2928
+ vec3 rayDirection;
2929
+
2930
+ if ( isOrthographicCamera ) {
2931
+
2932
+ rayDirection = ( cameraWorldMatrix * vec4( 0.0, 0.0, -1.0, 0.0 ) ).xyz;
2933
+ rayDirection = normalize( rayDirection );
2934
+
2935
+ } else {
2936
+
2937
+ vec3 cameraOrigin = ( cameraWorldMatrix * vec4( 0.0, 0.0, 0.0, 1.0 ) ).xyz;
2938
+ rayDirection = normalize( rayOrigin - cameraOrigin );
2939
+
2940
+ }
2941
+
2942
+ #if FEATURE_DOF
2943
+ {
2944
+
2945
+ // depth of field
2946
+ vec3 focalPoint = rayOrigin + normalize( rayDirection ) * physicalCamera.focusDistance;
2947
+
2948
+ // get the aperture sample
2949
+ vec2 apertureSample = sampleAperture( physicalCamera.apertureBlades ) * physicalCamera.bokehSize * 0.5 * 1e-3;
2950
+
2951
+ // rotate the aperture shape
2952
+ float ac = cos( physicalCamera.apertureRotation );
2953
+ float as = sin( physicalCamera.apertureRotation );
2954
+ apertureSample = vec2(
2955
+ apertureSample.x * ac - apertureSample.y * as,
2956
+ apertureSample.x * as + apertureSample.y * ac
2957
+ );
2958
+ apertureSample.x *= saturate( physicalCamera.anamorphicRatio );
2959
+ apertureSample.y *= saturate( 1.0 / physicalCamera.anamorphicRatio );
2960
+
2961
+ // create the new ray
2962
+ rayOrigin += ( cameraWorldMatrix * vec4( apertureSample, 0.0, 0.0 ) ).xyz;
2963
+ rayDirection = focalPoint - rayOrigin;
2964
+
2965
+ }
2966
+ #endif
2967
+ rayDirection = normalize( rayDirection );
2968
+
2969
+ // inverse environment rotation
2970
+ mat3 invEnvironmentRotation = inverse( environmentRotation );
2971
+
2972
+ // final color
2973
+ gl_FragColor = vec4( 0.0 );
2974
+ gl_FragColor.a = 1.0;
2975
+
2976
+ // hit results
2977
+ uvec4 faceIndices = uvec4( 0u );
2978
+ vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
2979
+ vec3 barycoord = vec3( 0.0 );
2980
+ float side = 1.0;
2981
+ float dist = 0.0;
2982
+
2983
+ // path tracing state
2984
+ float accumulatedRoughness = 0.0;
2985
+ bool transmissiveRay = true;
2986
+ int transparentTraversals = TRANSPARENT_TRAVERSALS;
2987
+ vec3 throughputColor = vec3( 1.0 );
2988
+ SampleRec sampleRec;
2989
+ int i;
2990
+ bool isShadowRay = false;
2991
+
2992
+ for ( i = 0; i < bounces; i ++ ) {
2993
+
2994
+ if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
2995
+
2996
+ if ( i == 0 || transmissiveRay ) {
2997
+
2998
+ gl_FragColor.rgb += sampleBackground( environmentRotation * rayDirection ) * throughputColor;
2999
+ gl_FragColor.a = backgroundAlpha;
3000
+
3001
+ } else {
3002
+
3003
+ #if FEATURE_MIS
3004
+
3005
+ // get the PDF of the hit envmap point
3006
+ vec3 envColor;
3007
+ float envPdf = envMapSample( environmentRotation * rayDirection, envMapInfo, envColor );
3008
+
3009
+ // and weight the contribution
3010
+ float misWeight = misHeuristic( sampleRec.pdf, envPdf );
3011
+ gl_FragColor.rgb += environmentIntensity * envColor * throughputColor * misWeight;
3012
+
3013
+ #else
3014
+
3015
+ gl_FragColor.rgb +=
3016
+ environmentIntensity *
3017
+ sampleEquirectEnvMapColor( environmentRotation * rayDirection, envMapInfo.map ) *
3018
+ throughputColor;
3019
+
3020
+ #endif
3021
+
3022
+ }
3023
+ break;
3024
+
3025
+ }
3026
+
3027
+ uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
3028
+ Material material = readMaterialInfo( materials, materialIndex );
3029
+
3030
+ if ( material.matte && i == 0 ) {
3031
+
3032
+ gl_FragColor = vec4( 0.0 );
3033
+ break;
3034
+
3035
+ }
3036
+
3037
+ // if we've determined that this is a shadow ray and we've hit an item with no shadow casting
3038
+ // then skip it
3039
+ if ( ! material.castShadow && isShadowRay ) {
3040
+
3041
+ vec3 point = rayOrigin + rayDirection * dist;
3042
+ vec3 absPoint = abs( point );
3043
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
3044
+ rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
3045
+
3046
+ continue;
3047
+
3048
+ }
3049
+
3050
+ vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
3051
+ // albedo
3052
+ vec4 albedo = vec4( material.color, material.opacity );
3053
+ if ( material.map != - 1 ) {
3054
+
3055
+ vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
3056
+ albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
3057
+ }
3058
+
3059
+ // alphaMap
3060
+ if ( material.alphaMap != -1 ) {
3061
+
3062
+ albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
3063
+
3064
+ }
3065
+
3066
+ // possibly skip this sample if it's transparent, alpha test is enabled, or we hit the wrong material side
3067
+ // and it's single sided.
3068
+ // - alpha test is disabled when it === 0
3069
+ // - the material sidedness test is complicated because we want light to pass through the back side but still
3070
+ // be able to see the front side. This boolean checks if the side we hit is the front side on the first ray
3071
+ // and we're rendering the other then we skip it. Do the opposite on subsequent bounces to get incoming light.
3072
+ float alphaTest = material.alphaTest;
3073
+ bool useAlphaTest = alphaTest != 0.0;
3074
+ bool isFirstHit = i == 0;
3075
+ if (
3076
+ // material sidedness
3077
+ material.side != 0.0 && ( side != material.side ) == isFirstHit
3078
+
3079
+ // alpha test
3080
+ || useAlphaTest && albedo.a < alphaTest
3081
+
3082
+ // opacity
3083
+ || ! useAlphaTest && albedo.a < rand()
3084
+ ) {
3085
+
3086
+ vec3 point = rayOrigin + rayDirection * dist;
3087
+ vec3 absPoint = abs( point );
3088
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
3089
+ rayOrigin = point - ( maxPoint + 1.0 ) * faceNormal * RAY_OFFSET;
3090
+
3091
+ // only allow a limited number of transparency discards otherwise we could
3092
+ // crash the context with too long a loop.
3093
+ i -= sign( transparentTraversals );
3094
+ transparentTraversals -= sign( transparentTraversals );
3095
+ continue;
3096
+
3097
+ }
3098
+
3099
+ // fetch the interpolated smooth normal
3100
+ vec3 normal = normalize( textureSampleBarycoord(
3101
+ normalAttribute,
3102
+ barycoord,
3103
+ faceIndices.xyz
3104
+ ).xyz );
3105
+
3106
+ // roughness
3107
+ float roughness = material.roughness;
3108
+ if ( material.roughnessMap != - 1 ) {
3109
+
3110
+ vec3 uvPrime = material.roughnessMapTransform * vec3( uv, 1 );
3111
+ roughness *= texture2D( textures, vec3( uvPrime.xy, material.roughnessMap ) ).g;
3112
+
3113
+ }
3114
+
3115
+ // metalness
3116
+ float metalness = material.metalness;
3117
+ if ( material.metalnessMap != - 1 ) {
3118
+
3119
+ vec3 uvPrime = material.metalnessMapTransform * vec3( uv, 1 );
3120
+ metalness *= texture2D( textures, vec3( uvPrime.xy, material.metalnessMap ) ).b;
3121
+
3122
+ }
3123
+
3124
+ // emission
3125
+ vec3 emission = material.emissiveIntensity * material.emissive;
3126
+ if ( material.emissiveMap != - 1 ) {
3127
+
3128
+ vec3 uvPrime = material.emissiveMapTransform * vec3( uv, 1 );
3129
+ emission *= texture2D( textures, vec3( uvPrime.xy, material.emissiveMap ) ).xyz;
3130
+
3131
+ }
3132
+
3133
+ // transmission
3134
+ float transmission = material.transmission;
3135
+ if ( material.transmissionMap != - 1 ) {
3136
+
3137
+ vec3 uvPrime = material.transmissionMapTransform * vec3( uv, 1 );
3138
+ transmission *= texture2D( textures, vec3( uvPrime.xy, material.transmissionMap ) ).r;
3139
+
3140
+ }
3141
+
3142
+ // normal
3143
+ if ( material.normalMap != - 1 ) {
3144
+
3145
+ vec4 tangentSample = textureSampleBarycoord(
3146
+ tangentAttribute,
3147
+ barycoord,
3148
+ faceIndices.xyz
3149
+ );
3150
+
3151
+ // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
3152
+ // resulting in NaNs and slow path tracing.
3153
+ if ( length( tangentSample.xyz ) > 0.0 ) {
3154
+
3155
+ vec3 tangent = normalize( tangentSample.xyz );
3156
+ vec3 bitangent = normalize( cross( normal, tangent ) * tangentSample.w );
3157
+ mat3 vTBN = mat3( tangent, bitangent, normal );
3158
+
3159
+ vec3 uvPrime = material.normalMapTransform * vec3( uv, 1 );
3160
+ vec3 texNormal = texture2D( textures, vec3( uvPrime.xy, material.normalMap ) ).xyz * 2.0 - 1.0;
3161
+ texNormal.xy *= material.normalScale;
3162
+ normal = vTBN * texNormal;
3163
+
3164
+ }
3165
+
3166
+ }
3167
+
3168
+ normal *= side;
3169
+
3170
+ SurfaceRec surfaceRec;
3171
+ surfaceRec.normal = normal;
3172
+ surfaceRec.faceNormal = faceNormal;
3173
+ surfaceRec.transmission = transmission;
3174
+ surfaceRec.ior = material.ior;
3175
+ surfaceRec.emission = emission;
3176
+ surfaceRec.metalness = metalness;
3177
+ surfaceRec.color = albedo.rgb;
3178
+ surfaceRec.roughness = roughness;
3179
+
3180
+ // frontFace is used to determine transmissive properties and PDF. If no transmission is used
3181
+ // then we can just always assume this is a front face.
3182
+ surfaceRec.frontFace = side == 1.0 || transmission == 0.0;
3183
+
3184
+ // Compute the filtered roughness value to use during specular reflection computations.
3185
+ // The accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
3186
+ // If we're exiting something transmissive then scale the factor down significantly so we can retain
3187
+ // sharp internal reflections
3188
+ surfaceRec.filteredRoughness = clamp( max( surfaceRec.roughness, accumulatedRoughness * filterGlossyFactor * 5.0 ), 0.0, 1.0 );
3189
+
3190
+ mat3 normalBasis = getBasisFromNormal( surfaceRec.normal );
3191
+ mat3 invBasis = inverse( normalBasis );
3192
+
3193
+ vec3 outgoing = - normalize( invBasis * rayDirection );
3194
+ sampleRec = bsdfSample( outgoing, surfaceRec );
3195
+
3196
+ float specRayPdf = specularPDF( outgoing, sampleRec.direction, surfaceRec );
3197
+ isShadowRay = sampleRec.specularPdf < rand();
3198
+
3199
+ // adjust the hit point by the surface normal by a factor of some offset and the
3200
+ // maximum component-wise value of the current point to accommodate floating point
3201
+ // error as values increase.
3202
+ vec3 point = rayOrigin + rayDirection * dist;
3203
+ vec3 absPoint = abs( point );
3204
+ float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
3205
+ rayDirection = normalize( normalBasis * sampleRec.direction );
3206
+
3207
+ bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
3208
+ rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
3209
+
3210
+ // direct env map sampling
3211
+ #if FEATURE_MIS
3212
+ {
3213
+
3214
+ // find a sample in the environment map to include in the contribution
3215
+ vec3 envColor, envDirection;
3216
+ float envPdf = randomEnvMapSample( envMapInfo, envColor, envDirection );
3217
+ envDirection = invEnvironmentRotation * envDirection;
3218
+
3219
+ // this env sampling is not set up for transmissive sampling and yields overly bright
3220
+ // results so we ignore the sample in this case.
3221
+ // TODO: this should be improved but how? The env samples could traverse a few layers?
3222
+ bool isSampleBelowSurface = dot( faceNormal, envDirection ) < 0.0;
3223
+ if ( isSampleBelowSurface ) {
3224
+
3225
+ envPdf = 0.0;
3226
+
3227
+ }
3228
+
3229
+ // check if a ray could even reach the surface
3230
+ vec3 attenuatedColor;
3231
+ if (
3232
+ envPdf > 0.0 &&
3233
+ isDirectionValid( envDirection, normal, faceNormal ) &&
3234
+ ! attenuateHit( bvh, rayOrigin, envDirection, bounces - i, isShadowRay, attenuatedColor )
3235
+ ) {
3236
+
3237
+ // get the material pdf
3238
+ vec3 sampleColor;
3239
+ float envMaterialPdf = bsdfResult( outgoing, normalize( invBasis * envDirection ), surfaceRec, sampleColor );
3240
+ if ( envMaterialPdf > 0.0 ) {
3241
+
3242
+ // weight the direct light contribution
3243
+ float misWeight = misHeuristic( envPdf, envMaterialPdf );
3244
+ gl_FragColor.rgb += attenuatedColor * environmentIntensity * envColor * throughputColor * sampleColor * misWeight / envPdf;
3245
+
3246
+ }
3247
+
3248
+ }
3249
+
3250
+ }
3251
+ #endif
3252
+
3253
+ // accumulate a roughness value to offset diffuse, specular, diffuse rays that have high contribution
3254
+ // to a single pixel resulting in fireflies
3255
+ if ( ! isBelowSurface ) {
3256
+
3257
+ // determine if this is a rough normal or not by checking how far off straight up it is
3258
+ vec3 halfVector = normalize( outgoing + sampleRec.direction );
3259
+ accumulatedRoughness += sin( acosApprox( halfVector.z ) );
3260
+ transmissiveRay = false;
3261
+
3262
+ }
3263
+
3264
+ // accumulate color
3265
+ gl_FragColor.rgb += ( emission * throughputColor );
3266
+
3267
+ // skip the sample if our PDF or ray is impossible
3268
+ if ( sampleRec.pdf <= 0.0 || ! isDirectionValid( rayDirection, normal, faceNormal) ) {
3269
+
3270
+ break;
3271
+
3272
+ }
3273
+
3274
+ throughputColor *= sampleRec.color / sampleRec.pdf;
3275
+
3276
+ // discard the sample if there are any NaNs
3277
+ if ( any( isnan( throughputColor ) ) || any( isinf( throughputColor ) ) ) {
3278
+
3279
+ break;
3280
+
3281
+ }
3282
+
3283
+ }
3284
+
3285
+ gl_FragColor.a *= opacity;
3286
+
3287
+ }
3288
+
3289
+ `
3290
+
3291
+ } );
3292
+
3293
+ this.setValues( parameters );
3294
+
3295
+ }
1462
3296
 
1463
- class PhysicalPathTracingMaterial extends MaterialBase {
1464
-
1465
- // three.js relies on this field to add env map functions and defines
1466
- get envMap() {
1467
-
1468
- return this.environmentMap;
1469
-
1470
- }
1471
-
1472
- constructor( parameters ) {
1473
-
1474
- super( {
1475
-
1476
- transparent: true,
1477
- depthWrite: false,
1478
-
1479
- defines: {
1480
- BOUNCES: 3,
1481
- TRANSPARENT_TRAVERSALS: 5,
1482
- MATERIAL_LENGTH: 0,
1483
- GRADIENT_BG: 0,
1484
- },
1485
-
1486
- uniforms: {
1487
- bvh: { value: new threeMeshBvh.MeshBVHUniformStruct() },
1488
- normalAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
1489
- tangentAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
1490
- uvAttribute: { value: new threeMeshBvh.FloatVertexAttributeTexture() },
1491
- materialIndexAttribute: { value: new threeMeshBvh.UIntVertexAttributeTexture() },
1492
- materials: { value: new MaterialStructArrayUniform() },
1493
- textures: { value: new RenderTarget2DArray().texture },
1494
- cameraWorldMatrix: { value: new three.Matrix4() },
1495
- invProjectionMatrix: { value: new three.Matrix4() },
1496
- environmentBlur: { value: 0.2 },
1497
- environmentIntensity: { value: 2.0 },
1498
- environmentMap: { value: null },
1499
- environmentRotation: { value: new three.Matrix3() },
1500
- seed: { value: 0 },
1501
- opacity: { value: 1 },
1502
- filterGlossyFactor: { value: 0.0 },
1503
-
1504
- gradientTop: { value: new three.Color( 0xbfd8ff ) },
1505
- gradientBottom: { value: new three.Color( 0xffffff ) },
1506
-
1507
- bgGradientTop: { value: new three.Color( 0x111111 ) },
1508
- bgGradientBottom: { value: new three.Color( 0x000000 ) },
1509
- },
1510
-
1511
- vertexShader: /* glsl */`
1512
-
1513
- varying vec2 vUv;
1514
- void main() {
1515
-
1516
- vec4 mvPosition = vec4( position, 1.0 );
1517
- mvPosition = modelViewMatrix * mvPosition;
1518
- gl_Position = projectionMatrix * mvPosition;
1519
-
1520
- vUv = uv;
1521
-
1522
- }
1523
-
1524
- `,
1525
-
1526
- fragmentShader: /* glsl */`
1527
- #define RAY_OFFSET 1e-5
1528
-
1529
- precision highp isampler2D;
1530
- precision highp usampler2D;
1531
- precision highp sampler2DArray;
1532
- vec4 envMapTexelToLinear( vec4 a ) { return a; }
1533
- #include <common>
1534
- #include <cube_uv_reflection_fragment>
1535
-
1536
- ${ threeMeshBvh.shaderStructs }
1537
- ${ threeMeshBvh.shaderIntersectFunction }
1538
- ${ shaderMaterialStructs }
1539
-
1540
- ${ shaderUtils }
1541
- ${ shaderMaterialSampling }
1542
-
1543
- #ifdef USE_ENVMAP
1544
-
1545
- uniform float environmentBlur;
1546
- uniform sampler2D environmentMap;
1547
- uniform mat3 environmentRotation;
1548
-
1549
- #else
1550
-
1551
- uniform vec3 gradientTop;
1552
- uniform vec3 gradientBottom;
1553
-
1554
- #endif
1555
-
1556
- #if GRADIENT_BG
1557
-
1558
- uniform vec3 bgGradientTop;
1559
- uniform vec3 bgGradientBottom;
1560
-
1561
- #endif
1562
-
1563
- uniform mat4 cameraWorldMatrix;
1564
- uniform mat4 invProjectionMatrix;
1565
- uniform sampler2D normalAttribute;
1566
- uniform sampler2D tangentAttribute;
1567
- uniform sampler2D uvAttribute;
1568
- uniform usampler2D materialIndexAttribute;
1569
- uniform BVH bvh;
1570
- uniform float environmentIntensity;
1571
- uniform float filterGlossyFactor;
1572
- uniform int seed;
1573
- uniform float opacity;
1574
- uniform Material materials[ MATERIAL_LENGTH ];
1575
- uniform sampler2DArray textures;
1576
- varying vec2 vUv;
1577
-
1578
- void main() {
1579
-
1580
- rng_initialize( gl_FragCoord.xy, seed );
1581
-
1582
- // get [-1, 1] normalized device coordinates
1583
- vec2 ndc = 2.0 * vUv - vec2( 1.0 );
1584
- vec3 rayOrigin, rayDirection;
1585
- ndcToCameraRay( ndc, cameraWorldMatrix, invProjectionMatrix, rayOrigin, rayDirection );
1586
-
1587
- // Lambertian render
1588
- gl_FragColor = vec4( 0.0 );
1589
-
1590
- vec3 throughputColor = vec3( 1.0 );
1591
-
1592
- // hit results
1593
- uvec4 faceIndices = uvec4( 0u );
1594
- vec3 faceNormal = vec3( 0.0, 0.0, 1.0 );
1595
- vec3 barycoord = vec3( 0.0 );
1596
- float side = 1.0;
1597
- float dist = 0.0;
1598
- float accumulatedRoughness = 0.0;
1599
- int i;
1600
- int transparentTraversals = TRANSPARENT_TRAVERSALS;
1601
- for ( i = 0; i < BOUNCES; i ++ ) {
1602
-
1603
- if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
1604
-
1605
- #if GRADIENT_BG
1606
-
1607
- if ( i == 0 ) {
1608
-
1609
- rayDirection = normalize( rayDirection + randDirection() * 0.05 );
1610
- float value = ( rayDirection.y + 1.0 ) / 2.0;
1611
-
1612
- value = pow( value, 2.0 );
1613
-
1614
- gl_FragColor = vec4( mix( bgGradientBottom, bgGradientTop, value ), 1.0 );
1615
- break;
1616
-
1617
- }
1618
-
1619
- #endif
1620
-
1621
- #ifdef USE_ENVMAP
1622
-
1623
- vec3 skyColor = textureCubeUV( environmentMap, environmentRotation * rayDirection, environmentBlur ).rgb;
1624
-
1625
- #else
1626
-
1627
- rayDirection = normalize( rayDirection );
1628
- float value = ( rayDirection.y + 1.0 ) / 2.0;
1629
- vec3 skyColor = mix( gradientBottom, gradientTop, value );
1630
-
1631
- #endif
1632
-
1633
- gl_FragColor += vec4( skyColor * throughputColor * environmentIntensity, 1.0 );
1634
-
1635
- break;
1636
-
1637
- }
1638
-
1639
- uint materialIndex = uTexelFetch1D( materialIndexAttribute, faceIndices.x ).r;
1640
- Material material = materials[ materialIndex ];
1641
-
1642
- vec2 uv = textureSampleBarycoord( uvAttribute, barycoord, faceIndices.xyz ).xy;
1643
-
1644
- // albedo
1645
- vec4 albedo = vec4( material.color, material.opacity );
1646
- if ( material.map != - 1 ) {
1647
-
1648
- albedo *= texture2D( textures, vec3( uv, material.map ) );
1649
-
1650
- }
1651
-
1652
- // possibly skip this sample if it's transparent or alpha test is enabled
1653
- // alpha test is disabled when it === 0
1654
- float alphaTest = material.alphaTest;
1655
- bool useAlphaTest = alphaTest != 0.0;
1656
- if (
1657
- useAlphaTest && albedo.a < alphaTest
1658
- || ! useAlphaTest && albedo.a < rand()
1659
- ) {
1660
-
1661
- vec3 point = rayOrigin + rayDirection * dist;
1662
- rayOrigin += rayDirection * dist - faceNormal * RAY_OFFSET;
1663
-
1664
- // only allow a limited number of transparency discards otherwise we could
1665
- // crash the context with too long a loop.
1666
- i -= sign( transparentTraversals );
1667
- transparentTraversals -= sign( transparentTraversals );
1668
- continue;
1669
-
1670
- }
1671
-
1672
- // fetch the interpolated smooth normal
1673
- vec3 normal = normalize( textureSampleBarycoord(
1674
- normalAttribute,
1675
- barycoord,
1676
- faceIndices.xyz
1677
- ).xyz );
1678
-
1679
- // roughness
1680
- float roughness = material.roughness;
1681
- if ( material.roughnessMap != - 1 ) {
1682
-
1683
- roughness *= texture2D( textures, vec3( uv, material.roughnessMap ) ).g;
1684
-
1685
- }
1686
-
1687
- // metalness
1688
- float metalness = material.metalness;
1689
- if ( material.metalnessMap != - 1 ) {
1690
-
1691
- metalness *= texture2D( textures, vec3( uv, material.metalnessMap ) ).b;
1692
-
1693
- }
1694
-
1695
- // emission
1696
- vec3 emission = material.emissiveIntensity * material.emissive;
1697
- if ( material.emissiveMap != - 1 ) {
1698
-
1699
- emission *= texture2D( textures, vec3( uv, material.emissiveMap ) ).xyz;
1700
-
1701
- }
1702
-
1703
- // transmission
1704
- float transmission = material.transmission;
1705
- if ( material.transmissionMap != - 1 ) {
1706
-
1707
- transmission *= texture2D( textures, vec3( uv, material.transmissionMap ) ).r;
1708
-
1709
- }
1710
-
1711
- // normal
1712
- if ( material.normalMap != - 1 ) {
1713
-
1714
- vec4 tangentSample = textureSampleBarycoord(
1715
- tangentAttribute,
1716
- barycoord,
1717
- faceIndices.xyz
1718
- );
1719
-
1720
- // some provided tangents can be malformed (0, 0, 0) causing the normal to be degenerate
1721
- // resulting in NaNs and slow path tracing.
1722
- if ( length( tangentSample.xyz ) > 0.0 ) {
1723
-
1724
- vec3 tangent = normalize( tangentSample.xyz );
1725
- vec3 bitangent = normalize( cross( normal, tangent ) * tangentSample.w );
1726
- mat3 vTBN = mat3( tangent, bitangent, normal );
1727
-
1728
- vec3 texNormal = texture2D( textures, vec3( uv, material.normalMap ) ).xyz * 2.0 - 1.0;
1729
- texNormal.xy *= material.normalScale;
1730
- normal = vTBN * texNormal;
1731
-
1732
- }
1733
-
1734
- }
1735
-
1736
- normal *= side;
1737
-
1738
- SurfaceRec surfaceRec;
1739
- surfaceRec.normal = normal;
1740
- surfaceRec.faceNormal = faceNormal;
1741
- surfaceRec.frontFace = side == 1.0;
1742
- surfaceRec.transmission = transmission;
1743
- surfaceRec.ior = material.ior;
1744
- surfaceRec.emission = emission;
1745
- surfaceRec.metalness = metalness;
1746
- surfaceRec.color = albedo.rgb;
1747
- surfaceRec.roughness = roughness;
1748
-
1749
- // Compute the filtered roughness value to use during specular reflection computations. A minimum
1750
- // value of 1e-6 is needed because the GGX functions do not work with a roughness value of 0 and
1751
- // the accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
1752
- // If we're exiting something transmissive then scale the factor down significantly so we can retain
1753
- // sharp internal reflections
1754
- surfaceRec.filteredRoughness = clamp(
1755
- max( surfaceRec.roughness, accumulatedRoughness * filterGlossyFactor * 5.0 ),
1756
- 1e-3,
1757
- 1.0
1758
- );
1759
-
1760
- mat3 normalBasis = getBasisFromNormal( surfaceRec.normal );
1761
- mat3 invBasis = inverse( normalBasis );
1762
-
1763
- vec3 outgoing = - normalize( invBasis * rayDirection );
1764
- SampleRec sampleRec = bsdfSample( outgoing, surfaceRec );
1765
-
1766
- // adjust the hit point by the surface normal by a factor of some offset and the
1767
- // maximum component-wise value of the current point to accommodate floating point
1768
- // error as values increase.
1769
- vec3 point = rayOrigin + rayDirection * dist;
1770
- vec3 absPoint = abs( point );
1771
- float maxPoint = max( absPoint.x, max( absPoint.y, absPoint.z ) );
1772
- rayDirection = normalize( normalBasis * sampleRec.direction );
1773
-
1774
- bool isBelowSurface = dot( rayDirection, faceNormal ) < 0.0;
1775
- rayOrigin = point + faceNormal * ( maxPoint + 1.0 ) * ( isBelowSurface ? - RAY_OFFSET : RAY_OFFSET );
1776
-
1777
- // accumulate a roughness value to offset diffuse, specular, diffuse rays that have high contribution
1778
- // to a single pixel resulting in fireflies
1779
- if ( ! isBelowSurface ) {
1780
-
1781
- // determine if this is a rough normal or not by checking how far off straight up it is
1782
- vec3 halfVector = normalize( outgoing + sampleRec.direction );
1783
- accumulatedRoughness += sin( acos( halfVector.z ) );
1784
-
1785
- }
1786
-
1787
- // accumulate color
1788
- gl_FragColor.rgb += ( emission * throughputColor );
1789
-
1790
- // skip the sample if our PDF or ray is impossible
1791
- if ( sampleRec.pdf <= 0.0 || ! isDirectionValid( rayDirection, normal, faceNormal) ) {
1792
-
1793
- break;
1794
-
1795
- }
1796
-
1797
- throughputColor *= sampleRec.color / sampleRec.pdf;
1798
-
1799
- // discard the sample if there are any NaNs
1800
- if ( any( isnan( throughputColor ) ) || any( isinf( throughputColor ) ) ) {
1801
-
1802
- break;
1803
-
1804
- }
1805
-
1806
- }
1807
-
1808
- gl_FragColor.a = opacity;
1809
-
1810
- }
1811
-
1812
- `
1813
-
1814
- } );
1815
-
1816
- this.setValues( parameters );
1817
-
1818
- }
1819
-
1820
3297
  }
1821
3298
 
1822
3299
  // core
1823
3300
 
3301
+ exports.BlurredEnvMapGenerator = BlurredEnvMapGenerator;
3302
+ exports.DynamicPathTracingSceneGenerator = DynamicPathTracingSceneGenerator;
3303
+ exports.EquirectHdrInfoUniform = EquirectHdrInfoUniform;
1824
3304
  exports.MaterialBase = MaterialBase;
1825
3305
  exports.MaterialReducer = MaterialReducer;
1826
- exports.MaterialStructArrayUniform = MaterialStructArrayUniform;
1827
- exports.MaterialStructUniform = MaterialStructUniform;
3306
+ exports.MaterialsTexture = MaterialsTexture;
1828
3307
  exports.PathTracingRenderer = PathTracingRenderer;
1829
3308
  exports.PathTracingSceneGenerator = PathTracingSceneGenerator;
3309
+ exports.PhysicalCamera = PhysicalCamera;
3310
+ exports.PhysicalCameraUniform = PhysicalCameraUniform;
1830
3311
  exports.PhysicalPathTracingMaterial = PhysicalPathTracingMaterial;
1831
3312
  exports.RenderTarget2DArray = RenderTarget2DArray;
3313
+ exports.getGroupMaterialIndicesAttribute = getGroupMaterialIndicesAttribute;
1832
3314
  exports.mergeMeshes = mergeMeshes;
3315
+ exports.setCommonAttributes = setCommonAttributes;
1833
3316
  exports.shaderMaterialSampling = shaderMaterialSampling;
1834
3317
  exports.shaderMaterialStructs = shaderMaterialStructs;
1835
3318
  exports.shaderUtils = shaderUtils;
3319
+ exports.trimToAttributes = trimToAttributes;
1836
3320
 
1837
3321
  Object.defineProperty(exports, '__esModule', { value: true });
1838
3322