three-gpu-pathtracer 0.0.16 → 0.0.18

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 (52) hide show
  1. package/README.md +3 -1
  2. package/build/index.module.js +1202 -471
  3. package/build/index.module.js.map +1 -1
  4. package/build/index.umd.cjs +1200 -470
  5. package/build/index.umd.cjs.map +1 -1
  6. package/package.json +5 -6
  7. package/src/core/DynamicPathTracingSceneGenerator.js +80 -35
  8. package/src/core/PathTracingRenderer.js +36 -38
  9. package/src/core/PathTracingSceneGenerator.js +11 -55
  10. package/src/materials/debug/GraphMaterial.js +1 -1
  11. package/src/materials/fullscreen/AlphaDisplayMaterial.js +1 -1
  12. package/src/materials/fullscreen/DenoiseMaterial.js +1 -1
  13. package/src/materials/fullscreen/GradientMapMaterial.js +1 -1
  14. package/src/materials/pathtracing/LambertPathTracingMaterial.js +5 -4
  15. package/src/materials/pathtracing/PhysicalPathTracingMaterial.js +87 -50
  16. package/src/materials/pathtracing/glsl/attenuateHit.glsl.js +21 -20
  17. package/src/materials/pathtracing/glsl/cameraUtils.glsl.js +2 -2
  18. package/src/materials/pathtracing/glsl/directLightContribution.glsl.js +12 -8
  19. package/src/materials/pathtracing/glsl/getSurfaceRecord.glsl.js +3 -2
  20. package/src/materials/pathtracing/glsl/traceScene.glsl.js +11 -13
  21. package/src/materials/surface/AmbientOcclusionMaterial.js +4 -3
  22. package/src/shader/bsdf/bsdfSampling.glsl.js +21 -17
  23. package/src/shader/common/bvhAnyHit.glsl.js +2 -2
  24. package/src/shader/common/fresnel.glsl.js +15 -9
  25. package/src/shader/common/intersectShapes.glsl.js +2 -2
  26. package/src/shader/rand/pcg.glsl.js +4 -4
  27. package/src/shader/rand/sobol.glsl.js +3 -3
  28. package/src/shader/rand/stratifiedTexture.glsl.js +45 -0
  29. package/src/shader/sampling/equirectSampling.glsl.js +10 -10
  30. package/src/shader/sampling/lightSampling.glsl.js +30 -37
  31. package/src/shader/structs/fogMaterialBvh.glsl.js +2 -2
  32. package/src/shader/structs/lightsStruct.glsl.js +15 -6
  33. package/src/shader/structs/materialStruct.glsl.js +16 -15
  34. package/src/textures/BlueNoiseTexture.js +87 -0
  35. package/src/textures/ProceduralEquirectTexture.js +6 -6
  36. package/src/textures/blueNoise/BlueNoiseGenerator.js +115 -0
  37. package/src/textures/blueNoise/BlueNoiseSamples.js +214 -0
  38. package/src/textures/blueNoise/utils.js +24 -0
  39. package/src/uniforms/EquirectHdrInfoUniform.js +59 -21
  40. package/src/uniforms/IESProfilesTexture.js +2 -2
  41. package/src/uniforms/LightsInfoUniformStruct.js +15 -9
  42. package/src/uniforms/MaterialsTexture.js +3 -1
  43. package/src/uniforms/RenderTarget2DArray.js +50 -3
  44. package/src/uniforms/StratifiedSamplesTexture.js +49 -0
  45. package/src/uniforms/stratified/StratifiedSampler.js +73 -0
  46. package/src/uniforms/stratified/StratifiedSamplerCombined.js +59 -0
  47. package/src/uniforms/utils.js +1 -1
  48. package/src/utils/BlurredEnvMapGenerator.js +4 -4
  49. package/src/utils/GeometryPreparationUtils.js +8 -95
  50. package/src/utils/IESLoader.js +7 -5
  51. package/src/utils/TextureUtils.js +15 -0
  52. package/src/workers/PathTracingSceneWorker.js +18 -8
@@ -1,7 +1,7 @@
1
- import { ShaderMaterial, NoBlending, Vector2, WebGLRenderTarget, FloatType, RGBAFormat, NearestFilter, Vector4, NormalBlending, Color, Vector3, MathUtils, Matrix4, PerspectiveCamera, BufferAttribute, Mesh, BufferGeometry, Camera, SpotLight, RectAreaLight, Spherical, DataTexture, EquirectangularReflectionMapping, RepeatWrapping, ClampToEdgeWrapping, LinearFilter, DoubleSide, BackSide, FrontSide, WebGLArrayRenderTarget, UnsignedByteType, MeshBasicMaterial, NoToneMapping, Source, HalfFloatType, DataUtils, RedFormat, Quaternion, Loader, FileLoader, PMREMGenerator, DataArrayTexture, MeshStandardMaterial, BoxGeometry } from 'three';
1
+ import { ShaderMaterial, NoBlending, Vector2, WebGLRenderTarget, FloatType, RGBAFormat, NearestFilter, Vector4, NormalBlending, Color, HalfFloatType, Vector3, MathUtils, Matrix4, PerspectiveCamera, BufferAttribute, MeshBasicMaterial, BufferGeometry, Mesh, Camera, SpotLight, RectAreaLight, Spherical, DataTexture, EquirectangularReflectionMapping, RepeatWrapping, ClampToEdgeWrapping, LinearFilter, DoubleSide, BackSide, FrontSide, WebGLArrayRenderTarget, UnsignedByteType, NoToneMapping, DataUtils, Source, RedFormat, Quaternion, Loader, FileLoader, PMREMGenerator, DataArrayTexture, RGFormat, MeshStandardMaterial, BoxGeometry } from 'three';
2
2
  import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
3
- import { StaticGeometryGenerator, SAH, MeshBVH, FloatVertexAttributeTexture, MeshBVHUniformStruct, UIntVertexAttributeTexture, shaderStructs, shaderIntersectFunction } from 'three-mesh-bvh';
4
- import { mergeVertices, mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
3
+ import { StaticGeometryGenerator, MeshBVH, SAH, FloatVertexAttributeTexture, MeshBVHUniformStruct, UIntVertexAttributeTexture, BVHShaderGLSL } from 'three-mesh-bvh';
4
+ import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
5
5
 
6
6
  class MaterialBase extends ShaderMaterial {
7
7
 
@@ -339,9 +339,9 @@ const sobolSamplingGLSL = /* glsl */`
339
339
 
340
340
  // Seeds
341
341
  uniform sampler2D sobolTexture;
342
- uint sobolPixelIndex;
343
- uint sobolPathIndex;
344
- uint sobolBounceIndex;
342
+ uint sobolPixelIndex = 0u;
343
+ uint sobolPathIndex = 0u;
344
+ uint sobolBounceIndex = 0u;
345
345
 
346
346
  uint sobolGetSeed( uint bounce, uint effect ) {
347
347
 
@@ -456,9 +456,6 @@ class SobolNumberMapGenerator {
456
456
 
457
457
  }
458
458
 
459
- const _scissor = new Vector4();
460
- const _viewport = new Vector4();
461
-
462
459
  function* renderTask() {
463
460
 
464
461
  const {
@@ -500,12 +497,21 @@ function* renderTask() {
500
497
  const h = _primaryTarget.height;
501
498
  material.resolution.set( w * subW, h * subH );
502
499
  material.sobolTexture = _sobolTarget.texture;
500
+ material.stratifiedTexture.init( 20, material.bounces + material.transmissiveBounces + 5 );
501
+ material.stratifiedTexture.next();
503
502
  material.seed ++;
504
503
 
505
504
  const tilesX = this.tiles.x || 1;
506
505
  const tilesY = this.tiles.y || 1;
507
506
  const totalTiles = tilesX * tilesY;
508
- const dprInv = ( 1 / _renderer.getPixelRatio() );
507
+
508
+ const pxSubW = Math.ceil( w * subW );
509
+ const pxSubH = Math.ceil( h * subH );
510
+ const pxSubX = Math.floor( subX * w );
511
+ const pxSubY = Math.floor( subY * h );
512
+
513
+ const pxTileW = Math.ceil( pxSubW / tilesX );
514
+ const pxTileH = Math.ceil( pxSubH / tilesY );
509
515
 
510
516
  for ( let y = 0; y < tilesY; y ++ ) {
511
517
 
@@ -554,40 +560,28 @@ function* renderTask() {
554
560
 
555
561
  }
556
562
 
563
+ // set the scissor and the viewport on the render target
564
+ // note that when using the webgl renderer set viewport the device pixel ratio
565
+ // is multiplied into the field causing some pixels to not be rendered
566
+ const reverseTy = tilesY - ty - 1;
567
+ _primaryTarget.scissor.set(
568
+ pxSubX + tx * pxTileW,
569
+ pxSubY + reverseTy * pxTileH,
570
+ Math.min( pxTileW, pxSubW - tx * pxTileW ),
571
+ Math.min( pxTileH, pxSubH - reverseTy * pxTileH ),
572
+ );
573
+
574
+ _primaryTarget.viewport.set(
575
+ pxSubX,
576
+ pxSubY,
577
+ pxSubW,
578
+ pxSubH,
579
+ );
580
+
557
581
  // three.js renderer takes values relative to the current pixel ratio
558
582
  _renderer.setRenderTarget( _primaryTarget );
559
583
  _renderer.setScissorTest( true );
560
584
 
561
- // set the scissor window for a subtile
562
- _scissor.x = tx * w / tilesX;
563
- _scissor.y = ( tilesY - ty - 1 ) * h / tilesY;
564
- _scissor.z = w / tilesX;
565
- _scissor.w = h / tilesY;
566
-
567
- // adjust for the subframe
568
- _scissor.x = subX * w + subW * _scissor.x;
569
- _scissor.y = subY * h + subH * _scissor.y;
570
- _scissor.z = subW * _scissor.z;
571
- _scissor.w = subH * _scissor.w;
572
-
573
- // round for floating point cases
574
- _scissor.x = _scissor.x;
575
- _scissor.y = _scissor.y;
576
- _scissor.z = _scissor.z;
577
- _scissor.w = _scissor.w;
578
-
579
- // multiply inverse of DPR in because threes multiplies it in
580
- _scissor.multiplyScalar( dprInv ).ceil();
581
-
582
- _viewport.x = subX * w;
583
- _viewport.y = subY * h;
584
- _viewport.z = subW * w;
585
- _viewport.w = subH * h;
586
- _viewport.multiplyScalar( dprInv ).ceil();
587
-
588
- _renderer.setScissor( _scissor );
589
- _renderer.setViewport( _viewport );
590
-
591
585
  _renderer.autoClear = false;
592
586
  _fsQuad.render( _renderer );
593
587
 
@@ -703,18 +697,22 @@ class PathTracingRenderer {
703
697
  this._currentTile = 0;
704
698
 
705
699
  this._sobolTarget = new SobolNumberMapGenerator().generate( renderer );
700
+
701
+ // will be null if extension not supported
702
+ const floatLinearExtensionSupported = renderer.extensions.get( 'OES_texture_float_linear' );
703
+
706
704
  this._primaryTarget = new WebGLRenderTarget( 1, 1, {
707
705
  format: RGBAFormat,
708
- type: FloatType,
706
+ type: floatLinearExtensionSupported ? FloatType : HalfFloatType,
709
707
  } );
710
708
  this._blendTargets = [
711
709
  new WebGLRenderTarget( 1, 1, {
712
710
  format: RGBAFormat,
713
- type: FloatType,
711
+ type: floatLinearExtensionSupported ? FloatType : HalfFloatType,
714
712
  } ),
715
713
  new WebGLRenderTarget( 1, 1, {
716
714
  format: RGBAFormat,
717
- type: FloatType,
715
+ type: floatLinearExtensionSupported ? FloatType : HalfFloatType,
718
716
  } ),
719
717
  ];
720
718
 
@@ -1073,25 +1071,6 @@ function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
1073
1071
 
1074
1072
  }
1075
1073
 
1076
- function trimToAttributes( geometry, attributes ) {
1077
-
1078
- // trim any unneeded attributes
1079
- if ( attributes ) {
1080
-
1081
- for ( const key in geometry.attributes ) {
1082
-
1083
- if ( ! attributes.includes( key ) ) {
1084
-
1085
- geometry.deleteAttribute( key );
1086
-
1087
- }
1088
-
1089
- }
1090
-
1091
- }
1092
-
1093
- }
1094
-
1095
1074
  function setCommonAttributes( geometry, options ) {
1096
1075
 
1097
1076
  const { attributes = [], normalMapRequired = false } = options;
@@ -1109,6 +1088,13 @@ function setCommonAttributes( geometry, options ) {
1109
1088
 
1110
1089
  }
1111
1090
 
1091
+ if ( ! geometry.attributes.uv2 && ( attributes && attributes.includes( 'uv2' ) ) ) {
1092
+
1093
+ const vertCount = geometry.attributes.position.count;
1094
+ geometry.setAttribute( 'uv2', new BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
1095
+
1096
+ }
1097
+
1112
1098
  if ( ! geometry.attributes.tangent && ( attributes && attributes.includes( 'tangent' ) ) ) {
1113
1099
 
1114
1100
  if ( normalMapRequired ) {
@@ -1157,158 +1143,46 @@ function setCommonAttributes( geometry, options ) {
1157
1143
 
1158
1144
  }
1159
1145
 
1160
- function mergeMeshes( meshes, options = {} ) {
1161
-
1162
- options = { attributes: null, cloneGeometry: true, ...options };
1163
-
1164
- const transformedGeometry = [];
1165
- const materialSet = new Set();
1166
- for ( let i = 0, l = meshes.length; i < l; i ++ ) {
1167
-
1168
- // save any materials
1169
- const mesh = meshes[ i ];
1170
- if ( mesh.visible === false ) continue;
1171
-
1172
- if ( Array.isArray( mesh.material ) ) {
1173
-
1174
- mesh.material.forEach( m => materialSet.add( m ) );
1175
-
1176
- } else {
1177
-
1178
- materialSet.add( mesh.material );
1179
-
1180
- }
1181
-
1182
- }
1183
-
1184
- const materials = Array.from( materialSet );
1185
- for ( let i = 0, l = meshes.length; i < l; i ++ ) {
1186
-
1187
- // ensure the matrix world is up to date
1188
- const mesh = meshes[ i ];
1189
- if ( mesh.visible === false ) continue;
1146
+ const dummyMaterial = new MeshBasicMaterial();
1147
+ function getDummyMesh() {
1190
1148
 
1191
- mesh.updateMatrixWorld();
1149
+ const emptyGeometry = new BufferGeometry();
1150
+ emptyGeometry.setAttribute( 'position', new BufferAttribute( new Float32Array( 9 ), 3 ) );
1151
+ return new Mesh( emptyGeometry, dummyMaterial );
1192
1152
 
1193
- // apply the matrix world to the geometry
1194
- const originalGeometry = meshes[ i ].geometry;
1195
- const geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
1196
- geometry.applyMatrix4( mesh.matrixWorld );
1153
+ }
1197
1154
 
1198
- // ensure our geometry has common attributes
1199
- setCommonAttributes( geometry, {
1200
- attributes: options.attributes,
1201
- normalMapRequired: ! ! mesh.material.normalMap,
1202
- } );
1203
- trimToAttributes( geometry, options.attributes );
1155
+ class DynamicPathTracingSceneGenerator {
1204
1156
 
1205
- // create the material index attribute
1206
- const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
1207
- geometry.setAttribute( 'materialIndex', materialIndexAttribute );
1157
+ get initialized() {
1208
1158
 
1209
- transformedGeometry.push( geometry );
1159
+ return Boolean( this.bvh );
1210
1160
 
1211
1161
  }
1212
1162
 
1213
- const textureSet = new Set();
1214
- materials.forEach( material => {
1215
-
1216
- for ( const key in material ) {
1217
-
1218
- const value = material[ key ];
1219
- if ( value && value.isTexture ) {
1163
+ constructor( objects ) {
1220
1164
 
1221
- textureSet.add( value );
1165
+ // ensure the objects is an array
1166
+ if ( ! Array.isArray( objects ) ) {
1222
1167
 
1223
- }
1168
+ objects = [ objects ];
1224
1169
 
1225
1170
  }
1226
1171
 
1227
- } );
1228
-
1229
- const geometry = mergeBufferGeometries( transformedGeometry, false );
1230
- const textures = Array.from( textureSet );
1231
- return { geometry, materials, textures };
1232
-
1233
- }
1234
-
1235
- class PathTracingSceneGenerator {
1236
-
1237
- prepScene( scene ) {
1238
-
1239
- scene = Array.isArray( scene ) ? scene : [ scene ];
1172
+ // use a dummy object for a fallback
1173
+ const finalObjects = [ ...objects ];
1174
+ if ( finalObjects.length === 0 ) {
1240
1175
 
1241
- const meshes = [];
1242
- const lights = [];
1243
-
1244
- for ( let i = 0, l = scene.length; i < l; i ++ ) {
1245
-
1246
- scene[ i ].traverseVisible( c => {
1247
-
1248
- if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
1249
-
1250
- const generator = new StaticGeometryGenerator( c );
1251
- generator.attributes = [ 'position', 'color', 'normal', 'tangent', 'uv', 'uv2' ];
1252
- generator.applyWorldTransforms = false;
1253
- const mesh = new Mesh(
1254
- generator.generate(),
1255
- c.material,
1256
- );
1257
- mesh.matrixWorld.copy( c.matrixWorld );
1258
- mesh.matrix.copy( c.matrixWorld );
1259
- mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
1260
- meshes.push( mesh );
1261
-
1262
- } else if ( c.isMesh ) {
1263
-
1264
- meshes.push( c );
1265
-
1266
- } else if ( c.isRectAreaLight || c.isSpotLight || c.isDirectionalLight || c.isPointLight ) {
1267
-
1268
- lights.push( c );
1269
-
1270
- }
1271
-
1272
- } );
1176
+ finalObjects.push( getDummyMesh() );
1273
1177
 
1274
1178
  }
1275
1179
 
1276
- return {
1277
- ...mergeMeshes( meshes, {
1278
- attributes: [ 'position', 'normal', 'tangent', 'uv', 'color' ],
1279
- } ),
1280
- lights,
1281
- };
1282
-
1283
- }
1284
-
1285
- generate( scene, options = {} ) {
1286
-
1287
- const { materials, textures, geometry, lights } = this.prepScene( scene );
1288
- const bvhOptions = { strategy: SAH, ...options, maxLeafTris: 1 };
1289
- return {
1290
- scene,
1291
- materials,
1292
- textures,
1293
- lights,
1294
- bvh: new MeshBVH( geometry, bvhOptions ),
1295
- };
1296
-
1297
- }
1298
-
1299
- }
1300
-
1301
- class DynamicPathTracingSceneGenerator {
1302
-
1303
- get initialized() {
1304
-
1305
- return Boolean( this.bvh );
1306
-
1307
- }
1308
-
1309
- constructor( scene ) {
1180
+ // options
1181
+ this.bvhOptions = {};
1182
+ this.attributes = [ 'position', 'normal', 'tangent', 'color', 'uv', 'uv2' ];
1310
1183
 
1311
- this.objects = Array.isArray( scene ) ? scene : [ scene ];
1184
+ // state
1185
+ this.objects = finalObjects;
1312
1186
  this.bvh = null;
1313
1187
  this.geometry = new BufferGeometry();
1314
1188
  this.materials = null;
@@ -1332,59 +1206,75 @@ class DynamicPathTracingSceneGenerator {
1332
1206
 
1333
1207
  dispose() {}
1334
1208
 
1335
- generate() {
1209
+ prepScene() {
1336
1210
 
1337
- const { objects, staticGeometryGenerator, geometry } = this;
1338
- if ( this.bvh === null ) {
1211
+ if ( this.bvh !== null ) {
1339
1212
 
1340
- const attributes = [ 'position', 'normal', 'tangent', 'uv', 'color' ];
1213
+ return;
1341
1214
 
1342
- for ( let i = 0, l = objects.length; i < l; i ++ ) {
1215
+ }
1343
1216
 
1344
- objects[ i ].traverse( c => {
1217
+ const { objects, staticGeometryGenerator, geometry, lights, attributes } = this;
1218
+ for ( let i = 0, l = objects.length; i < l; i ++ ) {
1345
1219
 
1346
- if ( c.isMesh ) {
1220
+ objects[ i ].traverse( c => {
1347
1221
 
1348
- const normalMapRequired = ! ! c.material.normalMap;
1349
- setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
1222
+ if ( c.isMesh ) {
1350
1223
 
1351
- } else if ( c.isRectAreaLight || c.isSpotLight ) {
1224
+ const normalMapRequired = ! ! c.material.normalMap;
1225
+ setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
1352
1226
 
1353
- this.lights.push( c );
1227
+ } else if (
1228
+ c.isRectAreaLight ||
1229
+ c.isSpotLight ||
1230
+ c.isPointLight ||
1231
+ c.isDirectionalLight
1232
+ ) {
1354
1233
 
1355
- }
1234
+ lights.push( c );
1356
1235
 
1357
- } );
1236
+ }
1358
1237
 
1359
- }
1238
+ } );
1360
1239
 
1361
- const textureSet = new Set();
1362
- const materials = staticGeometryGenerator.getMaterials();
1363
- materials.forEach( material => {
1240
+ }
1364
1241
 
1365
- for ( const key in material ) {
1242
+ const textureSet = new Set();
1243
+ const materials = staticGeometryGenerator.getMaterials();
1244
+ materials.forEach( material => {
1366
1245
 
1367
- const value = material[ key ];
1368
- if ( value && value.isTexture ) {
1246
+ for ( const key in material ) {
1369
1247
 
1370
- textureSet.add( value );
1248
+ const value = material[ key ];
1249
+ if ( value && value.isTexture ) {
1371
1250
 
1372
- }
1251
+ textureSet.add( value );
1373
1252
 
1374
1253
  }
1375
1254
 
1376
- } );
1255
+ }
1377
1256
 
1378
- staticGeometryGenerator.attributes = attributes;
1379
- staticGeometryGenerator.generate( geometry );
1257
+ } );
1258
+
1259
+ staticGeometryGenerator.attributes = attributes;
1260
+ staticGeometryGenerator.generate( geometry );
1261
+
1262
+ const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, materials, materials );
1263
+ geometry.setAttribute( 'materialIndex', materialIndexAttribute );
1264
+ geometry.clearGroups();
1380
1265
 
1381
- const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, materials, materials );
1382
- geometry.setAttribute( 'materialIndex', materialIndexAttribute );
1383
- geometry.clearGroups();
1266
+ this.materials = materials;
1267
+ this.textures = Array.from( textureSet );
1384
1268
 
1385
- this.bvh = new MeshBVH( geometry );
1386
- this.materials = materials;
1387
- this.textures = Array.from( textureSet );
1269
+ }
1270
+
1271
+ generate() {
1272
+
1273
+ const { objects, staticGeometryGenerator, geometry, bvhOptions } = this;
1274
+ if ( this.bvh === null ) {
1275
+
1276
+ this.prepScene();
1277
+ this.bvh = new MeshBVH( geometry, { strategy: SAH, maxLeafTris: 1, ...bvhOptions } );
1388
1278
 
1389
1279
  return {
1390
1280
  lights: this.lights,
@@ -1414,6 +1304,30 @@ class DynamicPathTracingSceneGenerator {
1414
1304
 
1415
1305
  }
1416
1306
 
1307
+ class PathTracingSceneGenerator {
1308
+
1309
+ generate( scene, options = {} ) {
1310
+
1311
+ // ensure scene transforms are up to date
1312
+ // TODO: remove this?
1313
+ if ( Array.isArray( scene ) ) {
1314
+
1315
+ scene.forEach( s => s.updateMatrixWorld( true ) );
1316
+
1317
+ } else {
1318
+
1319
+ scene.updateMatrixWorld( true );
1320
+
1321
+ }
1322
+
1323
+ const generator = new DynamicPathTracingSceneGenerator( scene );
1324
+ generator.bvhOptions = options;
1325
+ return generator.generate();
1326
+
1327
+ }
1328
+
1329
+ }
1330
+
1417
1331
  // https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
1418
1332
 
1419
1333
  function isTypedArray( arr ) {
@@ -1775,7 +1689,7 @@ const _polar = new Spherical();
1775
1689
  const _color = new Color();
1776
1690
  class ProceduralEquirectTexture extends DataTexture {
1777
1691
 
1778
- constructor( width, height ) {
1692
+ constructor( width = 512, height = 512 ) {
1779
1693
 
1780
1694
  super(
1781
1695
  new Float32Array( width * height * 4 ),
@@ -1811,10 +1725,10 @@ class ProceduralEquirectTexture extends DataTexture {
1811
1725
 
1812
1726
  const i = y * width + x;
1813
1727
  const i4 = 4 * i;
1814
- data[ i4 + 0 ] = _color.r;
1815
- data[ i4 + 1 ] = _color.g;
1816
- data[ i4 + 2 ] = _color.b;
1817
- data[ i4 + 3 ] = 1.0;
1728
+ data[ i4 + 0 ] = ( _color.r );
1729
+ data[ i4 + 1 ] = ( _color.g );
1730
+ data[ i4 + 2 ] = ( _color.b );
1731
+ data[ i4 + 3 ] = ( 1.0 );
1818
1732
 
1819
1733
  }
1820
1734
 
@@ -1869,7 +1783,7 @@ class GradientEquirectTexture extends ProceduralEquirectTexture {
1869
1783
  // when rendering each texture to the texture array they must have a consistent color space.
1870
1784
  function getTextureHash( t ) {
1871
1785
 
1872
- return `${ t.source.uuid }:${ t.encoding }`;
1786
+ return `${ t.source.uuid }:${ t.colorSpace }`;
1873
1787
 
1874
1788
  }
1875
1789
 
@@ -1948,6 +1862,8 @@ class MaterialsTexture extends DataTexture {
1948
1862
  this.type = FloatType;
1949
1863
  this.wrapS = ClampToEdgeWrapping;
1950
1864
  this.wrapT = ClampToEdgeWrapping;
1865
+ this.minFilter = NearestFilter;
1866
+ this.magFilter = NearestFilter;
1951
1867
  this.generateMipmaps = false;
1952
1868
  this.threeCompatibilityTransforms = false;
1953
1869
  this.features = new MaterialFeatures();
@@ -2417,7 +2333,7 @@ class RenderTarget2DArray extends WebGLArrayRenderTarget {
2417
2333
 
2418
2334
  };
2419
2335
 
2420
- const fsQuad = new FullScreenQuad( new MeshBasicMaterial() );
2336
+ const fsQuad = new FullScreenQuad( new CopyMaterial() );
2421
2337
  this.fsQuad = fsQuad;
2422
2338
 
2423
2339
  }
@@ -2452,7 +2368,6 @@ class RenderTarget2DArray extends WebGLArrayRenderTarget {
2452
2368
  texture.matrix.identity();
2453
2369
 
2454
2370
  fsQuad.material.map = texture;
2455
- fsQuad.material.transparent = true;
2456
2371
 
2457
2372
  renderer.setRenderTarget( this, i );
2458
2373
  fsQuad.render( renderer );
@@ -2482,6 +2397,67 @@ class RenderTarget2DArray extends WebGLArrayRenderTarget {
2482
2397
 
2483
2398
  }
2484
2399
 
2400
+ class CopyMaterial extends ShaderMaterial {
2401
+
2402
+ get map() {
2403
+
2404
+ return this.uniforms.map.value;
2405
+
2406
+ }
2407
+ set map( v ) {
2408
+
2409
+ this.uniforms.map.value = v;
2410
+
2411
+ }
2412
+
2413
+ constructor() {
2414
+
2415
+ super( {
2416
+ uniforms: {
2417
+
2418
+ map: { value: null },
2419
+
2420
+ },
2421
+
2422
+ vertexShader: /* glsl */`
2423
+ varying vec2 vUv;
2424
+ void main() {
2425
+
2426
+ vUv = uv;
2427
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
2428
+
2429
+ }
2430
+ `,
2431
+
2432
+ fragmentShader: /* glsl */`
2433
+ uniform sampler2D map;
2434
+ varying vec2 vUv;
2435
+ void main() {
2436
+
2437
+ gl_FragColor = texture2D( map, vUv );
2438
+
2439
+ }
2440
+ `
2441
+ } );
2442
+
2443
+ }
2444
+
2445
+
2446
+ }
2447
+
2448
+ function toHalfFloatArray( f32Array ) {
2449
+
2450
+ const f16Array = new Uint16Array( f32Array.length );
2451
+ for ( let i = 0, n = f32Array.length; i < n; ++ i ) {
2452
+
2453
+ f16Array[ i ] = DataUtils.toHalfFloat( f32Array[ i ] );
2454
+
2455
+ }
2456
+
2457
+ return f16Array;
2458
+
2459
+ }
2460
+
2485
2461
  function binarySearchFindClosestIndexOf( array, targetValue, offset = 0, count = array.length ) {
2486
2462
 
2487
2463
  let lower = offset;
@@ -2520,7 +2496,7 @@ function colorToLuminance( r, g, b ) {
2520
2496
  }
2521
2497
 
2522
2498
  // ensures the data is all floating point values and flipY is false
2523
- function preprocessEnvMap( envMap ) {
2499
+ function preprocessEnvMap( envMap, targetType = HalfFloatType ) {
2524
2500
 
2525
2501
  const map = envMap.clone();
2526
2502
  map.source = new Source( { ...map.image } );
@@ -2529,17 +2505,54 @@ function preprocessEnvMap( envMap ) {
2529
2505
  // TODO: is there a simple way to avoid cloning and adjusting the env map data here?
2530
2506
  // convert the data from half float uint 16 arrays to float arrays for cdf computation
2531
2507
  let newData = data;
2532
- if ( map.type === HalfFloatType ) {
2508
+ if ( map.type !== targetType ) {
2509
+
2510
+ if ( targetType === HalfFloatType ) {
2511
+
2512
+ newData = new Uint16Array( data.length );
2513
+
2514
+ } else {
2515
+
2516
+ newData = new Float32Array( data.length );
2517
+
2518
+ }
2519
+
2520
+ let maxIntValue;
2521
+ if ( data instanceof Int8Array || data instanceof Int16Array || data instanceof Int32Array ) {
2522
+
2523
+ maxIntValue = 2 ** ( 8 * data.BYTES_PER_ELEMENT - 1 ) - 1;
2524
+
2525
+ } else {
2526
+
2527
+ maxIntValue = 2 ** ( 8 * data.BYTES_PER_ELEMENT ) - 1;
2528
+
2529
+ }
2533
2530
 
2534
- newData = new Float32Array( data.length );
2535
- for ( const i in data ) {
2531
+ for ( let i = 0, l = data.length; i < l; i ++ ) {
2536
2532
 
2537
- newData[ i ] = DataUtils.fromHalfFloat( data[ i ] );
2533
+ let v = data[ i ];
2534
+ if ( map.type === HalfFloatType ) {
2535
+
2536
+ v = DataUtils.fromHalfFloat( data[ i ] );
2537
+
2538
+ }
2539
+
2540
+ if ( map.type !== FloatType && map.type !== HalfFloatType ) {
2541
+
2542
+ v /= maxIntValue;
2543
+
2544
+ }
2545
+
2546
+ if ( targetType === HalfFloatType ) {
2547
+
2548
+ newData[ i ] = DataUtils.toHalfFloat( v );
2549
+
2550
+ }
2538
2551
 
2539
2552
  }
2540
2553
 
2541
2554
  map.image.data = newData;
2542
- map.type = FloatType;
2555
+ map.type = targetType;
2543
2556
 
2544
2557
  }
2545
2558
 
@@ -2580,8 +2593,8 @@ class EquirectHdrInfoUniform {
2580
2593
 
2581
2594
  // Default to a white texture and associated weights so we don't
2582
2595
  // just render black initially.
2583
- const whiteTex = new DataTexture( new Float32Array( [ 1, 1, 1, 1 ] ), 1, 1 );
2584
- whiteTex.type = FloatType;
2596
+ const whiteTex = new DataTexture( toHalfFloatArray( new Float32Array( [ 1, 1, 1, 1 ] ) ), 1, 1 );
2597
+ whiteTex.type = HalfFloatType;
2585
2598
  whiteTex.format = RGBAFormat;
2586
2599
  whiteTex.minFilter = LinearFilter;
2587
2600
  whiteTex.magFilter = LinearFilter;
@@ -2592,8 +2605,8 @@ class EquirectHdrInfoUniform {
2592
2605
 
2593
2606
  // Stores a map of [0, 1] value -> cumulative importance row & pdf
2594
2607
  // used to sampling a random value to a relevant row to sample from
2595
- const marginalWeights = new DataTexture( new Float32Array( [ 0, 1 ] ), 1, 2 );
2596
- marginalWeights.type = FloatType;
2608
+ const marginalWeights = new DataTexture( toHalfFloatArray( new Float32Array( [ 0, 1 ] ) ), 1, 2 );
2609
+ marginalWeights.type = HalfFloatType;
2597
2610
  marginalWeights.format = RedFormat;
2598
2611
  marginalWeights.minFilter = LinearFilter;
2599
2612
  marginalWeights.magFilter = LinearFilter;
@@ -2602,8 +2615,8 @@ class EquirectHdrInfoUniform {
2602
2615
 
2603
2616
  // Stores a map of [0, 1] value -> cumulative importance column & pdf
2604
2617
  // used to sampling a random value to a relevant pixel to sample from
2605
- const conditionalWeights = new DataTexture( new Float32Array( [ 0, 0, 1, 1 ] ), 2, 2 );
2606
- conditionalWeights.type = FloatType;
2618
+ const conditionalWeights = new DataTexture( toHalfFloatArray( new Float32Array( [ 0, 0, 1, 1 ] ) ), 2, 2 );
2619
+ conditionalWeights.type = HalfFloatType;
2607
2620
  conditionalWeights.format = RedFormat;
2608
2621
  conditionalWeights.minFilter = LinearFilter;
2609
2622
  conditionalWeights.magFilter = LinearFilter;
@@ -2631,7 +2644,7 @@ class EquirectHdrInfoUniform {
2631
2644
  // https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
2632
2645
  const map = preprocessEnvMap( hdr );
2633
2646
  map.wrapS = RepeatWrapping;
2634
- map.wrapT = RepeatWrapping;
2647
+ map.wrapT = ClampToEdgeWrapping;
2635
2648
 
2636
2649
  const { width, height, data } = map.image;
2637
2650
 
@@ -2653,9 +2666,9 @@ class EquirectHdrInfoUniform {
2653
2666
  for ( let x = 0; x < width; x ++ ) {
2654
2667
 
2655
2668
  const i = y * width + x;
2656
- const r = data[ 4 * i + 0 ];
2657
- const g = data[ 4 * i + 1 ];
2658
- const b = data[ 4 * i + 2 ];
2669
+ const r = DataUtils.fromHalfFloat( data[ 4 * i + 0 ] );
2670
+ const g = DataUtils.fromHalfFloat( data[ 4 * i + 1 ] );
2671
+ const b = DataUtils.fromHalfFloat( data[ 4 * i + 2 ] );
2659
2672
 
2660
2673
  // the probability of the pixel being selected in this row is the
2661
2674
  // scale of the luminance relative to the rest of the pixels.
@@ -2707,8 +2720,8 @@ class EquirectHdrInfoUniform {
2707
2720
  // the marginal and conditional data. These will be used to sample with a random number
2708
2721
  // to retrieve a uv value to sample in the environment map.
2709
2722
  // These values continually increase so it's okay to interpolate between them.
2710
- const marginalDataArray = new Float32Array( height );
2711
- const conditionalDataArray = new Float32Array( width * height );
2723
+ const marginalDataArray = new Uint16Array( height );
2724
+ const conditionalDataArray = new Uint16Array( width * height );
2712
2725
 
2713
2726
  // we add a half texel offset so we're sampling the center of the pixel
2714
2727
  for ( let i = 0; i < height; i ++ ) {
@@ -2716,7 +2729,7 @@ class EquirectHdrInfoUniform {
2716
2729
  const dist = ( i + 1 ) / height;
2717
2730
  const row = binarySearchFindClosestIndexOf( cdfMarginal, dist );
2718
2731
 
2719
- marginalDataArray[ i ] = ( row + 0.5 ) / height;
2732
+ marginalDataArray[ i ] = DataUtils.toHalfFloat( ( row + 0.5 ) / height );
2720
2733
 
2721
2734
  }
2722
2735
 
@@ -2728,7 +2741,7 @@ class EquirectHdrInfoUniform {
2728
2741
  const dist = ( x + 1 ) / width;
2729
2742
  const col = binarySearchFindClosestIndexOf( cdfConditional, dist, y * width, width );
2730
2743
 
2731
- conditionalDataArray[ i ] = ( col + 0.5 ) / width;
2744
+ conditionalDataArray[ i ] = DataUtils.toHalfFloat( ( col + 0.5 ) / width );
2732
2745
 
2733
2746
  }
2734
2747
 
@@ -2802,6 +2815,8 @@ class LightsInfoUniformStruct {
2802
2815
  tex.wrapS = ClampToEdgeWrapping;
2803
2816
  tex.wrapT = ClampToEdgeWrapping;
2804
2817
  tex.generateMipmaps = false;
2818
+ tex.minFilter = NearestFilter;
2819
+ tex.magFilter = NearestFilter;
2805
2820
 
2806
2821
  this.tex = tex;
2807
2822
  this.count = 0;
@@ -2832,7 +2847,7 @@ class LightsInfoUniformStruct {
2832
2847
  const worldQuaternion = new Quaternion();
2833
2848
  const eye = new Vector3();
2834
2849
  const target = new Vector3();
2835
- const up = new Vector3();
2850
+ const up = new Vector3( 0, 1, 0 );
2836
2851
 
2837
2852
  for ( let i = 0, l = lights.length; i < l; i ++ ) {
2838
2853
 
@@ -2841,6 +2856,13 @@ class LightsInfoUniformStruct {
2841
2856
  const baseIndex = i * LIGHT_PIXELS * 4;
2842
2857
  let index = 0;
2843
2858
 
2859
+ // initialize to 0
2860
+ for ( let p = 0; p < LIGHT_PIXELS; p ++ ) {
2861
+
2862
+ floatArray[ baseIndex + p ] = 0;
2863
+
2864
+ }
2865
+
2844
2866
  // sample 1
2845
2867
  // position
2846
2868
  l.getWorldPosition( v );
@@ -2905,7 +2927,7 @@ class LightsInfoUniformStruct {
2905
2927
 
2906
2928
  } else if ( l.isSpotLight ) {
2907
2929
 
2908
- const radius = l.radius;
2930
+ const radius = l.radius || 0;
2909
2931
  eye.setFromMatrixPosition( l.matrixWorld );
2910
2932
  target.setFromMatrixPosition( l.target.matrixWorld );
2911
2933
  m.lookAt( eye, target, up );
@@ -2935,24 +2957,21 @@ class LightsInfoUniformStruct {
2935
2957
  // radius
2936
2958
  floatArray[ baseIndex + ( index ++ ) ] = radius;
2937
2959
 
2938
- // near
2939
- floatArray[ baseIndex + ( index ++ ) ] = l.shadow.camera.near;
2940
-
2941
2960
  // decay
2942
2961
  floatArray[ baseIndex + ( index ++ ) ] = l.decay;
2943
2962
 
2944
2963
  // distance
2945
2964
  floatArray[ baseIndex + ( index ++ ) ] = l.distance;
2946
2965
 
2947
- // sample 6
2948
2966
  // coneCos
2949
2967
  floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle );
2950
2968
 
2969
+ // sample 6
2951
2970
  // penumbraCos
2952
2971
  floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle * ( 1 - l.penumbra ) );
2953
2972
 
2954
2973
  // iesProfile
2955
- floatArray[ baseIndex + ( index ++ ) ] = iesTextures.indexOf( l.iesTexture );
2974
+ floatArray[ baseIndex + ( index ++ ) ] = l.iesTexture ? iesTextures.indexOf( l.iesTexture ) : - 1;
2956
2975
 
2957
2976
  } else if ( l.isPointLight ) {
2958
2977
 
@@ -2969,7 +2988,7 @@ class LightsInfoUniformStruct {
2969
2988
  index += 4;
2970
2989
 
2971
2990
  // sample 5
2972
- index += 2;
2991
+ index += 1;
2973
2992
 
2974
2993
  floatArray[ baseIndex + ( index ++ ) ] = l.decay;
2975
2994
  floatArray[ baseIndex + ( index ++ ) ] = l.distance;
@@ -3275,7 +3294,7 @@ class IESLoader extends Loader {
3275
3294
  loader.setPath( this.path );
3276
3295
  loader.setRequestHeader( this.requestHeader );
3277
3296
 
3278
- const texture = new DataTexture( null, 360, 180, RedFormat, FloatType );
3297
+ const texture = new DataTexture( null, 360, 180, RedFormat, HalfFloatType );
3279
3298
  texture.minFilter = LinearFilter;
3280
3299
  texture.magFilter = LinearFilter;
3281
3300
 
@@ -3283,7 +3302,7 @@ class IESLoader extends Loader {
3283
3302
 
3284
3303
  const iesLamp = new IESLamp( text );
3285
3304
 
3286
- texture.image.data = this._getIESValues( iesLamp );
3305
+ texture.image.data = toHalfFloatArray( this._getIESValues( iesLamp ) );
3287
3306
  texture.needsUpdate = true;
3288
3307
 
3289
3308
  if ( onLoad !== undefined ) {
@@ -3301,10 +3320,10 @@ class IESLoader extends Loader {
3301
3320
  parse( text ) {
3302
3321
 
3303
3322
  const iesLamp = new IESLamp( text );
3304
- const texture = new DataTexture( null, 360, 180, RedFormat, FloatType );
3323
+ const texture = new DataTexture( null, 360, 180, RedFormat, HalfFloatType );
3305
3324
  texture.minFilter = LinearFilter;
3306
3325
  texture.magFilter = LinearFilter;
3307
- texture.image.data = this._getIESValues( iesLamp );
3326
+ texture.image.data = toHalfFloatArray( this._getIESValues( iesLamp ) );
3308
3327
  texture.needsUpdate = true;
3309
3328
 
3310
3329
  return texture;
@@ -3322,7 +3341,7 @@ class IESProfilesTexture extends WebGLArrayRenderTarget {
3322
3341
 
3323
3342
  const tex = this.texture;
3324
3343
  tex.format = RGBAFormat;
3325
- tex.type = FloatType;
3344
+ tex.type = HalfFloatType;
3326
3345
  tex.minFilter = LinearFilter;
3327
3346
  tex.magFilter = LinearFilter;
3328
3347
  tex.wrapS = ClampToEdgeWrapping;
@@ -3573,7 +3592,7 @@ class BlurredEnvMapGenerator {
3573
3592
  this.renderer = renderer;
3574
3593
  this.pmremGenerator = new PMREMGenerator( renderer );
3575
3594
  this.copyQuad = new FullScreenQuad( new PMREMCopyMaterial() );
3576
- this.renderTarget = new WebGLRenderTarget( 1, 1, { type: FloatType, format: RGBAFormat } );
3595
+ this.renderTarget = new WebGLRenderTarget( 1, 1, { type: HalfFloatType, format: RGBAFormat } );
3577
3596
 
3578
3597
  }
3579
3598
 
@@ -3610,10 +3629,10 @@ class BlurredEnvMapGenerator {
3610
3629
  renderer.autoClear = prevClear;
3611
3630
 
3612
3631
  // read the data back
3613
- const buffer = new Float32Array( width * height * 4 );
3632
+ const buffer = new Uint16Array( width * height * 4 );
3614
3633
  renderer.readRenderTargetPixels( renderTarget, 0, 0, width, height, buffer );
3615
3634
 
3616
- const result = new DataTexture( buffer, width, height, RGBAFormat, FloatType );
3635
+ const result = new DataTexture( buffer, width, height, RGBAFormat, HalfFloatType );
3617
3636
  result.minFilter = texture.minFilter;
3618
3637
  result.magFilter = texture.magFilter;
3619
3638
  result.wrapS = texture.wrapS;
@@ -3755,7 +3774,7 @@ class DenoiseMaterial extends MaterialBase {
3755
3774
 
3756
3775
  gl_FragColor = smartDeNoise( map, vec2( vUv.x, vUv.y ), sigma, kSigma, threshold );
3757
3776
  #include <tonemapping_fragment>
3758
- #include <encodings_fragment>
3777
+ #include <colorspace_fragment>
3759
3778
  #include <premultiplied_alpha_fragment>
3760
3779
 
3761
3780
  }
@@ -3838,7 +3857,7 @@ class GradientMapMaterial extends MaterialBase {
3838
3857
  gl_FragColor.rgb = vec3( mix( minColor, maxColor, t ) );
3839
3858
  gl_FragColor.a = 1.0;
3840
3859
 
3841
- #include <encodings_fragment>
3860
+ #include <colorspace_fragment>
3842
3861
 
3843
3862
  }`,
3844
3863
 
@@ -4047,7 +4066,7 @@ class GraphMaterial extends MaterialBase {
4047
4066
 
4048
4067
  }
4049
4068
 
4050
- #include <encodings_fragment>
4069
+ #include <colorspace_fragment>
4051
4070
 
4052
4071
  }
4053
4072
 
@@ -4383,13 +4402,22 @@ const lightsStructGLSL = /* glsl */`
4383
4402
  vec4 s4 = texelFetch1D( tex, i + 4u );
4384
4403
  vec4 s5 = texelFetch1D( tex, i + 5u );
4385
4404
  l.radius = s4.r;
4386
- l.near = s4.g;
4387
- l.decay = s4.b;
4388
- l.distance = s4.a;
4405
+ l.decay = s4.g;
4406
+ l.distance = s4.b;
4407
+ l.coneCos = s4.a;
4408
+
4409
+ l.penumbraCos = s5.r;
4410
+ l.iesProfile = int( round( s5.g ) );
4389
4411
 
4390
- l.coneCos = s5.r;
4391
- l.penumbraCos = s5.g;
4392
- l.iesProfile = int( round ( s5.b ) );
4412
+ } else {
4413
+
4414
+ l.radius = 0.0;
4415
+ l.decay = 0.0;
4416
+ l.distance = 0.0;
4417
+
4418
+ l.coneCos = 0.0;
4419
+ l.penumbraCos = 0.0;
4420
+ l.iesProfile = - 1;
4393
4421
 
4394
4422
  }
4395
4423
 
@@ -4585,21 +4613,22 @@ const materialStructGLSL = /* glsl */ `
4585
4613
 
4586
4614
  uint firstTextureTransformIdx = i + 15u;
4587
4615
 
4588
- m.mapTransform = m.map == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx );
4589
- m.metalnessMapTransform = m.metalnessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 2u );
4590
- m.roughnessMapTransform = m.roughnessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 4u );
4591
- m.transmissionMapTransform = m.transmissionMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 6u );
4592
- m.emissiveMapTransform = m.emissiveMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 8u );
4593
- m.normalMapTransform = m.normalMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 10u );
4594
- m.clearcoatMapTransform = m.clearcoatMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 12u );
4595
- m.clearcoatNormalMapTransform = m.clearcoatNormalMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 14u );
4596
- m.clearcoatRoughnessMapTransform = m.clearcoatRoughnessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 16u );
4597
- m.sheenColorMapTransform = m.sheenColorMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 18u );
4598
- m.sheenRoughnessMapTransform = m.sheenRoughnessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 20u );
4599
- m.iridescenceMapTransform = m.iridescenceMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 22u );
4600
- m.iridescenceThicknessMapTransform = m.iridescenceThicknessMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 24u );
4601
- m.specularColorMapTransform = m.specularColorMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 26u );
4602
- m.specularIntensityMapTransform = m.specularIntensityMap == - 1 ? mat3( 0 ) : readTextureTransform( tex, firstTextureTransformIdx + 28u );
4616
+ // mat3( 1.0 ) is an identity matrix
4617
+ m.mapTransform = m.map == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx );
4618
+ m.metalnessMapTransform = m.metalnessMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 2u );
4619
+ m.roughnessMapTransform = m.roughnessMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 4u );
4620
+ m.transmissionMapTransform = m.transmissionMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 6u );
4621
+ m.emissiveMapTransform = m.emissiveMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 8u );
4622
+ m.normalMapTransform = m.normalMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 10u );
4623
+ m.clearcoatMapTransform = m.clearcoatMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 12u );
4624
+ m.clearcoatNormalMapTransform = m.clearcoatNormalMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 14u );
4625
+ m.clearcoatRoughnessMapTransform = m.clearcoatRoughnessMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 16u );
4626
+ m.sheenColorMapTransform = m.sheenColorMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 18u );
4627
+ m.sheenRoughnessMapTransform = m.sheenRoughnessMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 20u );
4628
+ m.iridescenceMapTransform = m.iridescenceMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 22u );
4629
+ m.iridescenceThicknessMapTransform = m.iridescenceThicknessMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 24u );
4630
+ m.specularColorMapTransform = m.specularColorMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 26u );
4631
+ m.specularIntensityMapTransform = m.specularIntensityMap == - 1 ? mat3( 1.0 ) : readTextureTransform( tex, firstTextureTransformIdx + 28u );
4603
4632
 
4604
4633
  return m;
4605
4634
 
@@ -4624,9 +4653,9 @@ bool isMaterialFogVolume( sampler2D materials, uint materialIndex ) {
4624
4653
 
4625
4654
  // returns true if we're within the first fog volume we hit
4626
4655
  bool bvhIntersectFogVolumeHit(
4627
- BVH bvh, vec3 rayOrigin, vec3 rayDirection,
4656
+ vec3 rayOrigin, vec3 rayDirection,
4628
4657
  usampler2D materialIndexAttribute, sampler2D materials,
4629
- out Material material
4658
+ inout Material material
4630
4659
  ) {
4631
4660
 
4632
4661
  material.fogVolume = false;
@@ -5086,7 +5115,7 @@ const bsdfSamplingGLSL = /* glsl */`
5086
5115
  ${ iridescenceGLSL }
5087
5116
 
5088
5117
  // diffuse
5089
- float diffuseEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRecord surf, out vec3 color ) {
5118
+ float diffuseEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRecord surf, inout vec3 color ) {
5090
5119
 
5091
5120
  // https://schuttejoe.github.io/post/disneybsdf/
5092
5121
  float fl = schlickFresnel( wi.z, 0.0 );
@@ -5100,15 +5129,17 @@ const bsdfSamplingGLSL = /* glsl */`
5100
5129
 
5101
5130
  // TODO: subsurface approx?
5102
5131
 
5103
- float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
5132
+ // float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
5133
+ float F = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
5104
5134
  color = ( 1.0 - F ) * transFactor * metalFactor * wi.z * surf.color * ( retro + lambert ) / PI;
5135
+
5105
5136
  return wi.z / PI;
5106
5137
 
5107
5138
  }
5108
5139
 
5109
5140
  vec3 diffuseDirection( vec3 wo, SurfaceRecord surf ) {
5110
5141
 
5111
- vec3 lightDirection = sampleSphere( sobol2( 11 ) );
5142
+ vec3 lightDirection = sampleSphere( rand2( 11 ) );
5112
5143
  lightDirection.z += 1.0;
5113
5144
  lightDirection = normalize( lightDirection );
5114
5145
 
@@ -5117,7 +5148,7 @@ const bsdfSamplingGLSL = /* glsl */`
5117
5148
  }
5118
5149
 
5119
5150
  // specular
5120
- float specularEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRecord surf, out vec3 color ) {
5151
+ float specularEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRecord surf, inout vec3 color ) {
5121
5152
 
5122
5153
  // if roughness is set to 0 then D === NaN which results in black pixels
5123
5154
  float metalness = surf.metalness;
@@ -5153,7 +5184,7 @@ const bsdfSamplingGLSL = /* glsl */`
5153
5184
  vec3 halfVector = ggxDirection(
5154
5185
  wo,
5155
5186
  vec2( roughness ),
5156
- sobol2( 12 )
5187
+ rand2( 12 )
5157
5188
  );
5158
5189
 
5159
5190
  // apply to new ray by reflecting off the new normal
@@ -5164,7 +5195,7 @@ const bsdfSamplingGLSL = /* glsl */`
5164
5195
 
5165
5196
  // transmission
5166
5197
  /*
5167
- float transmissionEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRecord surf, out vec3 color ) {
5198
+ float transmissionEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRecord surf, inout vec3 color ) {
5168
5199
 
5169
5200
  // See section 4.2 in https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.pdf
5170
5201
 
@@ -5190,7 +5221,7 @@ const bsdfSamplingGLSL = /* glsl */`
5190
5221
  vec3 halfVector = ggxDirection(
5191
5222
  wo,
5192
5223
  vec2( filteredRoughness ),
5193
- sobol2( 13 )
5224
+ rand2( 13 )
5194
5225
  );
5195
5226
 
5196
5227
  vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
@@ -5207,12 +5238,13 @@ const bsdfSamplingGLSL = /* glsl */`
5207
5238
 
5208
5239
  // TODO: This is just using a basic cosine-weighted specular distribution with an
5209
5240
  // incorrect PDF value at the moment. Update it to correctly use a GGX distribution
5210
- float transmissionEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRecord surf, out vec3 color ) {
5241
+ float transmissionEval( vec3 wo, vec3 wi, vec3 wh, SurfaceRecord surf, inout vec3 color ) {
5211
5242
 
5212
5243
  color = surf.transmission * surf.color;
5213
5244
 
5214
5245
  // PDF
5215
- float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
5246
+ // float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
5247
+ float F = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
5216
5248
  if ( F >= 1.0 ) {
5217
5249
 
5218
5250
  return 0.0;
@@ -5227,7 +5259,7 @@ const bsdfSamplingGLSL = /* glsl */`
5227
5259
 
5228
5260
  float roughness = surf.filteredRoughness;
5229
5261
  float eta = surf.eta;
5230
- vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere( sobol2( 13 ) ) * roughness );
5262
+ vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere( rand2( 13 ) ) * roughness );
5231
5263
  vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
5232
5264
 
5233
5265
  if ( surf.thinFilm ) {
@@ -5268,7 +5300,7 @@ const bsdfSamplingGLSL = /* glsl */`
5268
5300
  vec3 halfVector = ggxDirection(
5269
5301
  wo,
5270
5302
  vec2( roughness ),
5271
- sobol2( 14 )
5303
+ rand2( 14 )
5272
5304
  );
5273
5305
 
5274
5306
  // apply to new ray by reflecting off the new normal
@@ -5298,12 +5330,13 @@ const bsdfSamplingGLSL = /* glsl */`
5298
5330
  // bsdf
5299
5331
  void getLobeWeights(
5300
5332
  vec3 wo, vec3 wi, vec3 wh, vec3 clearcoatWo, SurfaceRecord surf,
5301
- out float diffuseWeight, out float specularWeight, out float transmissionWeight, out float clearcoatWeight
5333
+ inout float diffuseWeight, inout float specularWeight, inout float transmissionWeight, inout float clearcoatWeight
5302
5334
  ) {
5303
5335
 
5304
5336
  float metalness = surf.metalness;
5305
5337
  float transmission = surf.transmission;
5306
- float fEstimate = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
5338
+ // float fEstimate = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
5339
+ float fEstimate = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
5307
5340
 
5308
5341
  float transSpecularProb = mix( max( 0.25, fEstimate ), 1.0, metalness );
5309
5342
  float diffSpecularProb = 0.5 + 0.5 * metalness;
@@ -5322,7 +5355,7 @@ const bsdfSamplingGLSL = /* glsl */`
5322
5355
 
5323
5356
  float bsdfEval(
5324
5357
  vec3 wo, vec3 clearcoatWo, vec3 wi, vec3 clearcoatWi, SurfaceRecord surf,
5325
- float diffuseWeight, float specularWeight, float transmissionWeight, float clearcoatWeight, out float specularPdf, out vec3 color
5358
+ float diffuseWeight, float specularWeight, float transmissionWeight, float clearcoatWeight, inout float specularPdf, inout vec3 color
5326
5359
  ) {
5327
5360
 
5328
5361
  float metalness = surf.metalness;
@@ -5385,7 +5418,7 @@ const bsdfSamplingGLSL = /* glsl */`
5385
5418
 
5386
5419
  }
5387
5420
 
5388
- float bsdfResult( vec3 worldWo, vec3 worldWi, SurfaceRecord surf, out vec3 color ) {
5421
+ float bsdfResult( vec3 worldWo, vec3 worldWi, SurfaceRecord surf, inout vec3 color ) {
5389
5422
 
5390
5423
  if ( surf.volumeParticle ) {
5391
5424
 
@@ -5419,7 +5452,7 @@ const bsdfSamplingGLSL = /* glsl */`
5419
5452
  ScatterRecord sampleRec;
5420
5453
  sampleRec.specularPdf = 0.0;
5421
5454
  sampleRec.pdf = 1.0 / ( 4.0 * PI );
5422
- sampleRec.direction = sampleSphere( sobol2( 16 ) );
5455
+ sampleRec.direction = sampleSphere( rand2( 16 ) );
5423
5456
  sampleRec.color = surf.color / ( 4.0 * PI );
5424
5457
  return sampleRec;
5425
5458
 
@@ -5471,7 +5504,7 @@ const bsdfSamplingGLSL = /* glsl */`
5471
5504
  vec3 wi;
5472
5505
  vec3 clearcoatWi;
5473
5506
 
5474
- float r = sobol( 15 );
5507
+ float r = rand( 15 );
5475
5508
  if ( r <= cdf[0] ) { // diffuse
5476
5509
 
5477
5510
  wi = diffuseDirection( wo, surf );
@@ -5553,14 +5586,14 @@ const equirectSamplingGLSL = /* glsl */`
5553
5586
  }
5554
5587
 
5555
5588
  // samples the color given env map with CDF and returns the pdf of the direction
5556
- float sampleEquirect( EquirectHdrInfo info, vec3 direction, out vec3 color ) {
5589
+ float sampleEquirect( vec3 direction, inout vec3 color ) {
5557
5590
 
5558
5591
  vec2 uv = equirectDirectionToUv( direction );
5559
- color = texture2D( info.map, uv ).rgb;
5592
+ color = texture2D( envMapInfo.map, uv ).rgb;
5560
5593
 
5561
- float totalSum = info.totalSum;
5594
+ float totalSum = envMapInfo.totalSum;
5562
5595
  float lum = luminance( color );
5563
- ivec2 resolution = textureSize( info.map, 0 );
5596
+ ivec2 resolution = textureSize( envMapInfo.map, 0 );
5564
5597
  float pdf = lum / totalSum;
5565
5598
 
5566
5599
  return float( resolution.x * resolution.y ) * pdf * equirectDirectionPdf( direction );
@@ -5568,20 +5601,20 @@ const equirectSamplingGLSL = /* glsl */`
5568
5601
  }
5569
5602
 
5570
5603
  // samples a direction of the envmap with color and retrieves pdf
5571
- float sampleEquirectProbability( EquirectHdrInfo info, vec2 r, out vec3 color, out vec3 direction ) {
5604
+ float sampleEquirectProbability( vec2 r, inout vec3 color, inout vec3 direction ) {
5572
5605
 
5573
5606
  // sample env map cdf
5574
- float v = texture2D( info.marginalWeights, vec2( r.x, 0.0 ) ).x;
5575
- float u = texture2D( info.conditionalWeights, vec2( r.y, v ) ).x;
5607
+ float v = texture2D( envMapInfo.marginalWeights, vec2( r.x, 0.0 ) ).x;
5608
+ float u = texture2D( envMapInfo.conditionalWeights, vec2( r.y, v ) ).x;
5576
5609
  vec2 uv = vec2( u, v );
5577
5610
 
5578
5611
  vec3 derivedDirection = equirectUvToDirection( uv );
5579
5612
  direction = derivedDirection;
5580
- color = texture2D( info.map, uv ).rgb;
5613
+ color = texture2D( envMapInfo.map, uv ).rgb;
5581
5614
 
5582
- float totalSum = info.totalSum;
5615
+ float totalSum = envMapInfo.totalSum;
5583
5616
  float lum = luminance( color );
5584
- ivec2 resolution = textureSize( info.map, 0 );
5617
+ ivec2 resolution = textureSize( envMapInfo.map, 0 );
5585
5618
  float pdf = lum / totalSum;
5586
5619
 
5587
5620
  return float( resolution.x * resolution.y ) * pdf * equirectDirectionPdf( direction );
@@ -5634,24 +5667,17 @@ const lightSamplingGLSL = /* glsl */`
5634
5667
 
5635
5668
  };
5636
5669
 
5637
- bool lightsClosestHit( sampler2D lights, uint lightCount, vec3 rayOrigin, vec3 rayDirection, out LightRecord lightRec ) {
5670
+ bool intersectLightAtIndex( sampler2D lights, vec3 rayOrigin, vec3 rayDirection, uint l, inout LightRecord lightRec ) {
5638
5671
 
5639
5672
  bool didHit = false;
5640
- uint l;
5641
- for ( l = 0u; l < lightCount; l ++ ) {
5642
-
5643
- Light light = readLightInfo( lights, l );
5644
-
5645
- vec3 u = light.u;
5646
- vec3 v = light.v;
5647
-
5648
- // check for backface
5649
- vec3 normal = normalize( cross( u, v ) );
5650
- if ( dot( normal, rayDirection ) < 0.0 ) {
5673
+ Light light = readLightInfo( lights, l );
5651
5674
 
5652
- continue;
5675
+ vec3 u = light.u;
5676
+ vec3 v = light.v;
5653
5677
 
5654
- }
5678
+ // check for backface
5679
+ vec3 normal = normalize( cross( u, v ) );
5680
+ if ( dot( normal, rayDirection ) > 0.0 ) {
5655
5681
 
5656
5682
  u *= 1.0 / dot( u, u );
5657
5683
  v *= 1.0 / dot( v, v );
@@ -5664,17 +5690,13 @@ const lightSamplingGLSL = /* glsl */`
5664
5690
  ( light.type == CIRC_AREA_LIGHT_TYPE && intersectsCircle( light.position, normal, u, v, rayOrigin, rayDirection, dist ) )
5665
5691
  ) {
5666
5692
 
5667
- if ( ! didHit || dist < lightRec.dist ) {
5668
-
5669
- float cosTheta = dot( rayDirection, normal );
5670
- didHit = true;
5671
- lightRec.dist = dist;
5672
- lightRec.pdf = ( dist * dist ) / ( light.area * cosTheta );
5673
- lightRec.emission = light.color * light.intensity;
5674
- lightRec.direction = rayDirection;
5675
- lightRec.type = light.type;
5676
-
5677
- }
5693
+ float cosTheta = dot( rayDirection, normal );
5694
+ didHit = true;
5695
+ lightRec.dist = dist;
5696
+ lightRec.pdf = ( dist * dist ) / ( light.area * cosTheta );
5697
+ lightRec.emission = light.color * light.intensity;
5698
+ lightRec.direction = rayDirection;
5699
+ lightRec.type = light.type;
5678
5700
 
5679
5701
  }
5680
5702
 
@@ -5686,11 +5708,6 @@ const lightSamplingGLSL = /* glsl */`
5686
5708
 
5687
5709
  LightRecord randomAreaLightSample( Light light, vec3 rayOrigin, vec2 ruv ) {
5688
5710
 
5689
- LightRecord lightRec;
5690
- lightRec.type = light.type;
5691
-
5692
- lightRec.emission = light.color * light.intensity;
5693
-
5694
5711
  vec3 randomPos;
5695
5712
  if( light.type == RECT_AREA_LIGHT_TYPE ) {
5696
5713
 
@@ -5711,12 +5728,17 @@ const lightSamplingGLSL = /* glsl */`
5711
5728
 
5712
5729
  vec3 toLight = randomPos - rayOrigin;
5713
5730
  float lightDistSq = dot( toLight, toLight );
5714
- lightRec.dist = sqrt( lightDistSq );
5731
+ float dist = sqrt( lightDistSq );
5732
+ vec3 direction = toLight / dist;
5733
+ vec3 lightNormal = normalize( cross( light.u, light.v ) );
5715
5734
 
5716
- vec3 direction = toLight / lightRec.dist;
5735
+ LightRecord lightRec;
5736
+ lightRec.type = light.type;
5737
+ lightRec.emission = light.color * light.intensity;
5738
+ lightRec.dist = dist;
5717
5739
  lightRec.direction = direction;
5718
5740
 
5719
- vec3 lightNormal = normalize( cross( light.u, light.v ) );
5741
+ // TODO: the denominator is potentially zero
5720
5742
  lightRec.pdf = lightDistSq / ( light.area * dot( direction, lightNormal ) );
5721
5743
 
5722
5744
  return lightRec;
@@ -5764,13 +5786,15 @@ const lightSamplingGLSL = /* glsl */`
5764
5786
 
5765
5787
  LightRecord randomLightSample( sampler2D lights, sampler2DArray iesProfiles, uint lightCount, vec3 rayOrigin, vec3 ruv ) {
5766
5788
 
5789
+ LightRecord result;
5790
+
5767
5791
  // pick a random light
5768
5792
  uint l = uint( ruv.x * float( lightCount ) );
5769
5793
  Light light = readLightInfo( lights, l );
5770
5794
 
5771
5795
  if ( light.type == SPOT_LIGHT_TYPE ) {
5772
5796
 
5773
- return randomSpotLightSample( light, iesProfiles, rayOrigin, ruv.yz );
5797
+ result = randomSpotLightSample( light, iesProfiles, rayOrigin, ruv.yz );
5774
5798
 
5775
5799
  } else if ( light.type == POINT_LIGHT_TYPE ) {
5776
5800
 
@@ -5790,7 +5814,7 @@ const lightSamplingGLSL = /* glsl */`
5790
5814
  rec.pdf = 1.0;
5791
5815
  rec.emission = light.color * light.intensity * distanceFalloff;
5792
5816
  rec.type = light.type;
5793
- return rec;
5817
+ result = rec;
5794
5818
 
5795
5819
  } else if ( light.type == DIR_LIGHT_TYPE ) {
5796
5820
 
@@ -5801,15 +5825,17 @@ const lightSamplingGLSL = /* glsl */`
5801
5825
  rec.emission = light.color * light.intensity;
5802
5826
  rec.type = light.type;
5803
5827
 
5804
- return rec;
5828
+ result = rec;
5805
5829
 
5806
5830
  } else {
5807
5831
 
5808
5832
  // sample the light
5809
- return randomAreaLightSample( light, rayOrigin, ruv.yz );
5833
+ result = randomAreaLightSample( light, rayOrigin, ruv.yz );
5810
5834
 
5811
5835
  }
5812
5836
 
5837
+ return result;
5838
+
5813
5839
  }
5814
5840
 
5815
5841
  `;
@@ -5906,7 +5932,7 @@ const intersectShapesGLSL = /* glsl */`
5906
5932
  // Finds the point where the ray intersects the plane defined by u and v and checks if this point
5907
5933
  // falls in the bounds of the rectangle on that same plane.
5908
5934
  // Plane intersection: https://lousodrome.net/blog/light/2020/07/03/intersection-of-a-ray-and-a-plane/
5909
- bool intersectsRectangle( vec3 center, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
5935
+ bool intersectsRectangle( vec3 center, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, inout float dist ) {
5910
5936
 
5911
5937
  float t = dot( center - rayOrigin, normal ) / dot( rayDirection, normal );
5912
5938
 
@@ -5937,7 +5963,7 @@ const intersectShapesGLSL = /* glsl */`
5937
5963
 
5938
5964
  // Finds the point where the ray intersects the plane defined by u and v and checks if this point
5939
5965
  // falls in the bounds of the circle on that same plane. See above URL for a description of the plane intersection algorithm.
5940
- bool intersectsCircle( vec3 position, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, out float dist ) {
5966
+ bool intersectsCircle( vec3 position, vec3 normal, vec3 u, vec3 v, vec3 rayOrigin, vec3 rayDirection, inout float dist ) {
5941
5967
 
5942
5968
  float t = dot( position - rayOrigin, normal ) / dot( rayDirection, normal );
5943
5969
 
@@ -6117,32 +6143,38 @@ const fresnelGLSL = /* glsl */`
6117
6143
 
6118
6144
  }
6119
6145
 
6120
- float evaluateFresnelWeight( float cosTheta, float eta, float f0 ) {
6146
+ // TODO: disney fresnel was removed and replaced with this fresnel function to better align with
6147
+ // the glTF but is causing blown out pixels. Should be revisited
6148
+ // float evaluateFresnelWeight( float cosTheta, float eta, float f0 ) {
6121
6149
 
6122
- if ( totalInternalReflection( cosTheta, eta ) ) {
6150
+ // if ( totalInternalReflection( cosTheta, eta ) ) {
6123
6151
 
6124
- return 1.0;
6152
+ // return 1.0;
6125
6153
 
6126
- }
6154
+ // }
6127
6155
 
6128
- return schlickFresnel( cosTheta, f0 );
6156
+ // return schlickFresnel( cosTheta, f0 );
6129
6157
 
6130
- }
6158
+ // }
6131
6159
 
6132
- /*
6133
6160
  // https://schuttejoe.github.io/post/disneybsdf/
6134
6161
  float disneyFresnel( vec3 wo, vec3 wi, vec3 wh, float f0, float eta, float metalness ) {
6135
6162
 
6136
6163
  float dotHV = dot( wo, wh );
6137
- float dotHL = dot( wi, wh );
6164
+ if ( totalInternalReflection( dotHV, eta ) ) {
6165
+
6166
+ return 1.0;
6138
6167
 
6168
+ }
6169
+
6170
+ float dotHL = dot( wi, wh );
6139
6171
  float dielectricFresnel = dielectricFresnel( abs( dotHV ), eta );
6140
6172
  float metallicFresnel = schlickFresnel( dotHL, f0 );
6141
6173
 
6142
6174
  return mix( dielectricFresnel, metallicFresnel, metalness );
6143
6175
 
6144
6176
  }
6145
- */
6177
+
6146
6178
  `;
6147
6179
 
6148
6180
  const arraySamplerTexelFetchGLSL = /*glsl */`
@@ -6199,28 +6231,28 @@ const pcgGLSL = /* glsl */`
6199
6231
  }
6200
6232
 
6201
6233
  // returns [ 0, 1 ]
6202
- float rand() {
6234
+ float pcgRand() {
6203
6235
 
6204
6236
  pcg4d( WHITE_NOISE_SEED );
6205
6237
  return float( WHITE_NOISE_SEED.x ) / float( 0xffffffffu );
6206
6238
 
6207
6239
  }
6208
6240
 
6209
- vec2 rand2() {
6241
+ vec2 pcgRand2() {
6210
6242
 
6211
6243
  pcg4d( WHITE_NOISE_SEED );
6212
6244
  return vec2( WHITE_NOISE_SEED.xy ) / float(0xffffffffu);
6213
6245
 
6214
6246
  }
6215
6247
 
6216
- vec3 rand3() {
6248
+ vec3 pcgRand3() {
6217
6249
 
6218
6250
  pcg4d( WHITE_NOISE_SEED );
6219
6251
  return vec3( WHITE_NOISE_SEED.xyz ) / float( 0xffffffffu );
6220
6252
 
6221
6253
  }
6222
6254
 
6223
- vec4 rand4() {
6255
+ vec4 pcgRand4() {
6224
6256
 
6225
6257
  pcg4d( WHITE_NOISE_SEED );
6226
6258
  return vec4( WHITE_NOISE_SEED ) / float( 0xffffffffu );
@@ -6293,7 +6325,7 @@ const cameraUtilsGLSL = /* glsl */`
6293
6325
 
6294
6326
  // Jitter the camera ray by finding a uv coordinate at a random sample
6295
6327
  // around this pixel's UV coordinate for AA
6296
- vec2 ruv = sobol2( 0 );
6328
+ vec2 ruv = rand2( 0 );
6297
6329
  vec2 jitteredUv = vUv + vec2( tentFilter( ruv.x ) * ssd.x, tentFilter( ruv.y ) * ssd.y );
6298
6330
  Ray ray;
6299
6331
 
@@ -6338,7 +6370,7 @@ const cameraUtilsGLSL = /* glsl */`
6338
6370
 
6339
6371
  // get the aperture sample
6340
6372
  // if blades === 0 then we assume a circle
6341
- vec3 shapeUVW= sobol3( 1 );
6373
+ vec3 shapeUVW= rand3( 1 );
6342
6374
  int blades = physicalCamera.apertureBlades;
6343
6375
  float anamorphicRatio = physicalCamera.anamorphicRatio;
6344
6376
  vec2 apertureSample = blades == 0 ? sampleCircle( shapeUVW.xy ) : sampleRegularPolygon( blades, shapeUVW );
@@ -6369,11 +6401,14 @@ const attenuateHitGLSL = /* glsl */`
6369
6401
  // step through multiple surface hits and accumulate color attenuation based on transmissive surfaces
6370
6402
  // returns true if a solid surface was hit
6371
6403
  bool attenuateHit(
6372
- BVH bvh, RenderState state,
6404
+ RenderState state,
6373
6405
  Ray ray, float rayDist,
6374
6406
  out vec3 color
6375
6407
  ) {
6376
6408
 
6409
+ // store the original bounce index so we can reset it after
6410
+ uint originalBounceIndex = sobolBounceIndex;
6411
+
6377
6412
  int traversals = state.traversals;
6378
6413
  int transmissiveTraversals = state.transmissiveTraversals;
6379
6414
  bool isShadowRay = state.isShadowRay;
@@ -6383,34 +6418,28 @@ const attenuateHitGLSL = /* glsl */`
6383
6418
 
6384
6419
  // hit results
6385
6420
  SurfaceHit surfaceHit;
6386
- LightRecord lightRec;
6387
6421
 
6388
6422
  color = vec3( 1.0 );
6389
6423
 
6390
- // TODO: we should be using sobol sampling here instead of rand but the sobol bounce and path indices need to be incremented
6391
- // and then reset.
6424
+ bool result = true;
6392
6425
  for ( int i = 0; i < traversals; i ++ ) {
6393
6426
 
6394
- int hitType = traceScene(
6395
- ray, bvh, lights, fogMaterial,
6396
- surfaceHit, lightRec
6397
- );
6398
-
6399
- if ( hitType == FOG_HIT ) {
6427
+ sobolBounceIndex ++;
6400
6428
 
6401
- return true;
6429
+ int hitType = traceScene( ray, fogMaterial, surfaceHit );
6402
6430
 
6403
- } else if ( hitType == LIGHT_HIT ) {
6431
+ if ( hitType == FOG_HIT ) {
6404
6432
 
6405
- float totalDist = distance( startPoint, ray.origin + ray.direction * lightRec.dist );
6406
- return totalDist < rayDist - max( totalDist, rayDist ) * 1e-4;
6433
+ result = true;
6434
+ break;
6407
6435
 
6408
6436
  } else if ( hitType == SURFACE_HIT ) {
6409
6437
 
6410
6438
  float totalDist = distance( startPoint, ray.origin + ray.direction * surfaceHit.dist );
6411
6439
  if ( totalDist > rayDist ) {
6412
6440
 
6413
- return false;
6441
+ result = false;
6442
+ break;
6414
6443
 
6415
6444
  }
6416
6445
 
@@ -6492,7 +6521,7 @@ const attenuateHitGLSL = /* glsl */`
6492
6521
  bool useAlphaTest = alphaTest != 0.0;
6493
6522
  float transmissionFactor = ( 1.0 - metalness ) * transmission;
6494
6523
  if (
6495
- transmissionFactor < rand() && ! (
6524
+ transmissionFactor < rand( 9 ) && ! (
6496
6525
  // material sidedness
6497
6526
  material.side != 0.0 && surfaceHit.side == material.side
6498
6527
 
@@ -6500,11 +6529,12 @@ const attenuateHitGLSL = /* glsl */`
6500
6529
  || useAlphaTest && albedo.a < alphaTest
6501
6530
 
6502
6531
  // opacity
6503
- || material.transparent && ! useAlphaTest && albedo.a < rand()
6532
+ || material.transparent && ! useAlphaTest && albedo.a < rand( 10 )
6504
6533
  )
6505
6534
  ) {
6506
6535
 
6507
- return true;
6536
+ result = true;
6537
+ break;
6508
6538
 
6509
6539
  }
6510
6540
 
@@ -6530,13 +6560,16 @@ const attenuateHitGLSL = /* glsl */`
6530
6560
 
6531
6561
  } else {
6532
6562
 
6533
- return false;
6563
+ result = false;
6564
+ break;
6534
6565
 
6535
6566
  }
6536
6567
 
6537
6568
  }
6538
6569
 
6539
- return true;
6570
+ // reset the bounce index
6571
+ sobolBounceIndex = originalBounceIndex;
6572
+ return result;
6540
6573
 
6541
6574
  }
6542
6575
 
@@ -6549,22 +6582,26 @@ const traceSceneGLSL = /* glsl */`
6549
6582
  #define LIGHT_HIT 2
6550
6583
  #define FOG_HIT 3
6551
6584
 
6585
+ // Passing the global variable 'lights' into this function caused shader program errors.
6586
+ // So global variables like 'lights' and 'bvh' were moved out of the function parameters.
6587
+ // For more information, refer to: https://github.com/gkjohnson/three-gpu-pathtracer/pull/457
6552
6588
  int traceScene(
6553
6589
 
6554
- Ray ray, BVH bvh, LightsInfo lights, Material fogMaterial,
6555
- out SurfaceHit surfaceHit, out LightRecord lightRec
6590
+ Ray ray, Material fogMaterial, inout SurfaceHit surfaceHit
6556
6591
 
6557
6592
  ) {
6558
6593
 
6594
+ int result = NO_HIT;
6559
6595
  bool hit = bvhIntersectFirstHit( bvh, ray.origin, ray.direction, surfaceHit.faceIndices, surfaceHit.faceNormal, surfaceHit.barycoord, surfaceHit.side, surfaceHit.dist );
6560
- bool lightHit = lightsClosestHit( lights.tex, lights.count, ray.origin, ray.direction, lightRec );
6561
6596
 
6562
6597
  #if FEATURE_FOG
6563
6598
 
6564
6599
  if ( fogMaterial.fogVolume ) {
6565
6600
 
6566
- float particleDist = intersectFogVolume( fogMaterial, sobol( 1 ) );
6567
- if ( particleDist + 1e-4 < surfaceHit.dist && ( particleDist + 1e-4 < lightRec.dist || ! lightHit ) ) {
6601
+ // offset the distance so we don't run into issues with particles on the same surface
6602
+ // as other objects
6603
+ float particleDist = intersectFogVolume( fogMaterial, rand( 1 ) );
6604
+ if ( particleDist + RAY_OFFSET < surfaceHit.dist ) {
6568
6605
 
6569
6606
  surfaceHit.side = 1.0;
6570
6607
  surfaceHit.faceNormal = normalize( - ray.direction );
@@ -6577,19 +6614,13 @@ const traceSceneGLSL = /* glsl */`
6577
6614
 
6578
6615
  #endif
6579
6616
 
6580
- if ( lightHit && ( lightRec.dist < surfaceHit.dist || ! hit ) ) {
6581
-
6582
- return LIGHT_HIT;
6583
-
6584
- }
6585
-
6586
6617
  if ( hit ) {
6587
6618
 
6588
- return SURFACE_HIT;
6619
+ result = SURFACE_HIT;
6589
6620
 
6590
6621
  }
6591
6622
 
6592
- return NO_HIT;
6623
+ return result;
6593
6624
 
6594
6625
  }
6595
6626
 
@@ -6602,7 +6633,7 @@ const getSurfaceRecordGLSL = /* glsl */`
6602
6633
  int getSurfaceRecord(
6603
6634
  Material material, SurfaceHit surfaceHit, sampler2DArray attributesArray,
6604
6635
  float accumulatedRoughness,
6605
- out SurfaceRecord surf
6636
+ inout SurfaceRecord surf
6606
6637
  ) {
6607
6638
 
6608
6639
  if ( material.fogVolume ) {
@@ -6632,6 +6663,7 @@ const getSurfaceRecordGLSL = /* glsl */`
6632
6663
 
6633
6664
  vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
6634
6665
  albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
6666
+
6635
6667
  }
6636
6668
 
6637
6669
  if ( material.vertexColors ) {
@@ -6663,7 +6695,7 @@ const getSurfaceRecordGLSL = /* glsl */`
6663
6695
  || useAlphaTest && albedo.a < alphaTest
6664
6696
 
6665
6697
  // opacity
6666
- || material.transparent && ! useAlphaTest && albedo.a < sobol( 3 )
6698
+ || material.transparent && ! useAlphaTest && albedo.a < rand( 3 )
6667
6699
  ) {
6668
6700
 
6669
6701
  return SKIP_SURFACE;
@@ -6922,11 +6954,13 @@ const directLightContributionGLSL = /*glsl*/`
6922
6954
 
6923
6955
  vec3 directLightContribution( vec3 worldWo, SurfaceRecord surf, RenderState state, vec3 rayOrigin ) {
6924
6956
 
6957
+ vec3 result = vec3( 0.0 );
6958
+
6925
6959
  // uniformly pick a light or environment map
6926
- if( lightsDenom != 0.0 && sobol( 5 ) < float( lights.count ) / lightsDenom ) {
6960
+ if( lightsDenom != 0.0 && rand( 5 ) < float( lights.count ) / lightsDenom ) {
6927
6961
 
6928
6962
  // sample a light or environment
6929
- LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, sobol3( 6 ) );
6963
+ LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, rand3( 6 ) );
6930
6964
 
6931
6965
  bool isSampleBelowSurface = ! surf.volumeParticle && dot( surf.faceNormal, lightRec.direction ) < 0.0;
6932
6966
  if ( isSampleBelowSurface ) {
@@ -6943,7 +6977,7 @@ const directLightContributionGLSL = /*glsl*/`
6943
6977
  if (
6944
6978
  lightRec.pdf > 0.0 &&
6945
6979
  isDirectionValid( lightRec.direction, surf.normal, surf.faceNormal ) &&
6946
- ! attenuateHit( bvh, state, lightRay, lightRec.dist, attenuatedColor )
6980
+ ! attenuateHit( state, lightRay, lightRec.dist, attenuatedColor )
6947
6981
  ) {
6948
6982
 
6949
6983
  // get the material pdf
@@ -6955,7 +6989,7 @@ const directLightContributionGLSL = /*glsl*/`
6955
6989
  // weight the direct light contribution
6956
6990
  float lightPdf = lightRec.pdf / lightsDenom;
6957
6991
  float misWeight = lightRec.type == SPOT_LIGHT_TYPE || lightRec.type == DIR_LIGHT_TYPE || lightRec.type == POINT_LIGHT_TYPE ? 1.0 : misHeuristic( lightPdf, lightMaterialPdf );
6958
- return attenuatedColor * lightRec.emission * state.throughputColor * sampleColor * misWeight / lightPdf;
6992
+ result = attenuatedColor * lightRec.emission * state.throughputColor * sampleColor * misWeight / lightPdf;
6959
6993
 
6960
6994
  }
6961
6995
 
@@ -6965,7 +6999,7 @@ const directLightContributionGLSL = /*glsl*/`
6965
6999
 
6966
7000
  // find a sample in the environment map to include in the contribution
6967
7001
  vec3 envColor, envDirection;
6968
- float envPdf = sampleEquirectProbability( envMapInfo, sobol2( 7 ), envColor, envDirection );
7002
+ float envPdf = sampleEquirectProbability( rand2( 7 ), envColor, envDirection );
6969
7003
  envDirection = invEnvRotation3x3 * envDirection;
6970
7004
 
6971
7005
  // this env sampling is not set up for transmissive sampling and yields overly bright
@@ -6986,7 +7020,7 @@ const directLightContributionGLSL = /*glsl*/`
6986
7020
  if (
6987
7021
  envPdf > 0.0 &&
6988
7022
  isDirectionValid( envDirection, surf.normal, surf.faceNormal ) &&
6989
- ! attenuateHit( bvh, state, envRay, INFINITY, attenuatedColor )
7023
+ ! attenuateHit( state, envRay, INFINITY, attenuatedColor )
6990
7024
  ) {
6991
7025
 
6992
7026
  // get the material pdf
@@ -6998,7 +7032,7 @@ const directLightContributionGLSL = /*glsl*/`
6998
7032
  // weight the direct light contribution
6999
7033
  envPdf /= lightsDenom;
7000
7034
  float misWeight = misHeuristic( envPdf, envMaterialPdf );
7001
- return attenuatedColor * environmentIntensity * envColor * state.throughputColor * sampleColor * misWeight / envPdf;
7035
+ result = attenuatedColor * environmentIntensity * envColor * state.throughputColor * sampleColor * misWeight / envPdf;
7002
7036
 
7003
7037
  }
7004
7038
 
@@ -7006,12 +7040,675 @@ const directLightContributionGLSL = /*glsl*/`
7006
7040
 
7007
7041
  }
7008
7042
 
7009
- return vec3( 0.0 );
7043
+ // Function changed to have a single return statement to potentially help with crashes on Mac OS.
7044
+ // See issue #470
7045
+ return result;
7046
+
7047
+ }
7048
+
7049
+ `;
7050
+
7051
+ const stratifiedTextureGLSL = /* glsl */`
7052
+
7053
+ uniform sampler2D stratifiedTexture;
7054
+ uniform sampler2D stratifiedOffsetTexture;
7055
+
7056
+ uint sobolPixelIndex = 0u;
7057
+ uint sobolPathIndex = 0u;
7058
+ uint sobolBounceIndex = 0u;
7059
+ vec4 pixelSeed = vec4( 0 );
7060
+
7061
+ vec4 rand4( int v ) {
7062
+
7063
+ ivec2 uv = ivec2( v, sobolBounceIndex );
7064
+ vec4 stratifiedSample = texelFetch( stratifiedTexture, uv, 0 );
7065
+ return fract( stratifiedSample + pixelSeed.r ); // blue noise + stratified samples
7066
+
7067
+ }
7068
+
7069
+ vec3 rand3( int v ) {
7070
+
7071
+ return rand4( v ).xyz;
7072
+
7073
+ }
7074
+
7075
+ vec2 rand2( int v ) {
7076
+
7077
+ return rand4( v ).xy;
7078
+
7079
+ }
7080
+
7081
+ float rand( int v ) {
7082
+
7083
+ return rand4( v ).x;
7084
+
7085
+ }
7086
+
7087
+ void rng_initialize( vec2 screenCoord, int frame ) {
7088
+
7089
+ // tile the small noise texture across the entire screen
7090
+ ivec2 noiseSize = ivec2( textureSize( stratifiedOffsetTexture, 0 ) );
7091
+ pixelSeed = texelFetch( stratifiedOffsetTexture, ivec2( screenCoord.xy ) % noiseSize, 0 );
7010
7092
 
7011
7093
  }
7012
7094
 
7013
7095
  `;
7014
7096
 
7097
+ // Stratified Sampling based on implementation from hoverinc pathtracer
7098
+ // - https://github.com/hoverinc/ray-tracing-renderer
7099
+ // - http://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Stratified_Sampling.html
7100
+
7101
+ function shuffle( arr ) {
7102
+
7103
+ for ( let i = arr.length - 1; i > 0; i -- ) {
7104
+
7105
+ const j = Math.floor( Math.random() * ( i + 1 ) );
7106
+ const x = arr[ i ];
7107
+ arr[ i ] = arr[ j ];
7108
+ arr[ j ] = x;
7109
+
7110
+ }
7111
+
7112
+ return arr;
7113
+
7114
+ }
7115
+
7116
+ // strataCount : The number of bins per dimension
7117
+ // dimensions : The number of dimensions to generate stratified values for
7118
+ class StratifiedSampler {
7119
+
7120
+ constructor( strataCount, dimensions ) {
7121
+
7122
+ const l = strataCount ** dimensions;
7123
+ const strata = new Uint16Array( l );
7124
+ let index = l;
7125
+
7126
+ // each integer represents a statum bin
7127
+ for ( let i = 0; i < l; i ++ ) {
7128
+
7129
+ strata[ i ] = i;
7130
+
7131
+ }
7132
+
7133
+ this.samples = new Float32Array( dimensions );
7134
+
7135
+ this.strataCount = strataCount;
7136
+
7137
+ this.restart = function () {
7138
+
7139
+ index = 0;
7140
+
7141
+ };
7142
+
7143
+ this.next = function () {
7144
+
7145
+ const { samples } = this;
7146
+
7147
+ if ( index >= strata.length ) {
7148
+
7149
+ shuffle( strata );
7150
+ this.restart();
7151
+
7152
+ }
7153
+
7154
+ let stratum = strata[ index ++ ];
7155
+
7156
+ for ( let i = 0; i < dimensions; i ++ ) {
7157
+
7158
+ samples[ i ] = ( stratum % strataCount + Math.random() ) / strataCount;
7159
+ stratum = Math.floor( stratum / strataCount );
7160
+
7161
+ }
7162
+
7163
+ return samples;
7164
+
7165
+ };
7166
+
7167
+ }
7168
+
7169
+ }
7170
+
7171
+ // Stratified Sampling based on implementation from hoverinc pathtracer
7172
+
7173
+ // Stratified set of data with each tuple stratified separately and combined
7174
+ class StratifiedSamplerCombined {
7175
+
7176
+ constructor( strataCount, listOfDimensions ) {
7177
+
7178
+ let totalDim = 0;
7179
+ for ( const dim of listOfDimensions ) {
7180
+
7181
+ totalDim += dim;
7182
+
7183
+ }
7184
+
7185
+ const combined = new Float32Array( totalDim );
7186
+ const strataObjs = [];
7187
+ let offset = 0;
7188
+ for ( const dim of listOfDimensions ) {
7189
+
7190
+ const sampler = new StratifiedSampler( strataCount, dim );
7191
+ sampler.samples = new Float32Array( combined.buffer, offset, sampler.samples.length );
7192
+ offset += sampler.samples.length * 4;
7193
+ strataObjs.push( sampler );
7194
+
7195
+ }
7196
+
7197
+ this.samples = combined;
7198
+
7199
+ this.strataCount = strataCount;
7200
+
7201
+ this.next = function () {
7202
+
7203
+ for ( const strata of strataObjs ) {
7204
+
7205
+ strata.next();
7206
+
7207
+ }
7208
+
7209
+ return combined;
7210
+
7211
+ };
7212
+
7213
+ this.restart = function () {
7214
+
7215
+ for ( const strata of strataObjs ) {
7216
+
7217
+ strata.restart();
7218
+
7219
+ }
7220
+
7221
+ };
7222
+
7223
+ }
7224
+
7225
+ }
7226
+
7227
+ class StratifiedSamplesTexture extends DataTexture {
7228
+
7229
+ constructor( count = 1, depth = 1, strata = 8 ) {
7230
+
7231
+ super( new Float32Array( 1 ), 1, 1, RGBAFormat, FloatType );
7232
+ this.minFilter = NearestFilter;
7233
+ this.magFilter = NearestFilter;
7234
+
7235
+ this.strata = strata;
7236
+ this.sampler = null;
7237
+
7238
+ this.init( count, depth, strata );
7239
+
7240
+ }
7241
+
7242
+ init( count, depth, strata = this.strata ) {
7243
+
7244
+ const { image } = this;
7245
+ if ( image.width === depth && image.height === count ) {
7246
+
7247
+ return;
7248
+
7249
+ }
7250
+
7251
+ const dimensions = new Array( count * depth ).fill( 4 );
7252
+ const sampler = new StratifiedSamplerCombined( strata, dimensions );
7253
+
7254
+ image.width = depth;
7255
+ image.height = count;
7256
+ image.data = sampler.samples;
7257
+
7258
+ this.sampler = sampler;
7259
+
7260
+ this.dispose();
7261
+ this.next();
7262
+
7263
+ }
7264
+
7265
+ next() {
7266
+
7267
+ this.sampler.next();
7268
+ this.needsUpdate = true;
7269
+
7270
+ }
7271
+
7272
+ }
7273
+
7274
+ function shuffleArray( array, random = Math.random ) {
7275
+
7276
+ for ( let i = array.length - 1; i > 0; i -- ) {
7277
+
7278
+ const replaceIndex = ~ ~ ( ( random() - 1e-6 ) * i );
7279
+ const tmp = array[ i ];
7280
+ array[ i ] = array[ replaceIndex ];
7281
+ array[ replaceIndex ] = tmp;
7282
+
7283
+ }
7284
+
7285
+ }
7286
+
7287
+ function fillWithOnes( array, count ) {
7288
+
7289
+ array.fill( 0 );
7290
+
7291
+ for ( let i = 0; i < count; i ++ ) {
7292
+
7293
+ array[ i ] = 1;
7294
+
7295
+ }
7296
+
7297
+ }
7298
+
7299
+ class BlueNoiseSamples {
7300
+
7301
+ constructor( size ) {
7302
+
7303
+ this.count = 0;
7304
+ this.size = - 1;
7305
+ this.sigma = - 1;
7306
+ this.radius = - 1;
7307
+ this.lookupTable = null;
7308
+ this.score = null;
7309
+ this.binaryPattern = null;
7310
+
7311
+ this.resize( size );
7312
+ this.setSigma( 1.5 );
7313
+
7314
+ }
7315
+
7316
+ findVoid() {
7317
+
7318
+ const { score, binaryPattern } = this;
7319
+
7320
+ let currValue = Infinity;
7321
+ let currIndex = - 1;
7322
+ for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
7323
+
7324
+ if ( binaryPattern[ i ] !== 0 ) {
7325
+
7326
+ continue;
7327
+
7328
+ }
7329
+
7330
+ const pScore = score[ i ];
7331
+ if ( pScore < currValue ) {
7332
+
7333
+ currValue = pScore;
7334
+ currIndex = i;
7335
+
7336
+ }
7337
+
7338
+ }
7339
+
7340
+ return currIndex;
7341
+
7342
+ }
7343
+
7344
+ findCluster() {
7345
+
7346
+ const { score, binaryPattern } = this;
7347
+
7348
+ let currValue = - Infinity;
7349
+ let currIndex = - 1;
7350
+ for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
7351
+
7352
+ if ( binaryPattern[ i ] !== 1 ) {
7353
+
7354
+ continue;
7355
+
7356
+ }
7357
+
7358
+ const pScore = score[ i ];
7359
+ if ( pScore > currValue ) {
7360
+
7361
+ currValue = pScore;
7362
+ currIndex = i;
7363
+
7364
+ }
7365
+
7366
+ }
7367
+
7368
+ return currIndex;
7369
+
7370
+ }
7371
+
7372
+ setSigma( sigma ) {
7373
+
7374
+ if ( sigma === this.sigma ) {
7375
+
7376
+ return;
7377
+
7378
+ }
7379
+
7380
+ // generate a radius in which the score will be updated under the
7381
+ // assumption that e^-10 is insignificant enough to be the border at
7382
+ // which we drop off.
7383
+ const radius = ~ ~ ( Math.sqrt( 10 * 2 * ( sigma ** 2 ) ) + 1 );
7384
+ const lookupWidth = 2 * radius + 1;
7385
+ const lookupTable = new Float32Array( lookupWidth * lookupWidth );
7386
+ const sigma2 = sigma * sigma;
7387
+ for ( let x = - radius; x <= radius; x ++ ) {
7388
+
7389
+ for ( let y = - radius; y <= radius; y ++ ) {
7390
+
7391
+ const index = ( radius + y ) * lookupWidth + x + radius;
7392
+ const dist2 = x * x + y * y;
7393
+ lookupTable[ index ] = Math.E ** ( - dist2 / ( 2 * sigma2 ) );
7394
+
7395
+ }
7396
+
7397
+ }
7398
+
7399
+ this.lookupTable = lookupTable;
7400
+ this.sigma = sigma;
7401
+ this.radius = radius;
7402
+
7403
+ }
7404
+
7405
+ resize( size ) {
7406
+
7407
+ if ( this.size !== size ) {
7408
+
7409
+ this.size = size;
7410
+ this.score = new Float32Array( size * size );
7411
+ this.binaryPattern = new Uint8Array( size * size );
7412
+
7413
+ }
7414
+
7415
+
7416
+ }
7417
+
7418
+ invert() {
7419
+
7420
+ const { binaryPattern, score, size } = this;
7421
+
7422
+ score.fill( 0 );
7423
+
7424
+ for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
7425
+
7426
+ if ( binaryPattern[ i ] === 0 ) {
7427
+
7428
+ const y = ~ ~ ( i / size );
7429
+ const x = i - y * size;
7430
+ this.updateScore( x, y, 1 );
7431
+ binaryPattern[ i ] = 1;
7432
+
7433
+ } else {
7434
+
7435
+ binaryPattern[ i ] = 0;
7436
+
7437
+ }
7438
+
7439
+ }
7440
+
7441
+ }
7442
+
7443
+ updateScore( x, y, multiplier ) {
7444
+
7445
+ // TODO: Is there a way to keep track of the highest and lowest scores here to avoid have to search over
7446
+ // everything in the buffer?
7447
+ const { size, score, lookupTable } = this;
7448
+
7449
+ // const sigma2 = sigma * sigma;
7450
+ // const radius = Math.floor( size / 2 );
7451
+ const radius = this.radius;
7452
+ const lookupWidth = 2 * radius + 1;
7453
+ for ( let px = - radius; px <= radius; px ++ ) {
7454
+
7455
+ for ( let py = - radius; py <= radius; py ++ ) {
7456
+
7457
+ // const dist2 = px * px + py * py;
7458
+ // const value = Math.E ** ( - dist2 / ( 2 * sigma2 ) );
7459
+
7460
+ const lookupIndex = ( radius + py ) * lookupWidth + px + radius;
7461
+ const value = lookupTable[ lookupIndex ];
7462
+
7463
+ let sx = ( x + px );
7464
+ sx = sx < 0 ? size + sx : sx % size;
7465
+
7466
+ let sy = ( y + py );
7467
+ sy = sy < 0 ? size + sy : sy % size;
7468
+
7469
+ const sindex = sy * size + sx;
7470
+ score[ sindex ] += multiplier * value;
7471
+
7472
+ }
7473
+
7474
+ }
7475
+
7476
+ }
7477
+
7478
+ addPointIndex( index ) {
7479
+
7480
+ this.binaryPattern[ index ] = 1;
7481
+
7482
+ const size = this.size;
7483
+ const y = ~ ~ ( index / size );
7484
+ const x = index - y * size;
7485
+ this.updateScore( x, y, 1 );
7486
+ this.count ++;
7487
+
7488
+ }
7489
+
7490
+ removePointIndex( index ) {
7491
+
7492
+ this.binaryPattern[ index ] = 0;
7493
+
7494
+ const size = this.size;
7495
+ const y = ~ ~ ( index / size );
7496
+ const x = index - y * size;
7497
+ this.updateScore( x, y, - 1 );
7498
+ this.count --;
7499
+
7500
+ }
7501
+
7502
+ copy( source ) {
7503
+
7504
+ this.resize( source.size );
7505
+ this.score.set( source.score );
7506
+ this.binaryPattern.set( source.binaryPattern );
7507
+ this.setSigma( source.sigma );
7508
+ this.count = source.count;
7509
+
7510
+ }
7511
+
7512
+ }
7513
+
7514
+ class BlueNoiseGenerator {
7515
+
7516
+ constructor() {
7517
+
7518
+ this.random = Math.random;
7519
+ this.sigma = 1.5;
7520
+ this.size = 64;
7521
+ this.majorityPointsRatio = 0.1;
7522
+
7523
+ this.samples = new BlueNoiseSamples( 1 );
7524
+ this.savedSamples = new BlueNoiseSamples( 1 );
7525
+
7526
+ }
7527
+
7528
+ generate() {
7529
+
7530
+ // http://cv.ulichney.com/papers/1993-void-cluster.pdf
7531
+
7532
+ const {
7533
+ samples,
7534
+ savedSamples,
7535
+ sigma,
7536
+ majorityPointsRatio,
7537
+ size,
7538
+ } = this;
7539
+
7540
+ samples.resize( size );
7541
+ samples.setSigma( sigma );
7542
+
7543
+ // 1. Randomly place the minority points.
7544
+ const pointCount = Math.floor( size * size * majorityPointsRatio );
7545
+ const initialSamples = samples.binaryPattern;
7546
+
7547
+ fillWithOnes( initialSamples, pointCount );
7548
+ shuffleArray( initialSamples, this.random );
7549
+
7550
+ for ( let i = 0, l = initialSamples.length; i < l; i ++ ) {
7551
+
7552
+ if ( initialSamples[ i ] === 1 ) {
7553
+
7554
+ samples.addPointIndex( i );
7555
+
7556
+ }
7557
+
7558
+ }
7559
+
7560
+ // 2. Remove minority point that is in densest cluster and place it in the largest void.
7561
+ while ( true ) {
7562
+
7563
+ const clusterIndex = samples.findCluster();
7564
+ samples.removePointIndex( clusterIndex );
7565
+
7566
+ const voidIndex = samples.findVoid();
7567
+ if ( clusterIndex === voidIndex ) {
7568
+
7569
+ samples.addPointIndex( clusterIndex );
7570
+ break;
7571
+
7572
+ }
7573
+
7574
+ samples.addPointIndex( voidIndex );
7575
+
7576
+ }
7577
+
7578
+ // 3. PHASE I: Assign a rank to each progressively less dense cluster point and put it
7579
+ // in the dither array.
7580
+ const ditherArray = new Uint32Array( size * size );
7581
+ savedSamples.copy( samples );
7582
+
7583
+ let rank;
7584
+ rank = samples.count - 1;
7585
+ while ( rank >= 0 ) {
7586
+
7587
+ const clusterIndex = samples.findCluster();
7588
+ samples.removePointIndex( clusterIndex );
7589
+
7590
+ ditherArray[ clusterIndex ] = rank;
7591
+ rank --;
7592
+
7593
+ }
7594
+
7595
+ // 4. PHASE II: Do the same thing for the largest voids up to half of the total pixels using
7596
+ // the initial binary pattern.
7597
+ const totalSize = size * size;
7598
+ rank = savedSamples.count;
7599
+ while ( rank < totalSize / 2 ) {
7600
+
7601
+ const voidIndex = savedSamples.findVoid();
7602
+ savedSamples.addPointIndex( voidIndex );
7603
+ ditherArray[ voidIndex ] = rank;
7604
+ rank ++;
7605
+
7606
+ }
7607
+
7608
+ // 5. PHASE III: Invert the pattern and finish out by assigning a rank to the remaining
7609
+ // and iteratively removing them.
7610
+ savedSamples.invert();
7611
+
7612
+ while ( rank < totalSize ) {
7613
+
7614
+ const clusterIndex = savedSamples.findCluster();
7615
+ savedSamples.removePointIndex( clusterIndex );
7616
+ ditherArray[ clusterIndex ] = rank;
7617
+ rank ++;
7618
+
7619
+ }
7620
+
7621
+ return { data: ditherArray, maxValue: totalSize };
7622
+
7623
+ }
7624
+
7625
+ }
7626
+
7627
+ function getStride( channels ) {
7628
+
7629
+ if ( channels >= 3 ) {
7630
+
7631
+ return 4;
7632
+
7633
+ } else {
7634
+
7635
+ return channels;
7636
+
7637
+ }
7638
+
7639
+ }
7640
+
7641
+ function getFormat( channels ) {
7642
+
7643
+ switch ( channels ) {
7644
+
7645
+ case 1:
7646
+ return RedFormat;
7647
+ case 2:
7648
+ return RGFormat;
7649
+ default:
7650
+ return RGBAFormat;
7651
+
7652
+ }
7653
+
7654
+ }
7655
+
7656
+ class BlueNoiseTexture extends DataTexture {
7657
+
7658
+ constructor( size = 64, channels = 1 ) {
7659
+
7660
+ super( new Float32Array( 4 ), 1, 1, RGBAFormat, FloatType );
7661
+ this.minFilter = NearestFilter;
7662
+ this.magFilter = NearestFilter;
7663
+
7664
+ this.size = size;
7665
+ this.channels = channels;
7666
+ this.update();
7667
+
7668
+ }
7669
+
7670
+ update() {
7671
+
7672
+ const channels = this.channels;
7673
+ const size = this.size;
7674
+ const generator = new BlueNoiseGenerator();
7675
+ generator.channels = channels;
7676
+ generator.size = size;
7677
+
7678
+ const stride = getStride( channels );
7679
+ const format = getFormat( stride );
7680
+ if ( this.image.width !== size || format !== this.format ) {
7681
+
7682
+ this.image.width = size;
7683
+ this.image.height = size;
7684
+ this.image.data = new Float32Array( ( size ** 2 ) * stride );
7685
+ this.format = format;
7686
+ this.dispose();
7687
+
7688
+ }
7689
+
7690
+ const data = this.image.data;
7691
+ for ( let i = 0, l = channels; i < l; i ++ ) {
7692
+
7693
+ const result = generator.generate();
7694
+ const bin = result.data;
7695
+ const maxValue = result.maxValue;
7696
+
7697
+ for ( let j = 0, l2 = bin.length; j < l2; j ++ ) {
7698
+
7699
+ const value = bin[ j ] / maxValue;
7700
+ data[ j * stride + i ] = value;
7701
+
7702
+ }
7703
+
7704
+ }
7705
+
7706
+ this.needsUpdate = true;
7707
+
7708
+ }
7709
+
7710
+ }
7711
+
7015
7712
  class PhysicalPathTracingMaterial extends MaterialBase {
7016
7713
 
7017
7714
  onBeforeRender() {
@@ -7035,6 +7732,12 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7035
7732
  FEATURE_DOF: 1,
7036
7733
  FEATURE_BACKGROUND_MAP: 0,
7037
7734
  FEATURE_FOG: 1,
7735
+
7736
+ // 0 = PCG
7737
+ // 1 = Sobol
7738
+ // 2 = Stratified List
7739
+ RANDOM_TYPE: 2,
7740
+
7038
7741
  // 0 = Perspective
7039
7742
  // 1 = Orthographic
7040
7743
  // 2 = Equirectangular
@@ -7076,6 +7779,8 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7076
7779
 
7077
7780
  backgroundAlpha: { value: 1.0 },
7078
7781
  sobolTexture: { value: null },
7782
+ stratifiedTexture: { value: new StratifiedSamplesTexture() },
7783
+ stratifiedOffsetTexture: { value: new BlueNoiseTexture( 64, 1 ) },
7079
7784
  },
7080
7785
 
7081
7786
  vertexShader: /* glsl */`
@@ -7104,13 +7809,48 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7104
7809
  #include <common>
7105
7810
 
7106
7811
  // bvh intersection
7107
- ${ shaderStructs }
7108
- ${ shaderIntersectFunction }
7812
+ ${ BVHShaderGLSL.common_functions }
7813
+ ${ BVHShaderGLSL.bvh_struct_definitions }
7814
+ ${ BVHShaderGLSL.bvh_ray_functions }
7815
+
7816
+ // uniform structs
7817
+ ${ cameraStructGLSL }
7818
+ ${ lightsStructGLSL }
7819
+ ${ equirectStructGLSL }
7820
+ ${ materialStructGLSL }
7109
7821
 
7110
7822
  // random
7111
- ${ pcgGLSL }
7112
- ${ sobolCommonGLSL }
7113
- ${ sobolSamplingGLSL }
7823
+ #if RANDOM_TYPE == 2 // Stratified List
7824
+
7825
+ ${ stratifiedTextureGLSL }
7826
+
7827
+ #elif RANDOM_TYPE == 1 // Sobol
7828
+
7829
+ ${ pcgGLSL }
7830
+ ${ sobolCommonGLSL }
7831
+ ${ sobolSamplingGLSL }
7832
+
7833
+ #define rand(v) sobol(v)
7834
+ #define rand2(v) sobol2(v)
7835
+ #define rand3(v) sobol3(v)
7836
+ #define rand4(v) sobol4(v)
7837
+
7838
+ #else // PCG
7839
+
7840
+ ${ pcgGLSL }
7841
+
7842
+ // Using the sobol functions seems to break the the compiler on MacOS
7843
+ // - specifically the "sobolReverseBits" function.
7844
+ uint sobolPixelIndex = 0u;
7845
+ uint sobolPathIndex = 0u;
7846
+ uint sobolBounceIndex = 0u;
7847
+
7848
+ #define rand(v) pcgRand()
7849
+ #define rand2(v) pcgRand2()
7850
+ #define rand3(v) pcgRand3()
7851
+ #define rand4(v) pcgRand4()
7852
+
7853
+ #endif
7114
7854
 
7115
7855
  // common
7116
7856
  ${ arraySamplerTexelFetchGLSL }
@@ -7119,20 +7859,6 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7119
7859
  ${ mathGLSL }
7120
7860
  ${ intersectShapesGLSL }
7121
7861
 
7122
- // uniform structs
7123
- ${ cameraStructGLSL }
7124
- ${ lightsStructGLSL }
7125
- ${ equirectStructGLSL }
7126
- ${ materialStructGLSL }
7127
- ${ fogMaterialBvhGLSL }
7128
-
7129
- // sampling
7130
- ${ shapeSamplingGLSL }
7131
- ${ bsdfSamplingGLSL }
7132
- ${ equirectSamplingGLSL }
7133
- ${ lightSamplingGLSL }
7134
- ${ fogGLSL }
7135
-
7136
7862
  // environment
7137
7863
  uniform EquirectHdrInfo envMapInfo;
7138
7864
  uniform mat4 environmentRotation;
@@ -7184,6 +7910,14 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7184
7910
  mat3 invEnvRotation3x3;
7185
7911
  float lightsDenom;
7186
7912
 
7913
+ // sampling
7914
+ ${ fogMaterialBvhGLSL }
7915
+ ${ shapeSamplingGLSL }
7916
+ ${ bsdfSamplingGLSL }
7917
+ ${ equirectSamplingGLSL }
7918
+ ${ lightSamplingGLSL }
7919
+ ${ fogGLSL }
7920
+
7187
7921
  float applyFilteredGlossy( float roughness, float accumulatedRoughness ) {
7188
7922
 
7189
7923
  return clamp(
@@ -7223,7 +7957,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7223
7957
 
7224
7958
  // init
7225
7959
  rng_initialize( gl_FragCoord.xy, seed );
7226
- sobolPixelIndex = ( uint( gl_FragCoord.x ) << 16 ) | uint( gl_FragCoord.y );
7960
+ sobolPixelIndex = ( uint( gl_FragCoord.x ) << 16 ) | uint( gl_FragCoord.y );
7227
7961
  sobolPathIndex = uint( seed );
7228
7962
 
7229
7963
  // get camera ray
@@ -7239,7 +7973,6 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7239
7973
 
7240
7974
  // surface results
7241
7975
  SurfaceHit surfaceHit;
7242
- LightRecord lightRec;
7243
7976
  ScatterRecord scatterRec;
7244
7977
 
7245
7978
  // path tracing state
@@ -7248,7 +7981,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7248
7981
  #if FEATURE_FOG
7249
7982
 
7250
7983
  state.fogMaterial.fogVolume = bvhIntersectFogVolumeHit(
7251
- bvh, ray.origin, - ray.direction,
7984
+ ray.origin, - ray.direction,
7252
7985
  materialIndexAttribute, materials,
7253
7986
  state.fogMaterial
7254
7987
  );
@@ -7263,48 +7996,46 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7263
7996
  state.traversals = bounces - i;
7264
7997
  state.firstRay = i == 0 && state.transmissiveTraversals == transmissiveBounces;
7265
7998
 
7266
- int hitType = traceScene(
7267
- ray, bvh, lights, state.fogMaterial,
7268
- surfaceHit, lightRec
7269
- );
7999
+ int hitType = traceScene( ray, state.fogMaterial, surfaceHit );
7270
8000
 
7271
- if ( hitType == LIGHT_HIT ) {
8001
+ // check if we intersect any lights and accumulate the light contribution
8002
+ // TODO: we can add support for light surface rendering in the else condition if we
8003
+ // add the ability to toggle visibility of the the light
8004
+ if ( ! state.firstRay && ! state.transmissiveRay ) {
7272
8005
 
7273
- if ( state.firstRay || state.transmissiveRay ) {
8006
+ LightRecord lightRec;
8007
+ float lightDist = hitType == NO_HIT ? INFINITY : surfaceHit.dist;
8008
+ for ( uint i = 0u; i < lights.count; i ++ ) {
7274
8009
 
7275
- gl_FragColor.rgb += lightRec.emission * state.throughputColor;
8010
+ if (
8011
+ intersectLightAtIndex( lights.tex, ray.origin, ray.direction, i, lightRec ) &&
8012
+ lightRec.dist < lightDist
8013
+ ) {
7276
8014
 
7277
- } else {
7278
-
7279
- #if FEATURE_MIS
7280
-
7281
- // NOTE: we skip MIS for punctual lights since they are not supported in forward PT case
7282
- if ( lightRec.type == SPOT_LIGHT_TYPE || lightRec.type == DIR_LIGHT_TYPE || lightRec.type == POINT_LIGHT_TYPE ) {
7283
-
7284
- gl_FragColor.rgb += lightRec.emission * state.throughputColor;
7285
-
7286
- } else {
8015
+ #if FEATURE_MIS
7287
8016
 
7288
8017
  // weight the contribution
8018
+ // NOTE: Only area lights are supported for forward sampling and can be hit
7289
8019
  float misWeight = misHeuristic( scatterRec.pdf, lightRec.pdf / lightsDenom );
7290
8020
  gl_FragColor.rgb += lightRec.emission * state.throughputColor * misWeight;
7291
8021
 
7292
- }
8022
+ #else
7293
8023
 
7294
- #else
8024
+ gl_FragColor.rgb += lightRec.emission * state.throughputColor;
7295
8025
 
7296
- gl_FragColor.rgb += lightRec.emission * state.throughputColor;
8026
+ #endif
7297
8027
 
7298
- #endif
8028
+ }
7299
8029
 
7300
8030
  }
7301
- break;
7302
8031
 
7303
- } else if ( hitType == NO_HIT ) {
8032
+ }
8033
+
8034
+ if ( hitType == NO_HIT ) {
7304
8035
 
7305
8036
  if ( state.firstRay || state.transmissiveRay ) {
7306
8037
 
7307
- gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, sobol2( 2 ) ) * state.throughputColor;
8038
+ gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, rand2( 2 ) ) * state.throughputColor;
7308
8039
  gl_FragColor.a = backgroundAlpha;
7309
8040
 
7310
8041
  } else {
@@ -7313,7 +8044,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7313
8044
 
7314
8045
  // get the PDF of the hit envmap point
7315
8046
  vec3 envColor;
7316
- float envPdf = sampleEquirect( envMapInfo, envRotation3x3 * ray.direction, envColor );
8047
+ float envPdf = sampleEquirect( envRotation3x3 * ray.direction, envColor );
7317
8048
  envPdf /= lightsDenom;
7318
8049
 
7319
8050
  // and weight the contribution
@@ -7395,7 +8126,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7395
8126
  }
7396
8127
 
7397
8128
  scatterRec = bsdfSample( - ray.direction, surf );
7398
- state.isShadowRay = scatterRec.specularPdf < sobol( 4 );
8129
+ state.isShadowRay = scatterRec.specularPdf < rand( 4 );
7399
8130
 
7400
8131
  bool isBelowSurface = ! surf.volumeParticle && dot( scatterRec.direction, surf.faceNormal ) < 0.0;
7401
8132
  vec3 hitPoint = stepRayOrigin( ray.origin, ray.direction, isBelowSurface ? - surf.faceNormal : surf.faceNormal, surfaceHit.dist );
@@ -7465,7 +8196,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7465
8196
  rrProb = sqrt( rrProb );
7466
8197
  rrProb = max( rrProb, depthProb );
7467
8198
  rrProb = min( rrProb, 1.0 );
7468
- if ( sobol( 8 ) > rrProb ) {
8199
+ if ( rand( 8 ) > rrProb ) {
7469
8200
 
7470
8201
  break;
7471
8202
 
@@ -7870,5 +8601,5 @@ class CompatibilityDetector {
7870
8601
 
7871
8602
  // core
7872
8603
 
7873
- export { BlurredEnvMapGenerator, CompatibilityDetector, DenoiseMaterial, DynamicPathTracingSceneGenerator, EquirectCamera, EquirectHdrInfoUniform, FogVolumeMaterial, GradientEquirectTexture, GradientMapMaterial, GraphMaterial, IESLoader, IESProfilesTexture, LightsInfoUniformStruct, MaterialBase, MaterialReducer, MaterialsTexture, PathTracingRenderer, PathTracingSceneGenerator, PhysicalCamera, PhysicalCameraUniform, PhysicalPathTracingMaterial, PhysicalSpotLight, ProceduralEquirectTexture, QuiltPathTracingRenderer, RenderTarget2DArray, ShapedAreaLight, getGroupMaterialIndicesAttribute, mergeMeshes, setCommonAttributes, trimToAttributes };
8604
+ export { BlurredEnvMapGenerator, CompatibilityDetector, DenoiseMaterial, DynamicPathTracingSceneGenerator, EquirectCamera, EquirectHdrInfoUniform, FogVolumeMaterial, GradientEquirectTexture, GradientMapMaterial, GraphMaterial, IESLoader, IESProfilesTexture, LightsInfoUniformStruct, MaterialBase, MaterialReducer, MaterialsTexture, PathTracingRenderer, PathTracingSceneGenerator, PhysicalCamera, PhysicalCameraUniform, PhysicalPathTracingMaterial, PhysicalSpotLight, ProceduralEquirectTexture, QuiltPathTracingRenderer, RenderTarget2DArray, ShapedAreaLight, getDummyMesh, getGroupMaterialIndicesAttribute, setCommonAttributes };
7874
8605
  //# sourceMappingURL=index.module.js.map