three-gpu-pathtracer 0.0.22 → 0.0.24

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,4 +1,4 @@
1
- import { BufferAttribute, BufferGeometry, Matrix4, Vector3, Vector4, Matrix3, MeshBasicMaterial, Mesh, ShaderMaterial, NoBlending, Vector2, WebGLRenderTarget, FloatType, RGBAFormat, NearestFilter, PerspectiveCamera, DataUtils, HalfFloatType, Source, DataTexture, LinearFilter, RepeatWrapping, RedFormat, ClampToEdgeWrapping, Quaternion, DataArrayTexture, DoubleSide, BackSide, FrontSide, Color, WebGLArrayRenderTarget, UnsignedByteType, NoToneMapping, RGFormat, NormalBlending, Spherical, EquirectangularReflectionMapping, Clock, Scene, AdditiveBlending, Camera, SpotLight, RectAreaLight, PMREMGenerator, MeshStandardMaterial } from 'three';
1
+ import { BufferAttribute, BufferGeometry, Matrix4, Vector3, Vector4, Matrix3, MeshBasicMaterial, Mesh, ShaderMaterial, NoBlending, Vector2, WebGLRenderTarget, FloatType, RGBAFormat, NearestFilter, PerspectiveCamera, DataUtils, HalfFloatType, Source, DataTexture, LinearFilter, RepeatWrapping, RedFormat, ClampToEdgeWrapping, Quaternion, DataArrayTexture, DoubleSide, BackSide, FrontSide, Color, WebGLArrayRenderTarget, UnsignedByteType, NoToneMapping, RGFormat, NormalBlending, Spherical, EquirectangularReflectionMapping, LinearMipMapLinearFilter, Clock, Scene, AdditiveBlending, Camera, SpotLight, RectAreaLight, PMREMGenerator, MeshStandardMaterial } from 'three';
2
2
  import { SAH, MeshBVH, FloatVertexAttributeTexture, MeshBVHUniformStruct, UIntVertexAttributeTexture, BVHShaderGLSL } from 'three-mesh-bvh';
3
3
  import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
4
4
 
@@ -43,12 +43,18 @@ function createAttributeClone( attr, countOverride = null ) {
43
43
 
44
44
  }
45
45
 
46
- // Confirms that the two provided attributes are compatible
46
+ // Confirms that the two provided attributes are compatible. Returns false if they are not.
47
47
  function validateAttributes( attr1, attr2 ) {
48
48
 
49
49
  if ( ! attr1 && ! attr2 ) {
50
50
 
51
- return;
51
+ return true;
52
+
53
+ }
54
+
55
+ if ( Boolean( attr1 ) !== Boolean( attr2 ) ) {
56
+
57
+ return false;
52
58
 
53
59
  }
54
60
 
@@ -59,10 +65,12 @@ function validateAttributes( attr1, attr2 ) {
59
65
 
60
66
  if ( ! sameCount || ! sameNormalized || ! sameType || ! sameItemSize ) {
61
67
 
62
- throw new Error();
68
+ return false;
63
69
 
64
70
  }
65
71
 
72
+ return true;
73
+
66
74
  }
67
75
 
68
76
  function validateMergeability( geometries ) {
@@ -269,7 +277,20 @@ function mergeGeometries( geometries, options = {}, targetGeometry = new BufferG
269
277
  const attr = geometry.getAttribute( key );
270
278
  if ( ! skip ) {
271
279
 
272
- copyAttributeContents( attr, targetAttribute, offset );
280
+ if ( key === 'color' && targetAttribute.itemSize !== attr.itemSize ) {
281
+
282
+ // make sure the color attribute is aligned with itemSize 3 to 4
283
+ for ( let index = offset, l = attr.count; index < l; index ++ ) {
284
+
285
+ attr.setXYZW( index, targetAttribute.getX( index ), targetAttribute.getY( index ), targetAttribute.getZ( index ), 1.0 );
286
+
287
+ }
288
+
289
+ } else {
290
+
291
+ copyAttributeContents( attr, targetAttribute, offset );
292
+
293
+ }
273
294
 
274
295
  }
275
296
 
@@ -430,29 +451,22 @@ function bufferToHash( buffer ) {
430
451
 
431
452
  }
432
453
 
433
- function attributeSort( a, b ) {
434
-
435
- if ( a.uuid > b.uuid ) return 1;
436
- if ( a.uuid < b.uuid ) return - 1;
437
- return 0;
438
-
439
- }
440
-
441
454
  function getGeometryHash( geometry ) {
442
455
 
443
- let hash = '';
456
+ let hash = geometry.uuid;
444
457
  const attributes = Object.values( geometry.attributes );
445
458
  if ( geometry.index ) {
446
459
 
447
460
  attributes.push( geometry.index );
461
+ hash += `index|${ geometry.index.version }`;
448
462
 
449
463
  }
450
464
 
451
- attributes.sort( attributeSort );
465
+ const keys = Object.keys( attributes ).sort();
466
+ for ( const key of keys ) {
452
467
 
453
- for ( const attr of attributes ) {
454
-
455
- hash += `${ attr.uuid }_${ attr.version }|`;
468
+ const attr = attributes[ key ];
469
+ hash += `${ key }_${ attr.version }|`;
456
470
 
457
471
  }
458
472
 
@@ -883,6 +897,28 @@ class BakedGeometry extends BufferGeometry {
883
897
 
884
898
  }
885
899
 
900
+ // returns whether the passed mesh is compatible with this baked geometry
901
+ // such that it can be updated without resizing attributes
902
+ isCompatible( mesh, attributes ) {
903
+
904
+ const geometry = mesh.geometry;
905
+ for ( let i = 0; i < attributes.length; i ++ ) {
906
+
907
+ const key = attributes[ i ];
908
+ const attr1 = geometry.attributes[ key ];
909
+ const attr2 = this.attributes[ key ];
910
+ if ( attr1 && ! validateAttributes( attr1, attr2 ) ) {
911
+
912
+ return false;
913
+
914
+ }
915
+
916
+ }
917
+
918
+ return true;
919
+
920
+ }
921
+
886
922
  updateFrom( mesh, options ) {
887
923
 
888
924
  const diff = this._diff;
@@ -1072,15 +1108,24 @@ class StaticGeometryGenerator {
1072
1108
  unusedMeshKeys.delete( meshKey );
1073
1109
 
1074
1110
  // initialize the intermediate geometry
1075
- if ( ! _intermediateGeometry.has( meshKey ) ) {
1111
+ // if the mesh and source geometry have changed in such a way that they are no longer
1112
+ // compatible then regenerate the baked geometry from scratch
1113
+ let geom = _intermediateGeometry.get( meshKey );
1114
+ if ( ! geom || ! geom.isCompatible( mesh, this.attributes ) ) {
1115
+
1116
+ if ( geom ) {
1076
1117
 
1077
- _intermediateGeometry.set( meshKey, new BakedGeometry() );
1118
+ geom.dispose();
1119
+
1120
+ }
1121
+
1122
+ geom = new BakedGeometry();
1123
+ _intermediateGeometry.set( meshKey, geom );
1078
1124
 
1079
1125
  }
1080
1126
 
1081
1127
  // transform the geometry into the intermediate buffer geometry, saving whether
1082
1128
  // or not it changed.
1083
- const geom = _intermediateGeometry.get( meshKey );
1084
1129
  if ( geom.updateFrom( mesh, convertOptions ) ) {
1085
1130
 
1086
1131
  // TODO: provide option for only generating the set of attributes that are present
@@ -1132,6 +1177,12 @@ class StaticGeometryGenerator {
1132
1177
 
1133
1178
  // get the list of geometries to merge
1134
1179
  let forceUpdate = false;
1180
+ if ( meshes.length !== previousMergeInfo.length ) {
1181
+
1182
+ forceUpdate = true;
1183
+
1184
+ }
1185
+
1135
1186
  for ( let i = 0, l = meshes.length; i < l; i ++ ) {
1136
1187
 
1137
1188
  const mesh = meshes[ i ];
@@ -1277,6 +1328,7 @@ class PathTracingSceneGenerator {
1277
1328
  this._bvhWorker = null;
1278
1329
  this._pendingGenerate = null;
1279
1330
  this._buildAsync = false;
1331
+ this._materialUuids = null;
1280
1332
 
1281
1333
  }
1282
1334
 
@@ -1358,12 +1410,29 @@ class PathTracingSceneGenerator {
1358
1410
  // generate the geometry
1359
1411
  const result = staticGeometryGenerator.generate( geometry );
1360
1412
  const materials = result.materials;
1413
+ let needsMaterialIndexUpdate = result.changeType !== NO_CHANGE || this._materialUuids === null || this._materialUuids.length !== length;
1414
+ if ( ! needsMaterialIndexUpdate ) {
1415
+
1416
+ for ( let i = 0, length = materials.length; i < length; i ++ ) {
1417
+
1418
+ const material = materials[ i ];
1419
+ if ( material.uuid !== this._materialUuids[ i ] ) {
1420
+
1421
+ needsMaterialIndexUpdate = true;
1422
+ break;
1423
+
1424
+ }
1425
+
1426
+ }
1427
+
1428
+ }
1429
+
1361
1430
  const textures = getTextures$1( materials );
1362
1431
  const { lights, iesTextures } = getLights$1( objects );
1363
-
1364
- if ( result.changeType !== NO_CHANGE ) {
1432
+ if ( needsMaterialIndexUpdate ) {
1365
1433
 
1366
1434
  updateMaterialIndexAttribute( geometry, materials, materials );
1435
+ this._materialUuids = materials.map( material => material.uuid );
1367
1436
 
1368
1437
  }
1369
1438
 
@@ -1407,6 +1476,7 @@ class PathTracingSceneGenerator {
1407
1476
  return {
1408
1477
  bvhChanged: result.changeType !== NO_CHANGE,
1409
1478
  bvh: this.bvh,
1479
+ needsMaterialIndexUpdate,
1410
1480
  lights,
1411
1481
  iesTextures,
1412
1482
  geometry,
@@ -1443,6 +1513,17 @@ class PathTracingSceneWorker extends PathTracingSceneGenerator {
1443
1513
 
1444
1514
  class MaterialBase extends ShaderMaterial {
1445
1515
 
1516
+ set needsUpdate( v ) {
1517
+
1518
+ super.needsUpdate = true;
1519
+ this.dispatchEvent( {
1520
+
1521
+ type: 'recompilation',
1522
+
1523
+ } );
1524
+
1525
+ }
1526
+
1446
1527
  constructor( shader ) {
1447
1528
 
1448
1529
  super( shader );
@@ -2823,7 +2904,7 @@ function getLights( scene ) {
2823
2904
 
2824
2905
  }
2825
2906
 
2826
- const MATERIAL_PIXELS = 45;
2907
+ const MATERIAL_PIXELS = 47;
2827
2908
  const MATERIAL_STRIDE = MATERIAL_PIXELS * 4;
2828
2909
 
2829
2910
  class MaterialFeatures {
@@ -3248,6 +3329,9 @@ class MaterialsTexture extends DataTexture {
3248
3329
  // specularIntensityMap transform 43
3249
3330
  index += writeTextureMatrixToArray( m, 'specularIntensityMap', floatArray, index );
3250
3331
 
3332
+ // alphaMap transform 45
3333
+ index += writeTextureMatrixToArray( m, 'alphaMap', floatArray, index );
3334
+
3251
3335
  }
3252
3336
 
3253
3337
  // check if the contents have changed
@@ -3443,11 +3527,11 @@ class CopyMaterial extends ShaderMaterial {
3443
3527
  // - https://github.com/hoverinc/ray-tracing-renderer
3444
3528
  // - http://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Stratified_Sampling.html
3445
3529
 
3446
- function shuffle( arr ) {
3530
+ function shuffle( arr, random = Math.random() ) {
3447
3531
 
3448
3532
  for ( let i = arr.length - 1; i > 0; i -- ) {
3449
3533
 
3450
- const j = Math.floor( Math.random() * ( i + 1 ) );
3534
+ const j = Math.floor( random() * ( i + 1 ) );
3451
3535
  const x = arr[ i ];
3452
3536
  arr[ i ] = arr[ j ];
3453
3537
  arr[ j ] = x;
@@ -3462,7 +3546,7 @@ function shuffle( arr ) {
3462
3546
  // dimensions : The number of dimensions to generate stratified values for
3463
3547
  class StratifiedSampler {
3464
3548
 
3465
- constructor( strataCount, dimensions ) {
3549
+ constructor( strataCount, dimensions, random = Math.random ) {
3466
3550
 
3467
3551
  const l = strataCount ** dimensions;
3468
3552
  const strata = new Uint16Array( l );
@@ -3479,7 +3563,19 @@ class StratifiedSampler {
3479
3563
 
3480
3564
  this.strataCount = strataCount;
3481
3565
 
3482
- this.restart = function () {
3566
+ this.reset = function () {
3567
+
3568
+ for ( let i = 0; i < l; i ++ ) {
3569
+
3570
+ strata[ i ] = i;
3571
+
3572
+ }
3573
+
3574
+ index = 0;
3575
+
3576
+ };
3577
+
3578
+ this.reshuffle = function () {
3483
3579
 
3484
3580
  index = 0;
3485
3581
 
@@ -3491,8 +3587,8 @@ class StratifiedSampler {
3491
3587
 
3492
3588
  if ( index >= strata.length ) {
3493
3589
 
3494
- shuffle( strata );
3495
- this.restart();
3590
+ shuffle( strata, random );
3591
+ this.reshuffle();
3496
3592
 
3497
3593
  }
3498
3594
 
@@ -3500,7 +3596,7 @@ class StratifiedSampler {
3500
3596
 
3501
3597
  for ( let i = 0; i < dimensions; i ++ ) {
3502
3598
 
3503
- samples[ i ] = ( stratum % strataCount + Math.random() ) / strataCount;
3599
+ samples[ i ] = ( stratum % strataCount + random() ) / strataCount;
3504
3600
  stratum = Math.floor( stratum / strataCount );
3505
3601
 
3506
3602
  }
@@ -3518,7 +3614,7 @@ class StratifiedSampler {
3518
3614
  // Stratified set of data with each tuple stratified separately and combined
3519
3615
  class StratifiedSamplerCombined {
3520
3616
 
3521
- constructor( strataCount, listOfDimensions ) {
3617
+ constructor( strataCount, listOfDimensions, random = Math.random ) {
3522
3618
 
3523
3619
  let totalDim = 0;
3524
3620
  for ( const dim of listOfDimensions ) {
@@ -3532,7 +3628,7 @@ class StratifiedSamplerCombined {
3532
3628
  let offset = 0;
3533
3629
  for ( const dim of listOfDimensions ) {
3534
3630
 
3535
- const sampler = new StratifiedSampler( strataCount, dim );
3631
+ const sampler = new StratifiedSampler( strataCount, dim, random );
3536
3632
  sampler.samples = new Float32Array( combined.buffer, offset, sampler.samples.length );
3537
3633
  offset += sampler.samples.length * 4;
3538
3634
  strataObjs.push( sampler );
@@ -3555,11 +3651,21 @@ class StratifiedSamplerCombined {
3555
3651
 
3556
3652
  };
3557
3653
 
3558
- this.restart = function () {
3654
+ this.reshuffle = function () {
3559
3655
 
3560
3656
  for ( const strata of strataObjs ) {
3561
3657
 
3562
- strata.restart();
3658
+ strata.reshuffle();
3659
+
3660
+ }
3661
+
3662
+ };
3663
+
3664
+ this.reset = function () {
3665
+
3666
+ for ( const strata of strataObjs ) {
3667
+
3668
+ strata.reset();
3563
3669
 
3564
3670
  }
3565
3671
 
@@ -3569,6 +3675,36 @@ class StratifiedSamplerCombined {
3569
3675
 
3570
3676
  }
3571
3677
 
3678
+ // https://stackoverflow.com/questions/424292/seedable-javascript-random-number-generator
3679
+ class RandomGenerator {
3680
+
3681
+ constructor( seed = 0 ) {
3682
+
3683
+ // LCG using GCC's constants
3684
+ this.m = 0x80000000; // 2**31;
3685
+ this.a = 1103515245;
3686
+ this.c = 12345;
3687
+
3688
+ this.seed = seed;
3689
+
3690
+ }
3691
+
3692
+ nextInt() {
3693
+
3694
+ this.seed = ( this.a * this.seed + this.c ) % this.m;
3695
+ return this.seed;
3696
+
3697
+ }
3698
+
3699
+ nextFloat() {
3700
+
3701
+ // returns in range [0,1]
3702
+ return this.nextInt() / ( this.m - 1 );
3703
+
3704
+ }
3705
+
3706
+ }
3707
+
3572
3708
  class StratifiedSamplesTexture extends DataTexture {
3573
3709
 
3574
3710
  constructor( count = 1, depth = 1, strata = 8 ) {
@@ -3579,22 +3715,37 @@ class StratifiedSamplesTexture extends DataTexture {
3579
3715
 
3580
3716
  this.strata = strata;
3581
3717
  this.sampler = null;
3718
+ this.generator = new RandomGenerator();
3719
+ this.stableNoise = false;
3720
+ this.random = () => {
3721
+
3722
+ if ( this.stableNoise ) {
3723
+
3724
+ return this.generator.nextFloat();
3725
+
3726
+ } else {
3727
+
3728
+ return Math.random();
3729
+
3730
+ }
3731
+
3732
+ };
3582
3733
 
3583
3734
  this.init( count, depth, strata );
3584
3735
 
3585
3736
  }
3586
3737
 
3587
- init( count, depth, strata = this.strata ) {
3738
+ init( count = this.image.height, depth = this.image.width, strata = this.strata ) {
3588
3739
 
3589
3740
  const { image } = this;
3590
- if ( image.width === depth && image.height === count ) {
3741
+ if ( image.width === depth && image.height === count && this.sampler !== null ) {
3591
3742
 
3592
3743
  return;
3593
3744
 
3594
3745
  }
3595
3746
 
3596
3747
  const dimensions = new Array( count * depth ).fill( 4 );
3597
- const sampler = new StratifiedSamplerCombined( strata, dimensions );
3748
+ const sampler = new StratifiedSamplerCombined( strata, dimensions, this.random );
3598
3749
 
3599
3750
  image.width = depth;
3600
3751
  image.height = count;
@@ -3614,6 +3765,13 @@ class StratifiedSamplesTexture extends DataTexture {
3614
3765
 
3615
3766
  }
3616
3767
 
3768
+ reset() {
3769
+
3770
+ this.sampler.reset();
3771
+ this.generator.seed = 0;
3772
+
3773
+ }
3774
+
3617
3775
  }
3618
3776
 
3619
3777
  function shuffleArray( array, random = Math.random ) {
@@ -4253,6 +4411,7 @@ const material_struct = /* glsl */ `
4253
4411
  mat3 iridescenceThicknessMapTransform;
4254
4412
  mat3 specularColorMapTransform;
4255
4413
  mat3 specularIntensityMapTransform;
4414
+ mat3 alphaMapTransform;
4256
4415
 
4257
4416
  };
4258
4417
 
@@ -4273,7 +4432,7 @@ const material_struct = /* glsl */ `
4273
4432
 
4274
4433
  Material readMaterialInfo( sampler2D tex, uint index ) {
4275
4434
 
4276
- uint i = index * 45u;
4435
+ uint i = index * uint( MATERIAL_PIXELS );
4277
4436
 
4278
4437
  vec4 s0 = texelFetch1D( tex, i + 0u );
4279
4438
  vec4 s1 = texelFetch1D( tex, i + 1u );
@@ -4372,6 +4531,7 @@ const material_struct = /* glsl */ `
4372
4531
  m.iridescenceThicknessMapTransform = m.iridescenceThicknessMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 24u );
4373
4532
  m.specularColorMapTransform = m.specularColorMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 26u );
4374
4533
  m.specularIntensityMapTransform = m.specularIntensityMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 28u );
4534
+ m.alphaMapTransform = m.alphaMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 30u );
4375
4535
 
4376
4536
  return m;
4377
4537
 
@@ -6138,7 +6298,7 @@ const inside_fog_volume_function = /* glsl */`
6138
6298
  // returns whether the given material is a fog material or not
6139
6299
  bool isMaterialFogVolume( sampler2D materials, uint materialIndex ) {
6140
6300
 
6141
- uint i = materialIndex * 45u;
6301
+ uint i = materialIndex * uint( MATERIAL_PIXELS );
6142
6302
  vec4 s14 = texelFetch1D( materials, i + 14u );
6143
6303
  return bool( int( s14.b ) & 4 );
6144
6304
 
@@ -6368,7 +6528,8 @@ const attenuate_hit_function = /* glsl */`
6368
6528
  // alphaMap
6369
6529
  if ( material.alphaMap != - 1 ) {
6370
6530
 
6371
- albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
6531
+ vec3 uvPrime = material.alphaMapTransform * vec3( uv, 1 );
6532
+ albedo.a *= texture2D( textures, vec3( uvPrime.xy, material.alphaMap ) ).x;
6372
6533
 
6373
6534
  }
6374
6535
 
@@ -6510,7 +6671,7 @@ const camera_util_functions = /* glsl */`
6510
6671
  vec3 shapeUVW= rand3( 1 );
6511
6672
  int blades = physicalCamera.apertureBlades;
6512
6673
  float anamorphicRatio = physicalCamera.anamorphicRatio;
6513
- vec2 apertureSample = blades == 0 ? sampleCircle( shapeUVW.xy ) : sampleRegularPolygon( blades, shapeUVW );
6674
+ vec2 apertureSample = sampleAperture( blades, shapeUVW );
6514
6675
  apertureSample *= physicalCamera.bokehSize * 0.5 * 1e-3;
6515
6676
 
6516
6677
  // rotate the aperture shape
@@ -6680,7 +6841,8 @@ const get_surface_record_function = /* glsl */`
6680
6841
  // alphaMap
6681
6842
  if ( material.alphaMap != - 1 ) {
6682
6843
 
6683
- albedo.a *= texture2D( textures, vec3( uv, material.alphaMap ) ).x;
6844
+ vec3 uvPrime = material.alphaMapTransform * vec3( uv, 1 );
6845
+ albedo.a *= texture2D( textures, vec3( uvPrime.xy, material.alphaMap ) ).x;
6684
6846
 
6685
6847
  }
6686
6848
 
@@ -7095,6 +7257,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7095
7257
  ATTR_TANGENT: 1,
7096
7258
  ATTR_UV: 2,
7097
7259
  ATTR_COLOR: 3,
7260
+ MATERIAL_PIXELS: MATERIAL_PIXELS,
7098
7261
  },
7099
7262
 
7100
7263
  uniforms: {
@@ -7777,6 +7940,9 @@ class PathTracingRenderer {
7777
7940
 
7778
7941
  set material( v ) {
7779
7942
 
7943
+ this._fsQuad.material.removeEventListener( 'recompilation', this._compileFunction );
7944
+ v.addEventListener( 'recompilation', this._compileFunction );
7945
+
7780
7946
  this._fsQuad.material = v;
7781
7947
 
7782
7948
  }
@@ -7813,6 +7979,12 @@ class PathTracingRenderer {
7813
7979
 
7814
7980
  }
7815
7981
 
7982
+ get isCompiling() {
7983
+
7984
+ return Boolean( this._compilePromise );
7985
+
7986
+ }
7987
+
7816
7988
  constructor( renderer ) {
7817
7989
 
7818
7990
  this.camera = null;
@@ -7830,6 +8002,7 @@ class PathTracingRenderer {
7830
8002
  this._blendQuad = new FullScreenQuad( new BlendMaterial() );
7831
8003
  this._task = null;
7832
8004
  this._currentTile = 0;
8005
+ this._compilePromise = null;
7833
8006
 
7834
8007
  this._sobolTarget = new SobolNumberMapGenerator().generate( renderer );
7835
8008
 
@@ -7854,6 +8027,33 @@ class PathTracingRenderer {
7854
8027
  } ),
7855
8028
  ];
7856
8029
 
8030
+ // function for listening to for triggered compilation so we can wait for compilation to finish
8031
+ // before starting to render
8032
+ this._compileFunction = () => {
8033
+
8034
+ const promise = this.compileMaterial( this._fsQuad._mesh );
8035
+ promise.then( () => {
8036
+
8037
+ if ( this._compilePromise === promise ) {
8038
+
8039
+ this._compilePromise = null;
8040
+
8041
+ }
8042
+
8043
+ } );
8044
+
8045
+ this._compilePromise = promise;
8046
+
8047
+ };
8048
+
8049
+ this.material.addEventListener( 'recompilation', this._compileFunction );
8050
+
8051
+ }
8052
+
8053
+ compileMaterial() {
8054
+
8055
+ return this._renderer.compileAsync( this._fsQuad._mesh );
8056
+
7857
8057
  }
7858
8058
 
7859
8059
  setCamera( camera ) {
@@ -7885,7 +8085,6 @@ class PathTracingRenderer {
7885
8085
  material.setDefine( 'CAMERA_TYPE', cameraType );
7886
8086
 
7887
8087
  this.camera = camera;
7888
- // this.reset();
7889
8088
 
7890
8089
  }
7891
8090
 
@@ -7952,9 +8151,11 @@ class PathTracingRenderer {
7952
8151
  this.samples = 0;
7953
8152
  this._task = null;
7954
8153
 
8154
+ this.material.stratifiedTexture.stableNoise = this.stableNoise;
7955
8155
  if ( this.stableNoise ) {
7956
8156
 
7957
8157
  this.material.seed = 0;
8158
+ this.material.stratifiedTexture.reset();
7958
8159
 
7959
8160
  }
7960
8161
 
@@ -7962,6 +8163,15 @@ class PathTracingRenderer {
7962
8163
 
7963
8164
  update() {
7964
8165
 
8166
+ // ensure we've updated our defines before rendering so we can ensure we
8167
+ // can wait for compilation to finish
8168
+ this.material.onBeforeRender();
8169
+ if ( this.isCompiling ) {
8170
+
8171
+ return;
8172
+
8173
+ }
8174
+
7965
8175
  if ( ! this._task ) {
7966
8176
 
7967
8177
  this._task = renderTask.call( this );
@@ -8181,6 +8391,150 @@ class ClampedInterpolationMaterial extends ShaderMaterial {
8181
8391
 
8182
8392
  }
8183
8393
 
8394
+ class CubeToEquirectMaterial extends ShaderMaterial {
8395
+
8396
+ constructor() {
8397
+
8398
+ super( {
8399
+
8400
+ uniforms: {
8401
+
8402
+ envMap: { value: null },
8403
+ flipEnvMap: { value: - 1 },
8404
+
8405
+ },
8406
+
8407
+ vertexShader: /* glsl */`
8408
+ varying vec2 vUv;
8409
+ void main() {
8410
+
8411
+ vUv = uv;
8412
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
8413
+
8414
+ }`,
8415
+
8416
+ fragmentShader: /* glsl */`
8417
+ #define ENVMAP_TYPE_CUBE_UV
8418
+
8419
+ uniform samplerCube envMap;
8420
+ uniform float flipEnvMap;
8421
+ varying vec2 vUv;
8422
+
8423
+ #include <common>
8424
+ #include <cube_uv_reflection_fragment>
8425
+
8426
+ ${ util_functions }
8427
+
8428
+ void main() {
8429
+
8430
+ vec3 rayDirection = equirectUvToDirection( vUv );
8431
+ rayDirection.x *= flipEnvMap;
8432
+ gl_FragColor = textureCube( envMap, rayDirection );
8433
+
8434
+ }`
8435
+ } );
8436
+
8437
+ this.depthWrite = false;
8438
+ this.depthTest = false;
8439
+
8440
+ }
8441
+
8442
+ }
8443
+
8444
+ class CubeToEquirectGenerator {
8445
+
8446
+ constructor( renderer ) {
8447
+
8448
+ this._renderer = renderer;
8449
+ this._quad = new FullScreenQuad( new CubeToEquirectMaterial() );
8450
+
8451
+ }
8452
+
8453
+ generate( source, width = null, height = null ) {
8454
+
8455
+ if ( ! source.isCubeTexture ) {
8456
+
8457
+ throw new Error( 'CubeToEquirectMaterial: Source can only be cube textures.' );
8458
+
8459
+ }
8460
+
8461
+ const image = source.images[ 0 ];
8462
+ const renderer = this._renderer;
8463
+ const quad = this._quad;
8464
+
8465
+ // determine the dimensions if not provided
8466
+ if ( width === null ) {
8467
+
8468
+ width = 4 * image.height;
8469
+
8470
+ }
8471
+
8472
+ if ( height === null ) {
8473
+
8474
+ height = 2 * image.height;
8475
+
8476
+ }
8477
+
8478
+ const target = new WebGLRenderTarget( width, height, {
8479
+ type: FloatType,
8480
+ colorSpace: image.colorSpace,
8481
+ } );
8482
+
8483
+ // prep the cube map data
8484
+ const imageHeight = image.height;
8485
+ const maxMip = Math.log2( imageHeight ) - 2;
8486
+ const texelHeight = 1.0 / imageHeight;
8487
+ const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) );
8488
+
8489
+ quad.material.defines.CUBEUV_MAX_MIP = `${ maxMip }.0`;
8490
+ quad.material.defines.CUBEUV_TEXEL_WIDTH = texelWidth;
8491
+ quad.material.defines.CUBEUV_TEXEL_HEIGHT = texelHeight;
8492
+ quad.material.uniforms.envMap.value = source;
8493
+ quad.material.uniforms.flipEnvMap.value = source.isRenderTargetTexture ? 1 : - 1;
8494
+ quad.material.needsUpdate = true;
8495
+
8496
+ // save state and render the contents
8497
+ const currentTarget = renderer.getRenderTarget();
8498
+ const currentAutoClear = renderer.autoClear;
8499
+ renderer.autoClear = true;
8500
+ renderer.setRenderTarget( target );
8501
+ quad.render( renderer );
8502
+ renderer.setRenderTarget( currentTarget );
8503
+ renderer.autoClear = currentAutoClear;
8504
+
8505
+ // read the data back
8506
+ const buffer = new Uint16Array( width * height * 4 );
8507
+ const readBuffer = new Float32Array( width * height * 4 );
8508
+ renderer.readRenderTargetPixels( target, 0, 0, width, height, readBuffer );
8509
+ target.dispose();
8510
+
8511
+ for ( let i = 0, l = readBuffer.length; i < l; i ++ ) {
8512
+
8513
+ buffer[ i ] = DataUtils.toHalfFloat( readBuffer[ i ] );
8514
+
8515
+ }
8516
+
8517
+ // produce the data texture
8518
+ const result = new DataTexture( buffer, width, height, RGBAFormat, HalfFloatType );
8519
+ result.minFilter = LinearMipMapLinearFilter;
8520
+ result.magFilter = LinearFilter;
8521
+ result.wrapS = RepeatWrapping;
8522
+ result.wrapT = RepeatWrapping;
8523
+ result.mapping = EquirectangularReflectionMapping;
8524
+ result.needsUpdate = true;
8525
+
8526
+ return result;
8527
+
8528
+ }
8529
+
8530
+ dispose() {
8531
+
8532
+ this._quad.dispose();
8533
+
8534
+ }
8535
+
8536
+ }
8537
+
8184
8538
  function supportsFloatBlending( renderer ) {
8185
8539
 
8186
8540
  return renderer.extensions.get( 'EXT_float_blend' );
@@ -8256,6 +8610,24 @@ class WebGLPathTracer {
8256
8610
 
8257
8611
  }
8258
8612
 
8613
+ get stableNoise() {
8614
+
8615
+ return this._pathTracer.stableNoise;
8616
+
8617
+ }
8618
+
8619
+ set stableNoise( v ) {
8620
+
8621
+ this._pathTracer.stableNoise = v;
8622
+
8623
+ }
8624
+
8625
+ get isCompiling() {
8626
+
8627
+ return Boolean( this._pathTracer.isCompiling );
8628
+
8629
+ }
8630
+
8259
8631
  constructor( renderer ) {
8260
8632
 
8261
8633
  // members
@@ -8264,6 +8636,7 @@ class WebGLPathTracer {
8264
8636
  this._pathTracer = new PathTracingRenderer( renderer );
8265
8637
  this._queueReset = false;
8266
8638
  this._clock = new Clock();
8639
+ this._compilePromise = null;
8267
8640
 
8268
8641
  this._lowResPathTracer = new PathTracingRenderer( renderer );
8269
8642
  this._lowResPathTracer.tiles.set( 1, 1 );
@@ -8276,6 +8649,10 @@ class WebGLPathTracer {
8276
8649
  } ) );
8277
8650
  this._materials = null;
8278
8651
 
8652
+ this._previousEnvironment = null;
8653
+ this._previousBackground = null;
8654
+ this._internalBackground = null;
8655
+
8279
8656
  // options
8280
8657
  this.renderDelay = 100;
8281
8658
  this.minSamples = 5;
@@ -8404,6 +8781,13 @@ class WebGLPathTracer {
8404
8781
  const scene = this.scene;
8405
8782
  const material = this._pathTracer.material;
8406
8783
 
8784
+ if ( this._internalBackground ) {
8785
+
8786
+ this._internalBackground.dispose();
8787
+ this._internalBackground = null;
8788
+
8789
+ }
8790
+
8407
8791
  // update scene background
8408
8792
  material.backgroundBlur = scene.backgroundBlurriness;
8409
8793
  material.backgroundIntensity = scene.backgroundIntensity ?? 1;
@@ -8431,6 +8815,17 @@ class WebGLPathTracer {
8431
8815
  material.backgroundMap = colorBackground;
8432
8816
  material.backgroundAlpha = 1;
8433
8817
 
8818
+ } else if ( scene.background.isCubeTexture ) {
8819
+
8820
+ if ( scene.background !== this._previousBackground ) {
8821
+
8822
+ const background = new CubeToEquirectGenerator( this._renderer ).generate( scene.background );
8823
+ this._internalBackground = background;
8824
+ material.backgroundMap = background;
8825
+ material.backgroundAlpha = 1;
8826
+
8827
+ }
8828
+
8434
8829
  } else {
8435
8830
 
8436
8831
  material.backgroundMap = scene.background;
@@ -8439,26 +8834,32 @@ class WebGLPathTracer {
8439
8834
  }
8440
8835
 
8441
8836
  // update scene environment
8442
- material.environmentIntensity = scene.environmentIntensity ?? 1;
8837
+ material.environmentIntensity = scene.environment !== null ? ( scene.environmentIntensity ?? 1 ) : 0;
8443
8838
  material.environmentRotation.makeRotationFromEuler( scene.environmentRotation ).invert();
8444
8839
  if ( this._previousEnvironment !== scene.environment ) {
8445
8840
 
8446
- if ( scene.environment ) {
8841
+ if ( scene.environment !== null ) {
8447
8842
 
8448
- // TODO: Consider setting this to the highest supported bit depth by checking for
8449
- // OES_texture_float_linear or OES_texture_half_float_linear. Requires changes to
8450
- // the equirect uniform
8451
- material.envMapInfo.updateFrom( scene.environment );
8843
+ if ( scene.environment.isCubeTexture ) {
8452
8844
 
8453
- } else {
8845
+ const environment = new CubeToEquirectGenerator( this._renderer ).generate( scene.environment );
8846
+ material.envMapInfo.updateFrom( environment );
8847
+
8848
+ } else {
8849
+
8850
+ // TODO: Consider setting this to the highest supported bit depth by checking for
8851
+ // OES_texture_float_linear or OES_texture_half_float_linear. Requires changes to
8852
+ // the equirect uniform
8853
+ material.envMapInfo.updateFrom( scene.environment );
8454
8854
 
8455
- material.environmentIntensity = 0;
8855
+ }
8456
8856
 
8457
8857
  }
8458
8858
 
8459
8859
  }
8460
8860
 
8461
8861
  this._previousEnvironment = scene.environment;
8862
+ this._previousBackground = scene.background;
8462
8863
  this.reset();
8463
8864
 
8464
8865
  }
@@ -8470,6 +8871,7 @@ class WebGLPathTracer {
8470
8871
  geometry,
8471
8872
  bvh,
8472
8873
  bvhChanged,
8874
+ needsMaterialIndexUpdate,
8473
8875
  } = results;
8474
8876
 
8475
8877
  this._materials = materials;
@@ -8487,6 +8889,10 @@ class WebGLPathTracer {
8487
8889
  geometry.attributes.color,
8488
8890
  );
8489
8891
 
8892
+ }
8893
+
8894
+ if ( needsMaterialIndexUpdate ) {
8895
+
8490
8896
  material.materialIndexAttribute.updateFrom( geometry.attributes.materialIndex );
8491
8897
 
8492
8898
  }
@@ -8529,7 +8935,7 @@ class WebGLPathTracer {
8529
8935
  // render the path tracing sample after enough time has passed
8530
8936
  const delta = clock.getDelta() * 1e3;
8531
8937
  const elapsedTime = clock.getElapsedTime() * 1e3;
8532
- if ( ! this.pausePathTracing && this.enablePathTracing && this.renderDelay <= elapsedTime ) {
8938
+ if ( ! this.pausePathTracing && this.enablePathTracing && this.renderDelay <= elapsedTime && ! this.isCompiling ) {
8533
8939
 
8534
8940
  pathTracer.update();
8535
8941
 
@@ -8562,7 +8968,7 @@ class WebGLPathTracer {
8562
8968
  // render the fallback if we haven't rendered enough samples, are paused, or are occluded
8563
8969
  if ( ! this.enablePathTracing || this.samples < minSamples || quad.material.opacity < 1 ) {
8564
8970
 
8565
- if ( this.dynamicLowRes ) {
8971
+ if ( this.dynamicLowRes && ! this.isCompiling ) {
8566
8972
 
8567
8973
  if ( lowResPathTracer.samples < 1 ) {
8568
8974
 
@@ -8577,7 +8983,9 @@ class WebGLPathTracer {
8577
8983
  quad.render( renderer );
8578
8984
  quad.material.opacity = currentOpacity;
8579
8985
 
8580
- } else if ( this.rasterizeScene ) {
8986
+ }
8987
+
8988
+ if ( ! this.dynamicLowRes && this.rasterizeScene || this.dynamicLowRes && this.isCompiling ) {
8581
8989
 
8582
8990
  this.rasterizeSceneCallback( this.scene, this.camera );
8583
8991
 
@@ -8615,8 +9023,8 @@ class WebGLPathTracer {
8615
9023
 
8616
9024
  dispose() {
8617
9025
 
8618
- this._renderQuad.dispose();
8619
- this._renderQuad.material.dispose();
9026
+ this._quad.dispose();
9027
+ this._quad.material.dispose();
8620
9028
  this._pathTracer.dispose();
8621
9029
 
8622
9030
  }