three-gpu-pathtracer 0.0.1 → 0.0.2

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.
@@ -1,7 +1,6 @@
1
- import { Color, Vector2, WebGLRenderTarget, RGBAFormat, FloatType, BufferAttribute, WebGLArrayRenderTarget, UnsignedByteType, LinearFilter, RepeatWrapping, MeshBasicMaterial, NoToneMapping, ShaderMaterial, Matrix4, Matrix3 } from 'three';
1
+ import { Color, Vector2, WebGLRenderTarget, RGBAFormat, FloatType, BufferAttribute, Mesh, BufferGeometry, PerspectiveCamera, BackSide, FrontSide, DoubleSide, WebGLArrayRenderTarget, UnsignedByteType, LinearFilter, RepeatWrapping, MeshBasicMaterial, NoToneMapping, ShaderMaterial, Matrix4, Matrix3 } from 'three';
2
2
  import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
3
- import { SAH, MeshBVHUniformStruct, FloatVertexAttributeTexture, UIntVertexAttributeTexture, shaderStructs, shaderIntersectFunction } from 'three-mesh-bvh';
4
- import { GenerateMeshBVHWorker } from 'three-mesh-bvh/src/workers/GenerateMeshBVHWorker.js';
3
+ import { StaticGeometryGenerator, SAH, MeshBVH, MeshBVHUniformStruct, FloatVertexAttributeTexture, UIntVertexAttributeTexture, shaderStructs, shaderIntersectFunction } from 'three-mesh-bvh';
5
4
  import { mergeVertices, mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
6
5
 
7
6
  function* renderTask() {
@@ -142,193 +141,226 @@ class PathTracingRenderer {
142
141
 
143
142
  }
144
143
 
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 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 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 = mergeVertices( geometry );
242
-
243
- }
244
-
245
- geometry.computeTangents();
246
-
247
- } else {
248
-
249
- const vertCount = geometry.attributes.position.count;
250
- geometry.setAttribute( 'tangent', new 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 = mergeBufferGeometries( transformedGeometry, false );
311
- const textures = Array.from( textureSet );
312
- return { geometry, materials, textures };
313
-
144
+ function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
145
+
146
+ if ( ! Array.isArray( materials ) ) {
147
+
148
+ materials = [ materials ];
149
+
150
+ }
151
+
152
+ const indexAttr = geometry.index;
153
+ const posAttr = geometry.attributes.position;
154
+ const vertCount = posAttr.count;
155
+ const materialArray = new Uint8Array( vertCount );
156
+ const totalCount = indexAttr ? indexAttr.count : vertCount;
157
+ let groups = geometry.groups;
158
+ if ( groups.length === 0 ) {
159
+
160
+ groups = [ { count: totalCount, start: 0, materialIndex: 0 } ];
161
+
162
+ }
163
+
164
+ for ( let i = 0; i < groups.length; i ++ ) {
165
+
166
+ const group = groups[ i ];
167
+ const start = group.start;
168
+ const count = group.count;
169
+ const endCount = Math.min( count, totalCount - start );
170
+
171
+ const mat = materials[ group.materialIndex ];
172
+ const materialIndex = allMaterials.indexOf( mat );
173
+
174
+ for ( let j = 0; j < endCount; j ++ ) {
175
+
176
+ let index = start + j;
177
+ if ( indexAttr ) {
178
+
179
+ index = indexAttr.getX( index );
180
+
181
+ }
182
+
183
+ materialArray[ index ] = materialIndex;
184
+
185
+ }
186
+
187
+ }
188
+
189
+ return new BufferAttribute( materialArray, 1, false );
190
+
191
+ }
192
+
193
+ function trimToAttributes( geometry, attributes ) {
194
+
195
+ // trim any unneeded attributes
196
+ if ( attributes ) {
197
+
198
+ for ( const key in geometry.attributes ) {
199
+
200
+ if ( ! attributes.includes( key ) ) {
201
+
202
+ geometry.deleteAttribute( key );
203
+
204
+ }
205
+
206
+ }
207
+
208
+ }
209
+
210
+ }
211
+
212
+ function setCommonAttributes( geometry, options ) {
213
+
214
+ const { attributes = [], normalMapRequired = false } = options;
215
+
216
+ if ( ! geometry.attributes.normal && ( attributes && attributes.includes( 'normal' ) ) ) {
217
+
218
+ geometry.computeVertexNormals();
219
+
220
+ }
221
+
222
+ if ( ! geometry.attributes.uv && ( attributes && attributes.includes( 'uv' ) ) ) {
223
+
224
+ const vertCount = geometry.attributes.position.count;
225
+ geometry.setAttribute( 'uv', new BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
226
+
227
+ }
228
+
229
+ if ( ! geometry.attributes.tangent && ( attributes && attributes.includes( 'tangent' ) ) ) {
230
+
231
+ if ( normalMapRequired ) {
232
+
233
+ // computeTangents requires an index buffer
234
+ if ( geometry.index === null ) {
235
+
236
+ geometry = mergeVertices( geometry );
237
+
238
+ }
239
+
240
+ geometry.computeTangents();
241
+
242
+ } else {
243
+
244
+ const vertCount = geometry.attributes.position.count;
245
+ geometry.setAttribute( 'tangent', new BufferAttribute( new Float32Array( vertCount * 4 ), 4, false ) );
246
+
247
+ }
248
+
249
+ }
250
+
251
+ if ( ! geometry.index ) {
252
+
253
+ // TODO: compute a typed array
254
+ const indexCount = geometry.attributes.position.count;
255
+ const array = new Array( indexCount );
256
+ for ( let i = 0; i < indexCount; i ++ ) {
257
+
258
+ array[ i ] = i;
259
+
260
+ }
261
+
262
+ geometry.setIndex( array );
263
+
264
+ }
265
+
266
+ }
267
+
268
+ function mergeMeshes( meshes, options = {} ) {
269
+
270
+ options = { attributes: null, cloneGeometry: true, ...options };
271
+
272
+ const transformedGeometry = [];
273
+ const materialSet = new Set();
274
+ for ( let i = 0, l = meshes.length; i < l; i ++ ) {
275
+
276
+ // save any materials
277
+ const mesh = meshes[ i ];
278
+ if ( mesh.visible === false ) continue;
279
+
280
+ if ( Array.isArray( mesh.material ) ) {
281
+
282
+ mesh.material.forEach( m => materialSet.add( m ) );
283
+
284
+ } else {
285
+
286
+ materialSet.add( mesh.material );
287
+
288
+ }
289
+
290
+ }
291
+
292
+ const materials = Array.from( materialSet );
293
+ for ( let i = 0, l = meshes.length; i < l; i ++ ) {
294
+
295
+ // ensure the matrix world is up to date
296
+ const mesh = meshes[ i ];
297
+ if ( mesh.visible === false ) continue;
298
+
299
+ mesh.updateMatrixWorld();
300
+
301
+ // apply the matrix world to the geometry
302
+ const originalGeometry = meshes[ i ].geometry;
303
+ let geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
304
+ geometry.applyMatrix4( mesh.matrixWorld );
305
+
306
+ // ensure our geometry has common attributes
307
+ setCommonAttributes( geometry, {
308
+ attributes: options.attributes,
309
+ normalMapRequired: ! ! mesh.material.normalMap,
310
+ } );
311
+ trimToAttributes( geometry, options.attributes );
312
+
313
+ // create the material index attribute
314
+ const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
315
+ geometry.setAttribute( 'materialIndex', materialIndexAttribute );
316
+
317
+ transformedGeometry.push( geometry );
318
+
319
+ }
320
+
321
+ const textureSet = new Set();
322
+ materials.forEach( material => {
323
+
324
+ for ( const key in material ) {
325
+
326
+ const value = material[ key ];
327
+ if ( value && value.isTexture ) {
328
+
329
+ textureSet.add( value );
330
+
331
+ }
332
+
333
+ }
334
+
335
+ } );
336
+
337
+ const geometry = mergeBufferGeometries( transformedGeometry, false );
338
+ const textures = Array.from( textureSet );
339
+ return { geometry, materials, textures };
340
+
314
341
  }
315
342
 
316
343
  class PathTracingSceneGenerator {
317
344
 
318
- constructor() {
319
-
320
- this.bvhGenerator = new GenerateMeshBVHWorker();
321
-
322
- }
345
+ prepScene( scene ) {
323
346
 
324
- async generate( scene, options = {} ) {
325
-
326
- const { bvhGenerator } = this;
327
347
  const meshes = [];
328
-
329
348
  scene.traverse( c => {
330
349
 
331
- if ( c.isMesh ) {
350
+ if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
351
+
352
+ const generator = new StaticGeometryGenerator( c );
353
+ generator.applyWorldTransforms = false;
354
+ const mesh = new Mesh(
355
+ generator.generate(),
356
+ c.material,
357
+ );
358
+ mesh.matrixWorld.copy( c.matrixWorld );
359
+ mesh.matrix.copy( c.matrixWorld );
360
+ mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
361
+ meshes.push( mesh );
362
+
363
+ } else if ( c.isMesh ) {
332
364
 
333
365
  meshes.push( c );
334
366
 
@@ -336,24 +368,127 @@ class PathTracingSceneGenerator {
336
368
 
337
369
  } );
338
370
 
339
- const { geometry, materials, textures } = mergeMeshes( meshes, { attributes: [ 'position', 'normal', 'tangent', 'uv' ] } );
340
- const bvhPromise = bvhGenerator.generate( geometry, { strategy: SAH, ...options, maxLeafTris: 1 } );
371
+ return mergeMeshes( meshes, {
372
+ attributes: [ 'position', 'normal', 'tangent', 'uv' ],
373
+ } );
341
374
 
375
+ }
376
+
377
+ generate( scene, options = {} ) {
378
+
379
+ const { materials, textures, geometry } = this.prepScene( scene );
380
+ const bvhOptions = { strategy: SAH, ...options, maxLeafTris: 1 };
342
381
  return {
343
382
  scene,
344
383
  materials,
345
384
  textures,
346
- bvh: await bvhPromise,
385
+ bvh: new MeshBVH( geometry, bvhOptions ),
347
386
  };
348
387
 
349
388
  }
350
389
 
351
- dispose() {
352
-
353
- this.bvhGenerator.terminate();
354
-
355
- }
356
-
390
+ }
391
+
392
+ class DynamicPathTracingSceneGenerator {
393
+
394
+ get initialized() {
395
+
396
+ return Boolean( this.bvh );
397
+
398
+ }
399
+
400
+ constructor( scene ) {
401
+
402
+ this.scene = scene;
403
+ this.bvh = null;
404
+ this.geometry = new BufferGeometry();
405
+ this.materials = null;
406
+ this.textures = null;
407
+ this.staticGeometryGenerator = new StaticGeometryGenerator( scene );
408
+
409
+ }
410
+
411
+ reset() {
412
+
413
+ this.geometry.dispose();
414
+ this.geometry = new BufferGeometry();
415
+ this.materials = null;
416
+ this.textures = null;
417
+ this.staticGeometryGenerator = new StaticGeometryGenerator( this.scene );
418
+
419
+ }
420
+
421
+ dispose() {}
422
+
423
+ generate() {
424
+
425
+ const { scene, staticGeometryGenerator, geometry } = this;
426
+ if ( this.bvh === null ) {
427
+
428
+ const attributes = [ 'position', 'normal', 'tangent', 'uv' ];
429
+ scene.traverse( c => {
430
+
431
+ if ( c.isMesh ) {
432
+
433
+ const normalMapRequired = ! ! c.material.normalMap;
434
+ setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
435
+
436
+ }
437
+
438
+ } );
439
+
440
+ const textureSet = new Set();
441
+ const materials = staticGeometryGenerator.getMaterials();
442
+ materials.forEach( material => {
443
+
444
+ for ( const key in material ) {
445
+
446
+ const value = material[ key ];
447
+ if ( value && value.isTexture ) {
448
+
449
+ textureSet.add( value );
450
+
451
+ }
452
+
453
+ }
454
+
455
+ } );
456
+
457
+ staticGeometryGenerator.attributes = attributes;
458
+ staticGeometryGenerator.generate( geometry );
459
+
460
+ const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, materials, materials );
461
+ geometry.setAttribute( 'materialIndex', materialIndexAttribute );
462
+ geometry.clearGroups();
463
+
464
+ this.bvh = new MeshBVH( geometry );
465
+ this.materials = materials;
466
+ this.textures = Array.from( textureSet );
467
+
468
+ return {
469
+ bvh: this.bvh,
470
+ materials: this.materials,
471
+ textures: this.textures,
472
+ scene,
473
+ };
474
+
475
+ } else {
476
+
477
+ const { bvh } = this;
478
+ staticGeometryGenerator.generate( geometry );
479
+ bvh.refit();
480
+ return {
481
+ bvh: this.bvh,
482
+ materials: this.materials,
483
+ textures: this.textures,
484
+ scene,
485
+ };
486
+
487
+ }
488
+
489
+ }
490
+
491
+
357
492
  }
358
493
 
359
494
  // https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
@@ -613,6 +748,33 @@ class MaterialReducer {
613
748
 
614
749
  }
615
750
 
751
+ class PhysicalCamera extends PerspectiveCamera {
752
+
753
+ set bokehSize( size ) {
754
+
755
+ this.fStop = this.getFocalLength() / size;
756
+
757
+ }
758
+
759
+ get bokehSize() {
760
+
761
+ return this.getFocalLength() / this.fStop;
762
+
763
+ }
764
+
765
+ constructor( ...args ) {
766
+
767
+ super( ...args );
768
+ this.fStop = 1.4;
769
+ this.apertureBlades = 0;
770
+ this.apertureRotation = 0;
771
+ this.focusDistance = 25;
772
+ this.anamorphicRatio = 1;
773
+
774
+ }
775
+
776
+ }
777
+
616
778
  class MaterialStructUniform {
617
779
 
618
780
  constructor() {
@@ -646,6 +808,8 @@ class MaterialStructUniform {
646
808
  this.opacity = 1.0;
647
809
  this.alphaTest = 0.0;
648
810
 
811
+ this.side = 0;
812
+
649
813
  // TODO: Clearcoat
650
814
 
651
815
  // TODO: Sheen
@@ -705,6 +869,24 @@ class MaterialStructUniform {
705
869
 
706
870
  }
707
871
 
872
+ setSide( side ) {
873
+
874
+ switch ( side ) {
875
+
876
+ case DoubleSide:
877
+ this.side = 0;
878
+ break;
879
+ case FrontSide:
880
+ this.side = 1;
881
+ break;
882
+ case BackSide:
883
+ this.side = - 1;
884
+ break;
885
+
886
+ }
887
+
888
+ }
889
+
708
890
  }
709
891
 
710
892
  class MaterialStructArrayUniform extends Array {
@@ -850,6 +1032,16 @@ class MaterialBase extends ShaderMaterial {
850
1032
 
851
1033
  const shaderMaterialStructs = /* glsl */ `
852
1034
 
1035
+ struct PhysicalCamera {
1036
+
1037
+ float focusDistance;
1038
+ float anamorphicRatio;
1039
+ float bokehSize;
1040
+ int apertureBlades;
1041
+ float apertureRotation;
1042
+
1043
+ };
1044
+
853
1045
  struct Material {
854
1046
 
855
1047
  vec3 color;
@@ -875,6 +1067,8 @@ const shaderMaterialStructs = /* glsl */ `
875
1067
  float opacity;
876
1068
  float alphaTest;
877
1069
 
1070
+ float side;
1071
+
878
1072
  };
879
1073
 
880
1074
  `;
@@ -1419,6 +1613,7 @@ const shaderUtils = /* glsl */`
1419
1613
 
1420
1614
  }
1421
1615
 
1616
+ // returns [ 0, 1 ]
1422
1617
  float rand() {
1423
1618
 
1424
1619
  pcg4d(s0);
@@ -1458,8 +1653,94 @@ const shaderUtils = /* glsl */`
1458
1653
  return vec3( f * cos( t ), f * sin( t ), u );
1459
1654
 
1460
1655
  }
1656
+
1657
+ vec2 triangleSample( vec2 a, vec2 b, vec2 c ) {
1658
+
1659
+ // get the edges of the triangle and the diagonal across the
1660
+ // center of the parallelogram
1661
+ vec2 e1 = a - b;
1662
+ vec2 e2 = c - b;
1663
+ vec2 diag = normalize( e1 + e2 );
1664
+
1665
+ // pick a random point in the parallelogram
1666
+ vec2 r = rand2();
1667
+ if ( r.x + r.y > 1.0 ) {
1668
+
1669
+ r = vec2( 1.0 ) - r;
1670
+
1671
+ }
1672
+
1673
+ return e1 * r.x + e2 * r.y;
1674
+
1675
+ }
1676
+
1677
+ // samples an aperture shape with the given number of sides. 0 means circle
1678
+ vec2 sampleAperture( int blades ) {
1679
+
1680
+ if ( blades == 0 ) {
1681
+
1682
+ vec2 r = rand2();
1683
+ float angle = 2.0 * PI * r.x;
1684
+ float radius = sqrt( rand() );
1685
+ return vec2( cos( angle ), sin( angle ) ) * radius;
1686
+
1687
+ } else {
1688
+
1689
+ blades = max( blades, 3 );
1690
+
1691
+ vec3 r = rand3();
1692
+ float anglePerSegment = 2.0 * PI / float( blades );
1693
+ float segment = floor( float( blades ) * r.x );
1694
+
1695
+ float angle1 = anglePerSegment * segment;
1696
+ float angle2 = angle1 + anglePerSegment;
1697
+ vec2 a = vec2( sin( angle1 ), cos( angle1 ) );
1698
+ vec2 b = vec2( 0.0, 0.0 );
1699
+ vec2 c = vec2( sin( angle2 ), cos( angle2 ) );
1700
+
1701
+ return triangleSample( a, b, c );
1702
+
1703
+ }
1704
+
1705
+ }
1461
1706
  `;
1462
1707
 
1708
+ class PhysicalCameraUniform {
1709
+
1710
+ constructor() {
1711
+
1712
+ this.bokehSize = 0;
1713
+ this.apertureBlades = 0;
1714
+ this.apertureRotation = 0;
1715
+ this.focusDistance = 10;
1716
+ this.anamorphicRatio = 1;
1717
+
1718
+ }
1719
+
1720
+ updateFrom( camera ) {
1721
+
1722
+ if ( camera instanceof PhysicalCamera ) {
1723
+
1724
+ this.bokehSize = camera.bokehSize;
1725
+ this.apertureBlades = camera.apertureBlades;
1726
+ this.apertureRotation = camera.apertureRotation;
1727
+ this.focusDistance = camera.focusDistance;
1728
+ this.anamorphicRatio = camera.anamorphicRatio;
1729
+
1730
+ } else {
1731
+
1732
+ this.bokehSize = 0;
1733
+ this.apertureRotation = 0;
1734
+ this.apertureBlades = 0;
1735
+ this.focusDistance = 10;
1736
+ this.anamorphicRatio = 1;
1737
+
1738
+ }
1739
+
1740
+ }
1741
+
1742
+ }
1743
+
1463
1744
  class PhysicalPathTracingMaterial extends MaterialBase {
1464
1745
 
1465
1746
  // three.js relies on this field to add env map functions and defines
@@ -1477,13 +1758,16 @@ class PhysicalPathTracingMaterial extends MaterialBase {
1477
1758
  depthWrite: false,
1478
1759
 
1479
1760
  defines: {
1480
- BOUNCES: 3,
1761
+ DOF_SUPPORT: 1,
1481
1762
  TRANSPARENT_TRAVERSALS: 5,
1482
1763
  MATERIAL_LENGTH: 0,
1483
1764
  GRADIENT_BG: 0,
1484
1765
  },
1485
1766
 
1486
1767
  uniforms: {
1768
+ bounces: { value: 3 },
1769
+ physicalCamera: { value: new PhysicalCameraUniform() },
1770
+
1487
1771
  bvh: { value: new MeshBVHUniformStruct() },
1488
1772
  normalAttribute: { value: new FloatVertexAttributeTexture() },
1489
1773
  tangentAttribute: { value: new FloatVertexAttributeTexture() },
@@ -1560,6 +1844,14 @@ class PhysicalPathTracingMaterial extends MaterialBase {
1560
1844
 
1561
1845
  #endif
1562
1846
 
1847
+ #if DOF_SUPPORT
1848
+
1849
+ uniform PhysicalCamera physicalCamera;
1850
+
1851
+ #endif
1852
+
1853
+ uniform int bounces;
1854
+
1563
1855
  uniform mat4 cameraWorldMatrix;
1564
1856
  uniform mat4 invProjectionMatrix;
1565
1857
  uniform sampler2D normalAttribute;
@@ -1584,6 +1876,30 @@ class PhysicalPathTracingMaterial extends MaterialBase {
1584
1876
  vec3 rayOrigin, rayDirection;
1585
1877
  ndcToCameraRay( ndc, cameraWorldMatrix, invProjectionMatrix, rayOrigin, rayDirection );
1586
1878
 
1879
+ #if DOF_SUPPORT
1880
+
1881
+ // depth of field
1882
+ vec3 focalPoint = rayOrigin + normalize( rayDirection ) * physicalCamera.focusDistance;
1883
+
1884
+ // get the aperture sample
1885
+ vec2 apertureSample = sampleAperture( physicalCamera.apertureBlades ) * physicalCamera.bokehSize * 0.5 * 1e-3;
1886
+
1887
+ // rotate the aperture shape
1888
+ float ac = cos( physicalCamera.apertureRotation );
1889
+ float as = sin( physicalCamera.apertureRotation );
1890
+ apertureSample = vec2(
1891
+ apertureSample.x * ac - apertureSample.y * as,
1892
+ apertureSample.x * as + apertureSample.y * ac
1893
+ );
1894
+ apertureSample.x *= saturate( physicalCamera.anamorphicRatio );
1895
+ apertureSample.y *= saturate( 1.0 / physicalCamera.anamorphicRatio );
1896
+
1897
+ #endif
1898
+
1899
+ // create the new ray
1900
+ rayOrigin += ( cameraWorldMatrix * vec4( apertureSample, 0.0, 0.0 ) ).xyz;
1901
+ rayDirection = focalPoint - rayOrigin;
1902
+
1587
1903
  // Lambertian render
1588
1904
  gl_FragColor = vec4( 0.0 );
1589
1905
 
@@ -1598,7 +1914,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
1598
1914
  float accumulatedRoughness = 0.0;
1599
1915
  int i;
1600
1916
  int transparentTraversals = TRANSPARENT_TRAVERSALS;
1601
- for ( i = 0; i < BOUNCES; i ++ ) {
1917
+ for ( i = 0; i < bounces; i ++ ) {
1602
1918
 
1603
1919
  if ( ! bvhIntersectFirstHit( bvh, rayOrigin, rayDirection, faceIndices, faceNormal, barycoord, side, dist ) ) {
1604
1920
 
@@ -1649,12 +1965,23 @@ class PhysicalPathTracingMaterial extends MaterialBase {
1649
1965
 
1650
1966
  }
1651
1967
 
1652
- // possibly skip this sample if it's transparent or alpha test is enabled
1653
- // alpha test is disabled when it === 0
1968
+ // possibly skip this sample if it's transparent, alpha test is enabled, or we hit the wrong material side
1969
+ // and it's single sided.
1970
+ // - alpha test is disabled when it === 0
1971
+ // - the material sidedness test is complicated because we want light to pass through the back side but still
1972
+ // be able to see the front side. This boolean checks if the side we hit is the front side on the first ray
1973
+ // and we're rendering the other then we skip it. Do the opposite on subsequent bounces to get incoming light.
1654
1974
  float alphaTest = material.alphaTest;
1655
1975
  bool useAlphaTest = alphaTest != 0.0;
1976
+ bool isFirstHit = i == 0;
1656
1977
  if (
1657
- useAlphaTest && albedo.a < alphaTest
1978
+ // material sidedness
1979
+ material.side != 0.0 && ( side != material.side ) == isFirstHit
1980
+
1981
+ // alpha test
1982
+ || useAlphaTest && albedo.a < alphaTest
1983
+
1984
+ // opacity
1658
1985
  || ! useAlphaTest && albedo.a < rand()
1659
1986
  ) {
1660
1987
 
@@ -1738,7 +2065,6 @@ class PhysicalPathTracingMaterial extends MaterialBase {
1738
2065
  SurfaceRec surfaceRec;
1739
2066
  surfaceRec.normal = normal;
1740
2067
  surfaceRec.faceNormal = faceNormal;
1741
- surfaceRec.frontFace = side == 1.0;
1742
2068
  surfaceRec.transmission = transmission;
1743
2069
  surfaceRec.ior = material.ior;
1744
2070
  surfaceRec.emission = emission;
@@ -1746,6 +2072,10 @@ class PhysicalPathTracingMaterial extends MaterialBase {
1746
2072
  surfaceRec.color = albedo.rgb;
1747
2073
  surfaceRec.roughness = roughness;
1748
2074
 
2075
+ // frontFace is used to determine transmissive properties and PDF. If no transmission is used
2076
+ // then we can just always assume this is a front face.
2077
+ surfaceRec.frontFace = side == 1.0 || transmission == 0.0;
2078
+
1749
2079
  // Compute the filtered roughness value to use during specular reflection computations. A minimum
1750
2080
  // value of 1e-6 is needed because the GGX functions do not work with a roughness value of 0 and
1751
2081
  // the accumulated roughness value is scaled by a user setting and a "magic value" of 5.0.
@@ -1821,5 +2151,5 @@ class PhysicalPathTracingMaterial extends MaterialBase {
1821
2151
 
1822
2152
  // core
1823
2153
 
1824
- export { MaterialBase, MaterialReducer, MaterialStructArrayUniform, MaterialStructUniform, PathTracingRenderer, PathTracingSceneGenerator, PhysicalPathTracingMaterial, RenderTarget2DArray, mergeMeshes, shaderMaterialSampling, shaderMaterialStructs, shaderUtils };
2154
+ export { DynamicPathTracingSceneGenerator, MaterialBase, MaterialReducer, MaterialStructArrayUniform, MaterialStructUniform, PathTracingRenderer, PathTracingSceneGenerator, PhysicalCamera, PhysicalPathTracingMaterial, RenderTarget2DArray, getGroupMaterialIndicesAttribute, mergeMeshes, setCommonAttributes, shaderMaterialSampling, shaderMaterialStructs, shaderUtils, trimToAttributes };
1825
2155
  //# sourceMappingURL=index.module.js.map