three-gpu-pathtracer 0.0.17 → 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 (34) hide show
  1. package/build/index.module.js +1000 -319
  2. package/build/index.module.js.map +1 -1
  3. package/build/index.umd.cjs +996 -316
  4. package/build/index.umd.cjs.map +1 -1
  5. package/package.json +2 -2
  6. package/src/core/DynamicPathTracingSceneGenerator.js +80 -40
  7. package/src/core/PathTracingRenderer.js +28 -34
  8. package/src/core/PathTracingSceneGenerator.js +11 -60
  9. package/src/materials/pathtracing/LambertPathTracingMaterial.js +1 -1
  10. package/src/materials/pathtracing/PhysicalPathTracingMaterial.js +33 -11
  11. package/src/materials/pathtracing/glsl/attenuateHit.glsl.js +19 -9
  12. package/src/materials/pathtracing/glsl/cameraUtils.glsl.js +2 -2
  13. package/src/materials/pathtracing/glsl/directLightContribution.glsl.js +3 -3
  14. package/src/materials/pathtracing/glsl/getSurfaceRecord.glsl.js +2 -1
  15. package/src/materials/pathtracing/glsl/traceScene.glsl.js +1 -1
  16. package/src/shader/bsdf/bsdfSampling.glsl.js +14 -10
  17. package/src/shader/common/fresnel.glsl.js +15 -9
  18. package/src/shader/rand/pcg.glsl.js +4 -4
  19. package/src/shader/rand/stratifiedTexture.glsl.js +45 -0
  20. package/src/shader/structs/lightsStruct.glsl.js +5 -7
  21. package/src/textures/BlueNoiseTexture.js +87 -0
  22. package/src/textures/ProceduralEquirectTexture.js +7 -8
  23. package/src/textures/blueNoise/BlueNoiseGenerator.js +115 -0
  24. package/src/textures/blueNoise/BlueNoiseSamples.js +214 -0
  25. package/src/textures/blueNoise/utils.js +24 -0
  26. package/src/uniforms/EquirectHdrInfoUniform.js +45 -8
  27. package/src/uniforms/LightsInfoUniformStruct.js +11 -7
  28. package/src/uniforms/RenderTarget2DArray.js +50 -3
  29. package/src/uniforms/StratifiedSamplesTexture.js +49 -0
  30. package/src/uniforms/stratified/StratifiedSampler.js +73 -0
  31. package/src/uniforms/stratified/StratifiedSamplerCombined.js +59 -0
  32. package/src/uniforms/utils.js +1 -1
  33. package/src/utils/GeometryPreparationUtils.js +8 -101
  34. package/src/workers/PathTracingSceneWorker.js +18 -8
@@ -1,7 +1,7 @@
1
- import { ShaderMaterial, NoBlending, Vector2, WebGLRenderTarget, FloatType, RGBAFormat, NearestFilter, Vector4, NormalBlending, Color, HalfFloatType, Vector3, MathUtils, Matrix4, PerspectiveCamera, BufferAttribute, Mesh, BufferGeometry, Camera, SpotLight, RectAreaLight, Spherical, DataTexture, EquirectangularReflectionMapping, RepeatWrapping, ClampToEdgeWrapping, LinearFilter, DataUtils, DoubleSide, BackSide, FrontSide, WebGLArrayRenderTarget, UnsignedByteType, MeshBasicMaterial, NoToneMapping, Source, 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, BVHShaderGLSL } from 'three-mesh-bvh';
4
- import { mergeVertices, mergeGeometries } 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
 
@@ -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
 
@@ -1077,25 +1071,6 @@ function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
1077
1071
 
1078
1072
  }
1079
1073
 
1080
- function trimToAttributes( geometry, attributes ) {
1081
-
1082
- // trim any unneeded attributes
1083
- if ( attributes ) {
1084
-
1085
- for ( const key in geometry.attributes ) {
1086
-
1087
- if ( ! attributes.includes( key ) ) {
1088
-
1089
- geometry.deleteAttribute( key );
1090
-
1091
- }
1092
-
1093
- }
1094
-
1095
- }
1096
-
1097
- }
1098
-
1099
1074
  function setCommonAttributes( geometry, options ) {
1100
1075
 
1101
1076
  const { attributes = [], normalMapRequired = false } = options;
@@ -1113,6 +1088,13 @@ function setCommonAttributes( geometry, options ) {
1113
1088
 
1114
1089
  }
1115
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
+
1116
1098
  if ( ! geometry.attributes.tangent && ( attributes && attributes.includes( 'tangent' ) ) ) {
1117
1099
 
1118
1100
  if ( normalMapRequired ) {
@@ -1161,169 +1143,46 @@ function setCommonAttributes( geometry, options ) {
1161
1143
 
1162
1144
  }
1163
1145
 
1164
- function mergeMeshes( meshes, options = {} ) {
1165
-
1166
- options = { attributes: null, cloneGeometry: true, ...options };
1146
+ const dummyMaterial = new MeshBasicMaterial();
1147
+ function getDummyMesh() {
1167
1148
 
1168
- const transformedGeometry = [];
1169
- const materialSet = new Set();
1170
- for ( let i = 0, l = meshes.length; i < l; i ++ ) {
1149
+ const emptyGeometry = new BufferGeometry();
1150
+ emptyGeometry.setAttribute( 'position', new BufferAttribute( new Float32Array( 9 ), 3 ) );
1151
+ return new Mesh( emptyGeometry, dummyMaterial );
1171
1152
 
1172
- // save any materials
1173
- const mesh = meshes[ i ];
1174
- if ( mesh.visible === false ) continue;
1175
-
1176
- if ( Array.isArray( mesh.material ) ) {
1177
-
1178
- mesh.material.forEach( m => materialSet.add( m ) );
1179
-
1180
- } else {
1181
-
1182
- materialSet.add( mesh.material );
1183
-
1184
- }
1185
-
1186
- }
1187
-
1188
- const materials = Array.from( materialSet );
1189
- for ( let i = 0, l = meshes.length; i < l; i ++ ) {
1190
-
1191
- // ensure the matrix world is up to date
1192
- const mesh = meshes[ i ];
1193
- if ( mesh.visible === false ) continue;
1194
-
1195
- mesh.updateMatrixWorld();
1196
-
1197
- // apply the matrix world to the geometry
1198
- const originalGeometry = meshes[ i ].geometry;
1199
- const geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
1200
- geometry.applyMatrix4( mesh.matrixWorld );
1201
-
1202
- if ( mesh.matrixWorld.determinant() < 0 ) {
1203
-
1204
- geometry.index.array.reverse();
1205
-
1206
- }
1153
+ }
1207
1154
 
1208
- // ensure our geometry has common attributes
1209
- setCommonAttributes( geometry, {
1210
- attributes: options.attributes,
1211
- normalMapRequired: ! ! mesh.material.normalMap,
1212
- } );
1213
- trimToAttributes( geometry, options.attributes );
1155
+ class DynamicPathTracingSceneGenerator {
1214
1156
 
1215
- // create the material index attribute
1216
- const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
1217
- geometry.setAttribute( 'materialIndex', materialIndexAttribute );
1157
+ get initialized() {
1218
1158
 
1219
- transformedGeometry.push( geometry );
1159
+ return Boolean( this.bvh );
1220
1160
 
1221
1161
  }
1222
1162
 
1223
- const textureSet = new Set();
1224
- materials.forEach( material => {
1225
-
1226
- for ( const key in material ) {
1163
+ constructor( objects ) {
1227
1164
 
1228
- const value = material[ key ];
1229
- if ( value && value.isTexture ) {
1165
+ // ensure the objects is an array
1166
+ if ( ! Array.isArray( objects ) ) {
1230
1167
 
1231
- textureSet.add( value );
1232
-
1233
- }
1168
+ objects = [ objects ];
1234
1169
 
1235
1170
  }
1236
1171
 
1237
- } );
1238
-
1239
- const geometry = mergeGeometries( transformedGeometry, false );
1240
- const textures = Array.from( textureSet );
1241
- return { geometry, materials, textures };
1242
-
1243
- }
1244
-
1245
- class PathTracingSceneGenerator {
1246
-
1247
- prepScene( scene ) {
1172
+ // use a dummy object for a fallback
1173
+ const finalObjects = [ ...objects ];
1174
+ if ( finalObjects.length === 0 ) {
1248
1175
 
1249
- scene = Array.isArray( scene ) ? scene : [ scene ];
1250
-
1251
- const meshes = [];
1252
- const lights = [];
1253
-
1254
- for ( let i = 0, l = scene.length; i < l; i ++ ) {
1255
-
1256
- scene[ i ].traverseVisible( c => {
1257
-
1258
- if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
1259
-
1260
- const generator = new StaticGeometryGenerator( c );
1261
- generator.attributes = [ 'position', 'color', 'normal', 'tangent', 'uv', 'uv2' ];
1262
- generator.applyWorldTransforms = false;
1263
- const mesh = new Mesh(
1264
- generator.generate(),
1265
- c.material,
1266
- );
1267
- mesh.matrixWorld.copy( c.matrixWorld );
1268
- mesh.matrix.copy( c.matrixWorld );
1269
- mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
1270
- meshes.push( mesh );
1271
-
1272
- } else if ( c.isMesh ) {
1273
-
1274
- meshes.push( c );
1275
-
1276
- } else if (
1277
- c.isRectAreaLight ||
1278
- c.isSpotLight ||
1279
- c.isPointLight ||
1280
- c.isDirectionalLight
1281
- ) {
1282
-
1283
- lights.push( c );
1284
-
1285
- }
1286
-
1287
- } );
1176
+ finalObjects.push( getDummyMesh() );
1288
1177
 
1289
1178
  }
1290
1179
 
1291
- return {
1292
- ...mergeMeshes( meshes, {
1293
- attributes: [ 'position', 'normal', 'tangent', 'uv', 'color' ],
1294
- } ),
1295
- lights,
1296
- };
1297
-
1298
- }
1299
-
1300
- generate( scene, options = {} ) {
1301
-
1302
- const { materials, textures, geometry, lights } = this.prepScene( scene );
1303
- const bvhOptions = { strategy: SAH, ...options, maxLeafTris: 1 };
1304
- return {
1305
- scene,
1306
- materials,
1307
- textures,
1308
- lights,
1309
- bvh: new MeshBVH( geometry, bvhOptions ),
1310
- };
1311
-
1312
- }
1313
-
1314
- }
1315
-
1316
- class DynamicPathTracingSceneGenerator {
1317
-
1318
- get initialized() {
1319
-
1320
- return Boolean( this.bvh );
1321
-
1322
- }
1323
-
1324
- constructor( scene ) {
1180
+ // options
1181
+ this.bvhOptions = {};
1182
+ this.attributes = [ 'position', 'normal', 'tangent', 'color', 'uv', 'uv2' ];
1325
1183
 
1326
- this.objects = Array.isArray( scene ) ? scene : [ scene ];
1184
+ // state
1185
+ this.objects = finalObjects;
1327
1186
  this.bvh = null;
1328
1187
  this.geometry = new BufferGeometry();
1329
1188
  this.materials = null;
@@ -1347,64 +1206,75 @@ class DynamicPathTracingSceneGenerator {
1347
1206
 
1348
1207
  dispose() {}
1349
1208
 
1350
- generate() {
1209
+ prepScene() {
1351
1210
 
1352
- const { objects, staticGeometryGenerator, geometry, lights } = this;
1353
- if ( this.bvh === null ) {
1211
+ if ( this.bvh !== null ) {
1354
1212
 
1355
- const attributes = [ 'position', 'normal', 'tangent', 'uv', 'color' ];
1213
+ return;
1356
1214
 
1357
- for ( let i = 0, l = objects.length; i < l; i ++ ) {
1215
+ }
1358
1216
 
1359
- objects[ i ].traverse( c => {
1217
+ const { objects, staticGeometryGenerator, geometry, lights, attributes } = this;
1218
+ for ( let i = 0, l = objects.length; i < l; i ++ ) {
1360
1219
 
1361
- if ( c.isMesh ) {
1220
+ objects[ i ].traverse( c => {
1362
1221
 
1363
- const normalMapRequired = ! ! c.material.normalMap;
1364
- setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
1222
+ if ( c.isMesh ) {
1365
1223
 
1366
- } else if (
1367
- c.isRectAreaLight ||
1368
- c.isSpotLight ||
1369
- c.isPointLight ||
1370
- c.isDirectionalLight
1371
- ) {
1224
+ const normalMapRequired = ! ! c.material.normalMap;
1225
+ setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
1372
1226
 
1373
- lights.push( c );
1227
+ } else if (
1228
+ c.isRectAreaLight ||
1229
+ c.isSpotLight ||
1230
+ c.isPointLight ||
1231
+ c.isDirectionalLight
1232
+ ) {
1374
1233
 
1375
- }
1234
+ lights.push( c );
1376
1235
 
1377
- } );
1236
+ }
1378
1237
 
1379
- }
1238
+ } );
1380
1239
 
1381
- const textureSet = new Set();
1382
- const materials = staticGeometryGenerator.getMaterials();
1383
- materials.forEach( material => {
1240
+ }
1384
1241
 
1385
- for ( const key in material ) {
1242
+ const textureSet = new Set();
1243
+ const materials = staticGeometryGenerator.getMaterials();
1244
+ materials.forEach( material => {
1386
1245
 
1387
- const value = material[ key ];
1388
- if ( value && value.isTexture ) {
1246
+ for ( const key in material ) {
1389
1247
 
1390
- textureSet.add( value );
1248
+ const value = material[ key ];
1249
+ if ( value && value.isTexture ) {
1391
1250
 
1392
- }
1251
+ textureSet.add( value );
1393
1252
 
1394
1253
  }
1395
1254
 
1396
- } );
1255
+ }
1397
1256
 
1398
- staticGeometryGenerator.attributes = attributes;
1399
- 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();
1265
+
1266
+ this.materials = materials;
1267
+ this.textures = Array.from( textureSet );
1268
+
1269
+ }
1270
+
1271
+ generate() {
1400
1272
 
1401
- const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, materials, materials );
1402
- geometry.setAttribute( 'materialIndex', materialIndexAttribute );
1403
- geometry.clearGroups();
1273
+ const { objects, staticGeometryGenerator, geometry, bvhOptions } = this;
1274
+ if ( this.bvh === null ) {
1404
1275
 
1405
- this.bvh = new MeshBVH( geometry );
1406
- this.materials = materials;
1407
- this.textures = Array.from( textureSet );
1276
+ this.prepScene();
1277
+ this.bvh = new MeshBVH( geometry, { strategy: SAH, maxLeafTris: 1, ...bvhOptions } );
1408
1278
 
1409
1279
  return {
1410
1280
  lights: this.lights,
@@ -1434,6 +1304,30 @@ class DynamicPathTracingSceneGenerator {
1434
1304
 
1435
1305
  }
1436
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
+
1437
1331
  // https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
1438
1332
 
1439
1333
  function isTypedArray( arr ) {
@@ -1798,8 +1692,8 @@ class ProceduralEquirectTexture extends DataTexture {
1798
1692
  constructor( width = 512, height = 512 ) {
1799
1693
 
1800
1694
  super(
1801
- new Uint16Array( width * height * 4 ),
1802
- width, height, RGBAFormat, HalfFloatType, EquirectangularReflectionMapping,
1695
+ new Float32Array( width * height * 4 ),
1696
+ width, height, RGBAFormat, FloatType, EquirectangularReflectionMapping,
1803
1697
  RepeatWrapping, ClampToEdgeWrapping, LinearFilter, LinearFilter,
1804
1698
  );
1805
1699
 
@@ -1831,10 +1725,10 @@ class ProceduralEquirectTexture extends DataTexture {
1831
1725
 
1832
1726
  const i = y * width + x;
1833
1727
  const i4 = 4 * i;
1834
- data[ i4 + 0 ] = DataUtils.toHalfFloat( _color.r );
1835
- data[ i4 + 1 ] = DataUtils.toHalfFloat( _color.g );
1836
- data[ i4 + 2 ] = DataUtils.toHalfFloat( _color.b );
1837
- data[ i4 + 3 ] = DataUtils.toHalfFloat( 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 );
1838
1732
 
1839
1733
  }
1840
1734
 
@@ -1889,7 +1783,7 @@ class GradientEquirectTexture extends ProceduralEquirectTexture {
1889
1783
  // when rendering each texture to the texture array they must have a consistent color space.
1890
1784
  function getTextureHash( t ) {
1891
1785
 
1892
- return `${ t.source.uuid }:${ t.encoding }`;
1786
+ return `${ t.source.uuid }:${ t.colorSpace }`;
1893
1787
 
1894
1788
  }
1895
1789
 
@@ -2439,7 +2333,7 @@ class RenderTarget2DArray extends WebGLArrayRenderTarget {
2439
2333
 
2440
2334
  };
2441
2335
 
2442
- const fsQuad = new FullScreenQuad( new MeshBasicMaterial() );
2336
+ const fsQuad = new FullScreenQuad( new CopyMaterial() );
2443
2337
  this.fsQuad = fsQuad;
2444
2338
 
2445
2339
  }
@@ -2474,7 +2368,6 @@ class RenderTarget2DArray extends WebGLArrayRenderTarget {
2474
2368
  texture.matrix.identity();
2475
2369
 
2476
2370
  fsQuad.material.map = texture;
2477
- fsQuad.material.transparent = true;
2478
2371
 
2479
2372
  renderer.setRenderTarget( this, i );
2480
2373
  fsQuad.render( renderer );
@@ -2504,6 +2397,54 @@ class RenderTarget2DArray extends WebGLArrayRenderTarget {
2504
2397
 
2505
2398
  }
2506
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
+
2507
2448
  function toHalfFloatArray( f32Array ) {
2508
2449
 
2509
2450
  const f16Array = new Uint16Array( f32Array.length );
@@ -2555,7 +2496,7 @@ function colorToLuminance( r, g, b ) {
2555
2496
  }
2556
2497
 
2557
2498
  // ensures the data is all floating point values and flipY is false
2558
- function preprocessEnvMap( envMap ) {
2499
+ function preprocessEnvMap( envMap, targetType = HalfFloatType ) {
2559
2500
 
2560
2501
  const map = envMap.clone();
2561
2502
  map.source = new Source( { ...map.image } );
@@ -2564,17 +2505,54 @@ function preprocessEnvMap( envMap ) {
2564
2505
  // TODO: is there a simple way to avoid cloning and adjusting the env map data here?
2565
2506
  // convert the data from half float uint 16 arrays to float arrays for cdf computation
2566
2507
  let newData = data;
2567
- 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
+ }
2530
+
2531
+ for ( let i = 0, l = data.length; i < l; i ++ ) {
2532
+
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 ) {
2568
2547
 
2569
- newData = new Uint16Array( data.length );
2570
- for ( const i in data ) {
2548
+ newData[ i ] = DataUtils.toHalfFloat( v );
2571
2549
 
2572
- newData[ i ] = data[ i ];
2550
+ }
2573
2551
 
2574
2552
  }
2575
2553
 
2576
2554
  map.image.data = newData;
2577
- map.type = HalfFloatType;
2555
+ map.type = targetType;
2578
2556
 
2579
2557
  }
2580
2558
 
@@ -2666,7 +2644,7 @@ class EquirectHdrInfoUniform {
2666
2644
  // https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
2667
2645
  const map = preprocessEnvMap( hdr );
2668
2646
  map.wrapS = RepeatWrapping;
2669
- map.wrapT = RepeatWrapping;
2647
+ map.wrapT = ClampToEdgeWrapping;
2670
2648
 
2671
2649
  const { width, height, data } = map.image;
2672
2650
 
@@ -2878,6 +2856,13 @@ class LightsInfoUniformStruct {
2878
2856
  const baseIndex = i * LIGHT_PIXELS * 4;
2879
2857
  let index = 0;
2880
2858
 
2859
+ // initialize to 0
2860
+ for ( let p = 0; p < LIGHT_PIXELS; p ++ ) {
2861
+
2862
+ floatArray[ baseIndex + p ] = 0;
2863
+
2864
+ }
2865
+
2881
2866
  // sample 1
2882
2867
  // position
2883
2868
  l.getWorldPosition( v );
@@ -2942,7 +2927,7 @@ class LightsInfoUniformStruct {
2942
2927
 
2943
2928
  } else if ( l.isSpotLight ) {
2944
2929
 
2945
- const radius = l.radius;
2930
+ const radius = l.radius || 0;
2946
2931
  eye.setFromMatrixPosition( l.matrixWorld );
2947
2932
  target.setFromMatrixPosition( l.target.matrixWorld );
2948
2933
  m.lookAt( eye, target, up );
@@ -2972,24 +2957,21 @@ class LightsInfoUniformStruct {
2972
2957
  // radius
2973
2958
  floatArray[ baseIndex + ( index ++ ) ] = radius;
2974
2959
 
2975
- // near
2976
- floatArray[ baseIndex + ( index ++ ) ] = l.shadow.camera.near;
2977
-
2978
2960
  // decay
2979
2961
  floatArray[ baseIndex + ( index ++ ) ] = l.decay;
2980
2962
 
2981
2963
  // distance
2982
2964
  floatArray[ baseIndex + ( index ++ ) ] = l.distance;
2983
2965
 
2984
- // sample 6
2985
2966
  // coneCos
2986
2967
  floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle );
2987
2968
 
2969
+ // sample 6
2988
2970
  // penumbraCos
2989
2971
  floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle * ( 1 - l.penumbra ) );
2990
2972
 
2991
2973
  // iesProfile
2992
- floatArray[ baseIndex + ( index ++ ) ] = iesTextures.indexOf( l.iesTexture );
2974
+ floatArray[ baseIndex + ( index ++ ) ] = l.iesTexture ? iesTextures.indexOf( l.iesTexture ) : - 1;
2993
2975
 
2994
2976
  } else if ( l.isPointLight ) {
2995
2977
 
@@ -3006,7 +2988,7 @@ class LightsInfoUniformStruct {
3006
2988
  index += 4;
3007
2989
 
3008
2990
  // sample 5
3009
- index += 2;
2991
+ index += 1;
3010
2992
 
3011
2993
  floatArray[ baseIndex + ( index ++ ) ] = l.decay;
3012
2994
  floatArray[ baseIndex + ( index ++ ) ] = l.distance;
@@ -4420,18 +4402,16 @@ const lightsStructGLSL = /* glsl */`
4420
4402
  vec4 s4 = texelFetch1D( tex, i + 4u );
4421
4403
  vec4 s5 = texelFetch1D( tex, i + 5u );
4422
4404
  l.radius = s4.r;
4423
- l.near = s4.g;
4424
- l.decay = s4.b;
4425
- l.distance = s4.a;
4405
+ l.decay = s4.g;
4406
+ l.distance = s4.b;
4407
+ l.coneCos = s4.a;
4426
4408
 
4427
- l.coneCos = s5.r;
4428
- l.penumbraCos = s5.g;
4429
- l.iesProfile = int( round( s5.b ) );
4409
+ l.penumbraCos = s5.r;
4410
+ l.iesProfile = int( round( s5.g ) );
4430
4411
 
4431
4412
  } else {
4432
4413
 
4433
4414
  l.radius = 0.0;
4434
- l.near = 0.0;
4435
4415
  l.decay = 0.0;
4436
4416
  l.distance = 0.0;
4437
4417
 
@@ -5149,15 +5129,17 @@ const bsdfSamplingGLSL = /* glsl */`
5149
5129
 
5150
5130
  // TODO: subsurface approx?
5151
5131
 
5152
- 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 );
5153
5134
  color = ( 1.0 - F ) * transFactor * metalFactor * wi.z * surf.color * ( retro + lambert ) / PI;
5135
+
5154
5136
  return wi.z / PI;
5155
5137
 
5156
5138
  }
5157
5139
 
5158
5140
  vec3 diffuseDirection( vec3 wo, SurfaceRecord surf ) {
5159
5141
 
5160
- vec3 lightDirection = sampleSphere( sobol2( 11 ) );
5142
+ vec3 lightDirection = sampleSphere( rand2( 11 ) );
5161
5143
  lightDirection.z += 1.0;
5162
5144
  lightDirection = normalize( lightDirection );
5163
5145
 
@@ -5202,7 +5184,7 @@ const bsdfSamplingGLSL = /* glsl */`
5202
5184
  vec3 halfVector = ggxDirection(
5203
5185
  wo,
5204
5186
  vec2( roughness ),
5205
- sobol2( 12 )
5187
+ rand2( 12 )
5206
5188
  );
5207
5189
 
5208
5190
  // apply to new ray by reflecting off the new normal
@@ -5239,7 +5221,7 @@ const bsdfSamplingGLSL = /* glsl */`
5239
5221
  vec3 halfVector = ggxDirection(
5240
5222
  wo,
5241
5223
  vec2( filteredRoughness ),
5242
- sobol2( 13 )
5224
+ rand2( 13 )
5243
5225
  );
5244
5226
 
5245
5227
  vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
@@ -5261,7 +5243,8 @@ const bsdfSamplingGLSL = /* glsl */`
5261
5243
  color = surf.transmission * surf.color;
5262
5244
 
5263
5245
  // PDF
5264
- 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 );
5265
5248
  if ( F >= 1.0 ) {
5266
5249
 
5267
5250
  return 0.0;
@@ -5276,7 +5259,7 @@ const bsdfSamplingGLSL = /* glsl */`
5276
5259
 
5277
5260
  float roughness = surf.filteredRoughness;
5278
5261
  float eta = surf.eta;
5279
- 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 );
5280
5263
  vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
5281
5264
 
5282
5265
  if ( surf.thinFilm ) {
@@ -5317,7 +5300,7 @@ const bsdfSamplingGLSL = /* glsl */`
5317
5300
  vec3 halfVector = ggxDirection(
5318
5301
  wo,
5319
5302
  vec2( roughness ),
5320
- sobol2( 14 )
5303
+ rand2( 14 )
5321
5304
  );
5322
5305
 
5323
5306
  // apply to new ray by reflecting off the new normal
@@ -5352,7 +5335,8 @@ const bsdfSamplingGLSL = /* glsl */`
5352
5335
 
5353
5336
  float metalness = surf.metalness;
5354
5337
  float transmission = surf.transmission;
5355
- 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 );
5356
5340
 
5357
5341
  float transSpecularProb = mix( max( 0.25, fEstimate ), 1.0, metalness );
5358
5342
  float diffSpecularProb = 0.5 + 0.5 * metalness;
@@ -5468,7 +5452,7 @@ const bsdfSamplingGLSL = /* glsl */`
5468
5452
  ScatterRecord sampleRec;
5469
5453
  sampleRec.specularPdf = 0.0;
5470
5454
  sampleRec.pdf = 1.0 / ( 4.0 * PI );
5471
- sampleRec.direction = sampleSphere( sobol2( 16 ) );
5455
+ sampleRec.direction = sampleSphere( rand2( 16 ) );
5472
5456
  sampleRec.color = surf.color / ( 4.0 * PI );
5473
5457
  return sampleRec;
5474
5458
 
@@ -5520,7 +5504,7 @@ const bsdfSamplingGLSL = /* glsl */`
5520
5504
  vec3 wi;
5521
5505
  vec3 clearcoatWi;
5522
5506
 
5523
- float r = sobol( 15 );
5507
+ float r = rand( 15 );
5524
5508
  if ( r <= cdf[0] ) { // diffuse
5525
5509
 
5526
5510
  wi = diffuseDirection( wo, surf );
@@ -6159,32 +6143,38 @@ const fresnelGLSL = /* glsl */`
6159
6143
 
6160
6144
  }
6161
6145
 
6162
- 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 ) {
6163
6149
 
6164
- if ( totalInternalReflection( cosTheta, eta ) ) {
6150
+ // if ( totalInternalReflection( cosTheta, eta ) ) {
6165
6151
 
6166
- return 1.0;
6152
+ // return 1.0;
6167
6153
 
6168
- }
6154
+ // }
6169
6155
 
6170
- return schlickFresnel( cosTheta, f0 );
6156
+ // return schlickFresnel( cosTheta, f0 );
6171
6157
 
6172
- }
6158
+ // }
6173
6159
 
6174
- /*
6175
6160
  // https://schuttejoe.github.io/post/disneybsdf/
6176
6161
  float disneyFresnel( vec3 wo, vec3 wi, vec3 wh, float f0, float eta, float metalness ) {
6177
6162
 
6178
6163
  float dotHV = dot( wo, wh );
6179
- float dotHL = dot( wi, wh );
6164
+ if ( totalInternalReflection( dotHV, eta ) ) {
6165
+
6166
+ return 1.0;
6167
+
6168
+ }
6180
6169
 
6170
+ float dotHL = dot( wi, wh );
6181
6171
  float dielectricFresnel = dielectricFresnel( abs( dotHV ), eta );
6182
6172
  float metallicFresnel = schlickFresnel( dotHL, f0 );
6183
6173
 
6184
6174
  return mix( dielectricFresnel, metallicFresnel, metalness );
6185
6175
 
6186
6176
  }
6187
- */
6177
+
6188
6178
  `;
6189
6179
 
6190
6180
  const arraySamplerTexelFetchGLSL = /*glsl */`
@@ -6241,28 +6231,28 @@ const pcgGLSL = /* glsl */`
6241
6231
  }
6242
6232
 
6243
6233
  // returns [ 0, 1 ]
6244
- float rand() {
6234
+ float pcgRand() {
6245
6235
 
6246
6236
  pcg4d( WHITE_NOISE_SEED );
6247
6237
  return float( WHITE_NOISE_SEED.x ) / float( 0xffffffffu );
6248
6238
 
6249
6239
  }
6250
6240
 
6251
- vec2 rand2() {
6241
+ vec2 pcgRand2() {
6252
6242
 
6253
6243
  pcg4d( WHITE_NOISE_SEED );
6254
6244
  return vec2( WHITE_NOISE_SEED.xy ) / float(0xffffffffu);
6255
6245
 
6256
6246
  }
6257
6247
 
6258
- vec3 rand3() {
6248
+ vec3 pcgRand3() {
6259
6249
 
6260
6250
  pcg4d( WHITE_NOISE_SEED );
6261
6251
  return vec3( WHITE_NOISE_SEED.xyz ) / float( 0xffffffffu );
6262
6252
 
6263
6253
  }
6264
6254
 
6265
- vec4 rand4() {
6255
+ vec4 pcgRand4() {
6266
6256
 
6267
6257
  pcg4d( WHITE_NOISE_SEED );
6268
6258
  return vec4( WHITE_NOISE_SEED ) / float( 0xffffffffu );
@@ -6335,7 +6325,7 @@ const cameraUtilsGLSL = /* glsl */`
6335
6325
 
6336
6326
  // Jitter the camera ray by finding a uv coordinate at a random sample
6337
6327
  // around this pixel's UV coordinate for AA
6338
- vec2 ruv = sobol2( 0 );
6328
+ vec2 ruv = rand2( 0 );
6339
6329
  vec2 jitteredUv = vUv + vec2( tentFilter( ruv.x ) * ssd.x, tentFilter( ruv.y ) * ssd.y );
6340
6330
  Ray ray;
6341
6331
 
@@ -6380,7 +6370,7 @@ const cameraUtilsGLSL = /* glsl */`
6380
6370
 
6381
6371
  // get the aperture sample
6382
6372
  // if blades === 0 then we assume a circle
6383
- vec3 shapeUVW= sobol3( 1 );
6373
+ vec3 shapeUVW= rand3( 1 );
6384
6374
  int blades = physicalCamera.apertureBlades;
6385
6375
  float anamorphicRatio = physicalCamera.anamorphicRatio;
6386
6376
  vec2 apertureSample = blades == 0 ? sampleCircle( shapeUVW.xy ) : sampleRegularPolygon( blades, shapeUVW );
@@ -6416,6 +6406,9 @@ const attenuateHitGLSL = /* glsl */`
6416
6406
  out vec3 color
6417
6407
  ) {
6418
6408
 
6409
+ // store the original bounce index so we can reset it after
6410
+ uint originalBounceIndex = sobolBounceIndex;
6411
+
6419
6412
  int traversals = state.traversals;
6420
6413
  int transmissiveTraversals = state.transmissiveTraversals;
6421
6414
  bool isShadowRay = state.isShadowRay;
@@ -6428,22 +6421,25 @@ const attenuateHitGLSL = /* glsl */`
6428
6421
 
6429
6422
  color = vec3( 1.0 );
6430
6423
 
6431
- // TODO: we should be using sobol sampling here instead of rand but the sobol bounce and path indices need to be incremented
6432
- // and then reset.
6424
+ bool result = true;
6433
6425
  for ( int i = 0; i < traversals; i ++ ) {
6434
6426
 
6427
+ sobolBounceIndex ++;
6428
+
6435
6429
  int hitType = traceScene( ray, fogMaterial, surfaceHit );
6436
6430
 
6437
6431
  if ( hitType == FOG_HIT ) {
6438
6432
 
6439
- return true;
6433
+ result = true;
6434
+ break;
6440
6435
 
6441
6436
  } else if ( hitType == SURFACE_HIT ) {
6442
6437
 
6443
6438
  float totalDist = distance( startPoint, ray.origin + ray.direction * surfaceHit.dist );
6444
6439
  if ( totalDist > rayDist ) {
6445
6440
 
6446
- return false;
6441
+ result = false;
6442
+ break;
6447
6443
 
6448
6444
  }
6449
6445
 
@@ -6525,7 +6521,7 @@ const attenuateHitGLSL = /* glsl */`
6525
6521
  bool useAlphaTest = alphaTest != 0.0;
6526
6522
  float transmissionFactor = ( 1.0 - metalness ) * transmission;
6527
6523
  if (
6528
- transmissionFactor < rand() && ! (
6524
+ transmissionFactor < rand( 9 ) && ! (
6529
6525
  // material sidedness
6530
6526
  material.side != 0.0 && surfaceHit.side == material.side
6531
6527
 
@@ -6533,11 +6529,12 @@ const attenuateHitGLSL = /* glsl */`
6533
6529
  || useAlphaTest && albedo.a < alphaTest
6534
6530
 
6535
6531
  // opacity
6536
- || material.transparent && ! useAlphaTest && albedo.a < rand()
6532
+ || material.transparent && ! useAlphaTest && albedo.a < rand( 10 )
6537
6533
  )
6538
6534
  ) {
6539
6535
 
6540
- return true;
6536
+ result = true;
6537
+ break;
6541
6538
 
6542
6539
  }
6543
6540
 
@@ -6563,13 +6560,16 @@ const attenuateHitGLSL = /* glsl */`
6563
6560
 
6564
6561
  } else {
6565
6562
 
6566
- return false;
6563
+ result = false;
6564
+ break;
6567
6565
 
6568
6566
  }
6569
6567
 
6570
6568
  }
6571
6569
 
6572
- return true;
6570
+ // reset the bounce index
6571
+ sobolBounceIndex = originalBounceIndex;
6572
+ return result;
6573
6573
 
6574
6574
  }
6575
6575
 
@@ -6600,7 +6600,7 @@ const traceSceneGLSL = /* glsl */`
6600
6600
 
6601
6601
  // offset the distance so we don't run into issues with particles on the same surface
6602
6602
  // as other objects
6603
- float particleDist = intersectFogVolume( fogMaterial, sobol( 1 ) );
6603
+ float particleDist = intersectFogVolume( fogMaterial, rand( 1 ) );
6604
6604
  if ( particleDist + RAY_OFFSET < surfaceHit.dist ) {
6605
6605
 
6606
6606
  surfaceHit.side = 1.0;
@@ -6663,6 +6663,7 @@ const getSurfaceRecordGLSL = /* glsl */`
6663
6663
 
6664
6664
  vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
6665
6665
  albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
6666
+
6666
6667
  }
6667
6668
 
6668
6669
  if ( material.vertexColors ) {
@@ -6694,7 +6695,7 @@ const getSurfaceRecordGLSL = /* glsl */`
6694
6695
  || useAlphaTest && albedo.a < alphaTest
6695
6696
 
6696
6697
  // opacity
6697
- || material.transparent && ! useAlphaTest && albedo.a < sobol( 3 )
6698
+ || material.transparent && ! useAlphaTest && albedo.a < rand( 3 )
6698
6699
  ) {
6699
6700
 
6700
6701
  return SKIP_SURFACE;
@@ -6956,10 +6957,10 @@ const directLightContributionGLSL = /*glsl*/`
6956
6957
  vec3 result = vec3( 0.0 );
6957
6958
 
6958
6959
  // uniformly pick a light or environment map
6959
- if( lightsDenom != 0.0 && sobol( 5 ) < float( lights.count ) / lightsDenom ) {
6960
+ if( lightsDenom != 0.0 && rand( 5 ) < float( lights.count ) / lightsDenom ) {
6960
6961
 
6961
6962
  // sample a light or environment
6962
- LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, sobol3( 6 ) );
6963
+ LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, rand3( 6 ) );
6963
6964
 
6964
6965
  bool isSampleBelowSurface = ! surf.volumeParticle && dot( surf.faceNormal, lightRec.direction ) < 0.0;
6965
6966
  if ( isSampleBelowSurface ) {
@@ -6998,7 +6999,7 @@ const directLightContributionGLSL = /*glsl*/`
6998
6999
 
6999
7000
  // find a sample in the environment map to include in the contribution
7000
7001
  vec3 envColor, envDirection;
7001
- float envPdf = sampleEquirectProbability( sobol2( 7 ), envColor, envDirection );
7002
+ float envPdf = sampleEquirectProbability( rand2( 7 ), envColor, envDirection );
7002
7003
  envDirection = invEnvRotation3x3 * envDirection;
7003
7004
 
7004
7005
  // this env sampling is not set up for transmissive sampling and yields overly bright
@@ -7047,7 +7048,668 @@ const directLightContributionGLSL = /*glsl*/`
7047
7048
 
7048
7049
  `;
7049
7050
 
7050
- class PhysicalPathTracingMaterial extends MaterialBase {
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 );
7092
+
7093
+ }
7094
+
7095
+ `;
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
+
7712
+ class PhysicalPathTracingMaterial extends MaterialBase {
7051
7713
 
7052
7714
  onBeforeRender() {
7053
7715
 
@@ -7070,7 +7732,12 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7070
7732
  FEATURE_DOF: 1,
7071
7733
  FEATURE_BACKGROUND_MAP: 0,
7072
7734
  FEATURE_FOG: 1,
7073
- FEATURE_SOBOL: 0,
7735
+
7736
+ // 0 = PCG
7737
+ // 1 = Sobol
7738
+ // 2 = Stratified List
7739
+ RANDOM_TYPE: 2,
7740
+
7074
7741
  // 0 = Perspective
7075
7742
  // 1 = Orthographic
7076
7743
  // 2 = Equirectangular
@@ -7112,6 +7779,8 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7112
7779
 
7113
7780
  backgroundAlpha: { value: 1.0 },
7114
7781
  sobolTexture: { value: null },
7782
+ stratifiedTexture: { value: new StratifiedSamplesTexture() },
7783
+ stratifiedOffsetTexture: { value: new BlueNoiseTexture( 64, 1 ) },
7115
7784
  },
7116
7785
 
7117
7786
  vertexShader: /* glsl */`
@@ -7151,23 +7820,35 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7151
7820
  ${ materialStructGLSL }
7152
7821
 
7153
7822
  // random
7154
- ${ pcgGLSL }
7155
- #if FEATURE_SOBOL
7823
+ #if RANDOM_TYPE == 2 // Stratified List
7156
7824
 
7825
+ ${ stratifiedTextureGLSL }
7826
+
7827
+ #elif RANDOM_TYPE == 1 // Sobol
7828
+
7829
+ ${ pcgGLSL }
7157
7830
  ${ sobolCommonGLSL }
7158
7831
  ${ sobolSamplingGLSL }
7159
7832
 
7160
- #else
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 }
7161
7841
 
7162
7842
  // Using the sobol functions seems to break the the compiler on MacOS
7163
7843
  // - specifically the "sobolReverseBits" function.
7164
7844
  uint sobolPixelIndex = 0u;
7165
7845
  uint sobolPathIndex = 0u;
7166
7846
  uint sobolBounceIndex = 0u;
7167
- float sobol( int v ) { return rand(); }
7168
- vec2 sobol2( int v ) { return rand2(); }
7169
- vec3 sobol3( int v ) { return rand3(); }
7170
- vec4 sobol4( int v ) { return rand4(); }
7847
+
7848
+ #define rand(v) pcgRand()
7849
+ #define rand2(v) pcgRand2()
7850
+ #define rand3(v) pcgRand3()
7851
+ #define rand4(v) pcgRand4()
7171
7852
 
7172
7853
  #endif
7173
7854
 
@@ -7354,7 +8035,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7354
8035
 
7355
8036
  if ( state.firstRay || state.transmissiveRay ) {
7356
8037
 
7357
- gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, sobol2( 2 ) ) * state.throughputColor;
8038
+ gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, rand2( 2 ) ) * state.throughputColor;
7358
8039
  gl_FragColor.a = backgroundAlpha;
7359
8040
 
7360
8041
  } else {
@@ -7445,7 +8126,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7445
8126
  }
7446
8127
 
7447
8128
  scatterRec = bsdfSample( - ray.direction, surf );
7448
- state.isShadowRay = scatterRec.specularPdf < sobol( 4 );
8129
+ state.isShadowRay = scatterRec.specularPdf < rand( 4 );
7449
8130
 
7450
8131
  bool isBelowSurface = ! surf.volumeParticle && dot( scatterRec.direction, surf.faceNormal ) < 0.0;
7451
8132
  vec3 hitPoint = stepRayOrigin( ray.origin, ray.direction, isBelowSurface ? - surf.faceNormal : surf.faceNormal, surfaceHit.dist );
@@ -7515,7 +8196,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7515
8196
  rrProb = sqrt( rrProb );
7516
8197
  rrProb = max( rrProb, depthProb );
7517
8198
  rrProb = min( rrProb, 1.0 );
7518
- if ( sobol( 8 ) > rrProb ) {
8199
+ if ( rand( 8 ) > rrProb ) {
7519
8200
 
7520
8201
  break;
7521
8202
 
@@ -7920,5 +8601,5 @@ class CompatibilityDetector {
7920
8601
 
7921
8602
  // core
7922
8603
 
7923
- 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 };
7924
8605
  //# sourceMappingURL=index.module.js.map