three-gpu-pathtracer 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/build/index.module.js +1013 -322
  2. package/build/index.module.js.map +1 -1
  3. package/build/index.umd.cjs +1010 -320
  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 +37 -12
  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 +4 -4
  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/sampling/equirectSampling.glsl.js +8 -1
  21. package/src/shader/structs/lightsStruct.glsl.js +5 -7
  22. package/src/textures/BlueNoiseTexture.js +87 -0
  23. package/src/textures/ProceduralEquirectTexture.js +7 -8
  24. package/src/textures/blueNoise/BlueNoiseGenerator.js +115 -0
  25. package/src/textures/blueNoise/BlueNoiseSamples.js +214 -0
  26. package/src/textures/blueNoise/utils.js +24 -0
  27. package/src/uniforms/EquirectHdrInfoUniform.js +45 -8
  28. package/src/uniforms/LightsInfoUniformStruct.js +11 -7
  29. package/src/uniforms/MaterialsTexture.js +1 -1
  30. package/src/uniforms/RenderTarget2DArray.js +50 -3
  31. package/src/uniforms/StratifiedSamplesTexture.js +49 -0
  32. package/src/uniforms/stratified/StratifiedSampler.js +73 -0
  33. package/src/uniforms/stratified/StratifiedSamplerCombined.js +59 -0
  34. package/src/uniforms/utils.js +1 -1
  35. package/src/utils/GeometryPreparationUtils.js +8 -101
  36. 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
 
@@ -2101,7 +1995,7 @@ class MaterialsTexture extends DataTexture {
2101
1995
 
2102
1996
  let index = 0;
2103
1997
  const pixelCount = materials.length * MATERIAL_PIXELS;
2104
- const dimension = Math.ceil( Math.sqrt( pixelCount ) );
1998
+ const dimension = Math.ceil( Math.sqrt( pixelCount ) ) || 1;
2105
1999
  const { threeCompatibilityTransforms, image, features } = this;
2106
2000
 
2107
2001
  // get the list of textures with unique sources
@@ -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;
2568
2524
 
2569
- newData = new Uint16Array( data.length );
2570
- for ( const i in data ) {
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 ) {
2571
2547
 
2572
- newData[ i ] = data[ i ];
2548
+ newData[ i ] = DataUtils.toHalfFloat( v );
2549
+
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 * 4; 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 );
@@ -5604,10 +5588,17 @@ const equirectSamplingGLSL = /* glsl */`
5604
5588
  // samples the color given env map with CDF and returns the pdf of the direction
5605
5589
  float sampleEquirect( vec3 direction, inout vec3 color ) {
5606
5590
 
5591
+ float totalSum = envMapInfo.totalSum;
5592
+ if ( totalSum == 0.0 ) {
5593
+
5594
+ color = vec3( 0.0 );
5595
+ return 1.0;
5596
+
5597
+ }
5598
+
5607
5599
  vec2 uv = equirectDirectionToUv( direction );
5608
5600
  color = texture2D( envMapInfo.map, uv ).rgb;
5609
5601
 
5610
- float totalSum = envMapInfo.totalSum;
5611
5602
  float lum = luminance( color );
5612
5603
  ivec2 resolution = textureSize( envMapInfo.map, 0 );
5613
5604
  float pdf = lum / totalSum;
@@ -6159,32 +6150,38 @@ const fresnelGLSL = /* glsl */`
6159
6150
 
6160
6151
  }
6161
6152
 
6162
- float evaluateFresnelWeight( float cosTheta, float eta, float f0 ) {
6153
+ // TODO: disney fresnel was removed and replaced with this fresnel function to better align with
6154
+ // the glTF but is causing blown out pixels. Should be revisited
6155
+ // float evaluateFresnelWeight( float cosTheta, float eta, float f0 ) {
6163
6156
 
6164
- if ( totalInternalReflection( cosTheta, eta ) ) {
6157
+ // if ( totalInternalReflection( cosTheta, eta ) ) {
6165
6158
 
6166
- return 1.0;
6159
+ // return 1.0;
6167
6160
 
6168
- }
6161
+ // }
6169
6162
 
6170
- return schlickFresnel( cosTheta, f0 );
6163
+ // return schlickFresnel( cosTheta, f0 );
6171
6164
 
6172
- }
6165
+ // }
6173
6166
 
6174
- /*
6175
6167
  // https://schuttejoe.github.io/post/disneybsdf/
6176
6168
  float disneyFresnel( vec3 wo, vec3 wi, vec3 wh, float f0, float eta, float metalness ) {
6177
6169
 
6178
6170
  float dotHV = dot( wo, wh );
6179
- float dotHL = dot( wi, wh );
6171
+ if ( totalInternalReflection( dotHV, eta ) ) {
6172
+
6173
+ return 1.0;
6174
+
6175
+ }
6180
6176
 
6177
+ float dotHL = dot( wi, wh );
6181
6178
  float dielectricFresnel = dielectricFresnel( abs( dotHV ), eta );
6182
6179
  float metallicFresnel = schlickFresnel( dotHL, f0 );
6183
6180
 
6184
6181
  return mix( dielectricFresnel, metallicFresnel, metalness );
6185
6182
 
6186
6183
  }
6187
- */
6184
+
6188
6185
  `;
6189
6186
 
6190
6187
  const arraySamplerTexelFetchGLSL = /*glsl */`
@@ -6241,28 +6238,28 @@ const pcgGLSL = /* glsl */`
6241
6238
  }
6242
6239
 
6243
6240
  // returns [ 0, 1 ]
6244
- float rand() {
6241
+ float pcgRand() {
6245
6242
 
6246
6243
  pcg4d( WHITE_NOISE_SEED );
6247
6244
  return float( WHITE_NOISE_SEED.x ) / float( 0xffffffffu );
6248
6245
 
6249
6246
  }
6250
6247
 
6251
- vec2 rand2() {
6248
+ vec2 pcgRand2() {
6252
6249
 
6253
6250
  pcg4d( WHITE_NOISE_SEED );
6254
6251
  return vec2( WHITE_NOISE_SEED.xy ) / float(0xffffffffu);
6255
6252
 
6256
6253
  }
6257
6254
 
6258
- vec3 rand3() {
6255
+ vec3 pcgRand3() {
6259
6256
 
6260
6257
  pcg4d( WHITE_NOISE_SEED );
6261
6258
  return vec3( WHITE_NOISE_SEED.xyz ) / float( 0xffffffffu );
6262
6259
 
6263
6260
  }
6264
6261
 
6265
- vec4 rand4() {
6262
+ vec4 pcgRand4() {
6266
6263
 
6267
6264
  pcg4d( WHITE_NOISE_SEED );
6268
6265
  return vec4( WHITE_NOISE_SEED ) / float( 0xffffffffu );
@@ -6335,7 +6332,7 @@ const cameraUtilsGLSL = /* glsl */`
6335
6332
 
6336
6333
  // Jitter the camera ray by finding a uv coordinate at a random sample
6337
6334
  // around this pixel's UV coordinate for AA
6338
- vec2 ruv = sobol2( 0 );
6335
+ vec2 ruv = rand2( 0 );
6339
6336
  vec2 jitteredUv = vUv + vec2( tentFilter( ruv.x ) * ssd.x, tentFilter( ruv.y ) * ssd.y );
6340
6337
  Ray ray;
6341
6338
 
@@ -6380,7 +6377,7 @@ const cameraUtilsGLSL = /* glsl */`
6380
6377
 
6381
6378
  // get the aperture sample
6382
6379
  // if blades === 0 then we assume a circle
6383
- vec3 shapeUVW= sobol3( 1 );
6380
+ vec3 shapeUVW= rand3( 1 );
6384
6381
  int blades = physicalCamera.apertureBlades;
6385
6382
  float anamorphicRatio = physicalCamera.anamorphicRatio;
6386
6383
  vec2 apertureSample = blades == 0 ? sampleCircle( shapeUVW.xy ) : sampleRegularPolygon( blades, shapeUVW );
@@ -6416,6 +6413,9 @@ const attenuateHitGLSL = /* glsl */`
6416
6413
  out vec3 color
6417
6414
  ) {
6418
6415
 
6416
+ // store the original bounce index so we can reset it after
6417
+ uint originalBounceIndex = sobolBounceIndex;
6418
+
6419
6419
  int traversals = state.traversals;
6420
6420
  int transmissiveTraversals = state.transmissiveTraversals;
6421
6421
  bool isShadowRay = state.isShadowRay;
@@ -6428,22 +6428,25 @@ const attenuateHitGLSL = /* glsl */`
6428
6428
 
6429
6429
  color = vec3( 1.0 );
6430
6430
 
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.
6431
+ bool result = true;
6433
6432
  for ( int i = 0; i < traversals; i ++ ) {
6434
6433
 
6434
+ sobolBounceIndex ++;
6435
+
6435
6436
  int hitType = traceScene( ray, fogMaterial, surfaceHit );
6436
6437
 
6437
6438
  if ( hitType == FOG_HIT ) {
6438
6439
 
6439
- return true;
6440
+ result = true;
6441
+ break;
6440
6442
 
6441
6443
  } else if ( hitType == SURFACE_HIT ) {
6442
6444
 
6443
6445
  float totalDist = distance( startPoint, ray.origin + ray.direction * surfaceHit.dist );
6444
6446
  if ( totalDist > rayDist ) {
6445
6447
 
6446
- return false;
6448
+ result = false;
6449
+ break;
6447
6450
 
6448
6451
  }
6449
6452
 
@@ -6525,7 +6528,7 @@ const attenuateHitGLSL = /* glsl */`
6525
6528
  bool useAlphaTest = alphaTest != 0.0;
6526
6529
  float transmissionFactor = ( 1.0 - metalness ) * transmission;
6527
6530
  if (
6528
- transmissionFactor < rand() && ! (
6531
+ transmissionFactor < rand( 9 ) && ! (
6529
6532
  // material sidedness
6530
6533
  material.side != 0.0 && surfaceHit.side == material.side
6531
6534
 
@@ -6533,11 +6536,12 @@ const attenuateHitGLSL = /* glsl */`
6533
6536
  || useAlphaTest && albedo.a < alphaTest
6534
6537
 
6535
6538
  // opacity
6536
- || material.transparent && ! useAlphaTest && albedo.a < rand()
6539
+ || material.transparent && ! useAlphaTest && albedo.a < rand( 10 )
6537
6540
  )
6538
6541
  ) {
6539
6542
 
6540
- return true;
6543
+ result = true;
6544
+ break;
6541
6545
 
6542
6546
  }
6543
6547
 
@@ -6563,13 +6567,16 @@ const attenuateHitGLSL = /* glsl */`
6563
6567
 
6564
6568
  } else {
6565
6569
 
6566
- return false;
6570
+ result = false;
6571
+ break;
6567
6572
 
6568
6573
  }
6569
6574
 
6570
6575
  }
6571
6576
 
6572
- return true;
6577
+ // reset the bounce index
6578
+ sobolBounceIndex = originalBounceIndex;
6579
+ return result;
6573
6580
 
6574
6581
  }
6575
6582
 
@@ -6600,7 +6607,7 @@ const traceSceneGLSL = /* glsl */`
6600
6607
 
6601
6608
  // offset the distance so we don't run into issues with particles on the same surface
6602
6609
  // as other objects
6603
- float particleDist = intersectFogVolume( fogMaterial, sobol( 1 ) );
6610
+ float particleDist = intersectFogVolume( fogMaterial, rand( 1 ) );
6604
6611
  if ( particleDist + RAY_OFFSET < surfaceHit.dist ) {
6605
6612
 
6606
6613
  surfaceHit.side = 1.0;
@@ -6663,6 +6670,7 @@ const getSurfaceRecordGLSL = /* glsl */`
6663
6670
 
6664
6671
  vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
6665
6672
  albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
6673
+
6666
6674
  }
6667
6675
 
6668
6676
  if ( material.vertexColors ) {
@@ -6694,7 +6702,7 @@ const getSurfaceRecordGLSL = /* glsl */`
6694
6702
  || useAlphaTest && albedo.a < alphaTest
6695
6703
 
6696
6704
  // opacity
6697
- || material.transparent && ! useAlphaTest && albedo.a < sobol( 3 )
6705
+ || material.transparent && ! useAlphaTest && albedo.a < rand( 3 )
6698
6706
  ) {
6699
6707
 
6700
6708
  return SKIP_SURFACE;
@@ -6956,10 +6964,10 @@ const directLightContributionGLSL = /*glsl*/`
6956
6964
  vec3 result = vec3( 0.0 );
6957
6965
 
6958
6966
  // uniformly pick a light or environment map
6959
- if( lightsDenom != 0.0 && sobol( 5 ) < float( lights.count ) / lightsDenom ) {
6967
+ if( lightsDenom != 0.0 && rand( 5 ) < float( lights.count ) / lightsDenom ) {
6960
6968
 
6961
6969
  // sample a light or environment
6962
- LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, sobol3( 6 ) );
6970
+ LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, rand3( 6 ) );
6963
6971
 
6964
6972
  bool isSampleBelowSurface = ! surf.volumeParticle && dot( surf.faceNormal, lightRec.direction ) < 0.0;
6965
6973
  if ( isSampleBelowSurface ) {
@@ -6994,11 +7002,11 @@ const directLightContributionGLSL = /*glsl*/`
6994
7002
 
6995
7003
  }
6996
7004
 
6997
- } else {
7005
+ } else if ( envMapInfo.totalSum != 0.0 && environmentIntensity != 0.0 ) {
6998
7006
 
6999
7007
  // find a sample in the environment map to include in the contribution
7000
7008
  vec3 envColor, envDirection;
7001
- float envPdf = sampleEquirectProbability( sobol2( 7 ), envColor, envDirection );
7009
+ float envPdf = sampleEquirectProbability( rand2( 7 ), envColor, envDirection );
7002
7010
  envDirection = invEnvRotation3x3 * envDirection;
7003
7011
 
7004
7012
  // this env sampling is not set up for transmissive sampling and yields overly bright
@@ -7047,6 +7055,667 @@ const directLightContributionGLSL = /*glsl*/`
7047
7055
 
7048
7056
  `;
7049
7057
 
7058
+ const stratifiedTextureGLSL = /* glsl */`
7059
+
7060
+ uniform sampler2D stratifiedTexture;
7061
+ uniform sampler2D stratifiedOffsetTexture;
7062
+
7063
+ uint sobolPixelIndex = 0u;
7064
+ uint sobolPathIndex = 0u;
7065
+ uint sobolBounceIndex = 0u;
7066
+ vec4 pixelSeed = vec4( 0 );
7067
+
7068
+ vec4 rand4( int v ) {
7069
+
7070
+ ivec2 uv = ivec2( v, sobolBounceIndex );
7071
+ vec4 stratifiedSample = texelFetch( stratifiedTexture, uv, 0 );
7072
+ return fract( stratifiedSample + pixelSeed.r ); // blue noise + stratified samples
7073
+
7074
+ }
7075
+
7076
+ vec3 rand3( int v ) {
7077
+
7078
+ return rand4( v ).xyz;
7079
+
7080
+ }
7081
+
7082
+ vec2 rand2( int v ) {
7083
+
7084
+ return rand4( v ).xy;
7085
+
7086
+ }
7087
+
7088
+ float rand( int v ) {
7089
+
7090
+ return rand4( v ).x;
7091
+
7092
+ }
7093
+
7094
+ void rng_initialize( vec2 screenCoord, int frame ) {
7095
+
7096
+ // tile the small noise texture across the entire screen
7097
+ ivec2 noiseSize = ivec2( textureSize( stratifiedOffsetTexture, 0 ) );
7098
+ pixelSeed = texelFetch( stratifiedOffsetTexture, ivec2( screenCoord.xy ) % noiseSize, 0 );
7099
+
7100
+ }
7101
+
7102
+ `;
7103
+
7104
+ // Stratified Sampling based on implementation from hoverinc pathtracer
7105
+ // - https://github.com/hoverinc/ray-tracing-renderer
7106
+ // - http://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Stratified_Sampling.html
7107
+
7108
+ function shuffle( arr ) {
7109
+
7110
+ for ( let i = arr.length - 1; i > 0; i -- ) {
7111
+
7112
+ const j = Math.floor( Math.random() * ( i + 1 ) );
7113
+ const x = arr[ i ];
7114
+ arr[ i ] = arr[ j ];
7115
+ arr[ j ] = x;
7116
+
7117
+ }
7118
+
7119
+ return arr;
7120
+
7121
+ }
7122
+
7123
+ // strataCount : The number of bins per dimension
7124
+ // dimensions : The number of dimensions to generate stratified values for
7125
+ class StratifiedSampler {
7126
+
7127
+ constructor( strataCount, dimensions ) {
7128
+
7129
+ const l = strataCount ** dimensions;
7130
+ const strata = new Uint16Array( l );
7131
+ let index = l;
7132
+
7133
+ // each integer represents a statum bin
7134
+ for ( let i = 0; i < l; i ++ ) {
7135
+
7136
+ strata[ i ] = i;
7137
+
7138
+ }
7139
+
7140
+ this.samples = new Float32Array( dimensions );
7141
+
7142
+ this.strataCount = strataCount;
7143
+
7144
+ this.restart = function () {
7145
+
7146
+ index = 0;
7147
+
7148
+ };
7149
+
7150
+ this.next = function () {
7151
+
7152
+ const { samples } = this;
7153
+
7154
+ if ( index >= strata.length ) {
7155
+
7156
+ shuffle( strata );
7157
+ this.restart();
7158
+
7159
+ }
7160
+
7161
+ let stratum = strata[ index ++ ];
7162
+
7163
+ for ( let i = 0; i < dimensions; i ++ ) {
7164
+
7165
+ samples[ i ] = ( stratum % strataCount + Math.random() ) / strataCount;
7166
+ stratum = Math.floor( stratum / strataCount );
7167
+
7168
+ }
7169
+
7170
+ return samples;
7171
+
7172
+ };
7173
+
7174
+ }
7175
+
7176
+ }
7177
+
7178
+ // Stratified Sampling based on implementation from hoverinc pathtracer
7179
+
7180
+ // Stratified set of data with each tuple stratified separately and combined
7181
+ class StratifiedSamplerCombined {
7182
+
7183
+ constructor( strataCount, listOfDimensions ) {
7184
+
7185
+ let totalDim = 0;
7186
+ for ( const dim of listOfDimensions ) {
7187
+
7188
+ totalDim += dim;
7189
+
7190
+ }
7191
+
7192
+ const combined = new Float32Array( totalDim );
7193
+ const strataObjs = [];
7194
+ let offset = 0;
7195
+ for ( const dim of listOfDimensions ) {
7196
+
7197
+ const sampler = new StratifiedSampler( strataCount, dim );
7198
+ sampler.samples = new Float32Array( combined.buffer, offset, sampler.samples.length );
7199
+ offset += sampler.samples.length * 4;
7200
+ strataObjs.push( sampler );
7201
+
7202
+ }
7203
+
7204
+ this.samples = combined;
7205
+
7206
+ this.strataCount = strataCount;
7207
+
7208
+ this.next = function () {
7209
+
7210
+ for ( const strata of strataObjs ) {
7211
+
7212
+ strata.next();
7213
+
7214
+ }
7215
+
7216
+ return combined;
7217
+
7218
+ };
7219
+
7220
+ this.restart = function () {
7221
+
7222
+ for ( const strata of strataObjs ) {
7223
+
7224
+ strata.restart();
7225
+
7226
+ }
7227
+
7228
+ };
7229
+
7230
+ }
7231
+
7232
+ }
7233
+
7234
+ class StratifiedSamplesTexture extends DataTexture {
7235
+
7236
+ constructor( count = 1, depth = 1, strata = 8 ) {
7237
+
7238
+ super( new Float32Array( 1 ), 1, 1, RGBAFormat, FloatType );
7239
+ this.minFilter = NearestFilter;
7240
+ this.magFilter = NearestFilter;
7241
+
7242
+ this.strata = strata;
7243
+ this.sampler = null;
7244
+
7245
+ this.init( count, depth, strata );
7246
+
7247
+ }
7248
+
7249
+ init( count, depth, strata = this.strata ) {
7250
+
7251
+ const { image } = this;
7252
+ if ( image.width === depth && image.height === count ) {
7253
+
7254
+ return;
7255
+
7256
+ }
7257
+
7258
+ const dimensions = new Array( count * depth ).fill( 4 );
7259
+ const sampler = new StratifiedSamplerCombined( strata, dimensions );
7260
+
7261
+ image.width = depth;
7262
+ image.height = count;
7263
+ image.data = sampler.samples;
7264
+
7265
+ this.sampler = sampler;
7266
+
7267
+ this.dispose();
7268
+ this.next();
7269
+
7270
+ }
7271
+
7272
+ next() {
7273
+
7274
+ this.sampler.next();
7275
+ this.needsUpdate = true;
7276
+
7277
+ }
7278
+
7279
+ }
7280
+
7281
+ function shuffleArray( array, random = Math.random ) {
7282
+
7283
+ for ( let i = array.length - 1; i > 0; i -- ) {
7284
+
7285
+ const replaceIndex = ~ ~ ( ( random() - 1e-6 ) * i );
7286
+ const tmp = array[ i ];
7287
+ array[ i ] = array[ replaceIndex ];
7288
+ array[ replaceIndex ] = tmp;
7289
+
7290
+ }
7291
+
7292
+ }
7293
+
7294
+ function fillWithOnes( array, count ) {
7295
+
7296
+ array.fill( 0 );
7297
+
7298
+ for ( let i = 0; i < count; i ++ ) {
7299
+
7300
+ array[ i ] = 1;
7301
+
7302
+ }
7303
+
7304
+ }
7305
+
7306
+ class BlueNoiseSamples {
7307
+
7308
+ constructor( size ) {
7309
+
7310
+ this.count = 0;
7311
+ this.size = - 1;
7312
+ this.sigma = - 1;
7313
+ this.radius = - 1;
7314
+ this.lookupTable = null;
7315
+ this.score = null;
7316
+ this.binaryPattern = null;
7317
+
7318
+ this.resize( size );
7319
+ this.setSigma( 1.5 );
7320
+
7321
+ }
7322
+
7323
+ findVoid() {
7324
+
7325
+ const { score, binaryPattern } = this;
7326
+
7327
+ let currValue = Infinity;
7328
+ let currIndex = - 1;
7329
+ for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
7330
+
7331
+ if ( binaryPattern[ i ] !== 0 ) {
7332
+
7333
+ continue;
7334
+
7335
+ }
7336
+
7337
+ const pScore = score[ i ];
7338
+ if ( pScore < currValue ) {
7339
+
7340
+ currValue = pScore;
7341
+ currIndex = i;
7342
+
7343
+ }
7344
+
7345
+ }
7346
+
7347
+ return currIndex;
7348
+
7349
+ }
7350
+
7351
+ findCluster() {
7352
+
7353
+ const { score, binaryPattern } = this;
7354
+
7355
+ let currValue = - Infinity;
7356
+ let currIndex = - 1;
7357
+ for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
7358
+
7359
+ if ( binaryPattern[ i ] !== 1 ) {
7360
+
7361
+ continue;
7362
+
7363
+ }
7364
+
7365
+ const pScore = score[ i ];
7366
+ if ( pScore > currValue ) {
7367
+
7368
+ currValue = pScore;
7369
+ currIndex = i;
7370
+
7371
+ }
7372
+
7373
+ }
7374
+
7375
+ return currIndex;
7376
+
7377
+ }
7378
+
7379
+ setSigma( sigma ) {
7380
+
7381
+ if ( sigma === this.sigma ) {
7382
+
7383
+ return;
7384
+
7385
+ }
7386
+
7387
+ // generate a radius in which the score will be updated under the
7388
+ // assumption that e^-10 is insignificant enough to be the border at
7389
+ // which we drop off.
7390
+ const radius = ~ ~ ( Math.sqrt( 10 * 2 * ( sigma ** 2 ) ) + 1 );
7391
+ const lookupWidth = 2 * radius + 1;
7392
+ const lookupTable = new Float32Array( lookupWidth * lookupWidth );
7393
+ const sigma2 = sigma * sigma;
7394
+ for ( let x = - radius; x <= radius; x ++ ) {
7395
+
7396
+ for ( let y = - radius; y <= radius; y ++ ) {
7397
+
7398
+ const index = ( radius + y ) * lookupWidth + x + radius;
7399
+ const dist2 = x * x + y * y;
7400
+ lookupTable[ index ] = Math.E ** ( - dist2 / ( 2 * sigma2 ) );
7401
+
7402
+ }
7403
+
7404
+ }
7405
+
7406
+ this.lookupTable = lookupTable;
7407
+ this.sigma = sigma;
7408
+ this.radius = radius;
7409
+
7410
+ }
7411
+
7412
+ resize( size ) {
7413
+
7414
+ if ( this.size !== size ) {
7415
+
7416
+ this.size = size;
7417
+ this.score = new Float32Array( size * size );
7418
+ this.binaryPattern = new Uint8Array( size * size );
7419
+
7420
+ }
7421
+
7422
+
7423
+ }
7424
+
7425
+ invert() {
7426
+
7427
+ const { binaryPattern, score, size } = this;
7428
+
7429
+ score.fill( 0 );
7430
+
7431
+ for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
7432
+
7433
+ if ( binaryPattern[ i ] === 0 ) {
7434
+
7435
+ const y = ~ ~ ( i / size );
7436
+ const x = i - y * size;
7437
+ this.updateScore( x, y, 1 );
7438
+ binaryPattern[ i ] = 1;
7439
+
7440
+ } else {
7441
+
7442
+ binaryPattern[ i ] = 0;
7443
+
7444
+ }
7445
+
7446
+ }
7447
+
7448
+ }
7449
+
7450
+ updateScore( x, y, multiplier ) {
7451
+
7452
+ // TODO: Is there a way to keep track of the highest and lowest scores here to avoid have to search over
7453
+ // everything in the buffer?
7454
+ const { size, score, lookupTable } = this;
7455
+
7456
+ // const sigma2 = sigma * sigma;
7457
+ // const radius = Math.floor( size / 2 );
7458
+ const radius = this.radius;
7459
+ const lookupWidth = 2 * radius + 1;
7460
+ for ( let px = - radius; px <= radius; px ++ ) {
7461
+
7462
+ for ( let py = - radius; py <= radius; py ++ ) {
7463
+
7464
+ // const dist2 = px * px + py * py;
7465
+ // const value = Math.E ** ( - dist2 / ( 2 * sigma2 ) );
7466
+
7467
+ const lookupIndex = ( radius + py ) * lookupWidth + px + radius;
7468
+ const value = lookupTable[ lookupIndex ];
7469
+
7470
+ let sx = ( x + px );
7471
+ sx = sx < 0 ? size + sx : sx % size;
7472
+
7473
+ let sy = ( y + py );
7474
+ sy = sy < 0 ? size + sy : sy % size;
7475
+
7476
+ const sindex = sy * size + sx;
7477
+ score[ sindex ] += multiplier * value;
7478
+
7479
+ }
7480
+
7481
+ }
7482
+
7483
+ }
7484
+
7485
+ addPointIndex( index ) {
7486
+
7487
+ this.binaryPattern[ index ] = 1;
7488
+
7489
+ const size = this.size;
7490
+ const y = ~ ~ ( index / size );
7491
+ const x = index - y * size;
7492
+ this.updateScore( x, y, 1 );
7493
+ this.count ++;
7494
+
7495
+ }
7496
+
7497
+ removePointIndex( index ) {
7498
+
7499
+ this.binaryPattern[ index ] = 0;
7500
+
7501
+ const size = this.size;
7502
+ const y = ~ ~ ( index / size );
7503
+ const x = index - y * size;
7504
+ this.updateScore( x, y, - 1 );
7505
+ this.count --;
7506
+
7507
+ }
7508
+
7509
+ copy( source ) {
7510
+
7511
+ this.resize( source.size );
7512
+ this.score.set( source.score );
7513
+ this.binaryPattern.set( source.binaryPattern );
7514
+ this.setSigma( source.sigma );
7515
+ this.count = source.count;
7516
+
7517
+ }
7518
+
7519
+ }
7520
+
7521
+ class BlueNoiseGenerator {
7522
+
7523
+ constructor() {
7524
+
7525
+ this.random = Math.random;
7526
+ this.sigma = 1.5;
7527
+ this.size = 64;
7528
+ this.majorityPointsRatio = 0.1;
7529
+
7530
+ this.samples = new BlueNoiseSamples( 1 );
7531
+ this.savedSamples = new BlueNoiseSamples( 1 );
7532
+
7533
+ }
7534
+
7535
+ generate() {
7536
+
7537
+ // http://cv.ulichney.com/papers/1993-void-cluster.pdf
7538
+
7539
+ const {
7540
+ samples,
7541
+ savedSamples,
7542
+ sigma,
7543
+ majorityPointsRatio,
7544
+ size,
7545
+ } = this;
7546
+
7547
+ samples.resize( size );
7548
+ samples.setSigma( sigma );
7549
+
7550
+ // 1. Randomly place the minority points.
7551
+ const pointCount = Math.floor( size * size * majorityPointsRatio );
7552
+ const initialSamples = samples.binaryPattern;
7553
+
7554
+ fillWithOnes( initialSamples, pointCount );
7555
+ shuffleArray( initialSamples, this.random );
7556
+
7557
+ for ( let i = 0, l = initialSamples.length; i < l; i ++ ) {
7558
+
7559
+ if ( initialSamples[ i ] === 1 ) {
7560
+
7561
+ samples.addPointIndex( i );
7562
+
7563
+ }
7564
+
7565
+ }
7566
+
7567
+ // 2. Remove minority point that is in densest cluster and place it in the largest void.
7568
+ while ( true ) {
7569
+
7570
+ const clusterIndex = samples.findCluster();
7571
+ samples.removePointIndex( clusterIndex );
7572
+
7573
+ const voidIndex = samples.findVoid();
7574
+ if ( clusterIndex === voidIndex ) {
7575
+
7576
+ samples.addPointIndex( clusterIndex );
7577
+ break;
7578
+
7579
+ }
7580
+
7581
+ samples.addPointIndex( voidIndex );
7582
+
7583
+ }
7584
+
7585
+ // 3. PHASE I: Assign a rank to each progressively less dense cluster point and put it
7586
+ // in the dither array.
7587
+ const ditherArray = new Uint32Array( size * size );
7588
+ savedSamples.copy( samples );
7589
+
7590
+ let rank;
7591
+ rank = samples.count - 1;
7592
+ while ( rank >= 0 ) {
7593
+
7594
+ const clusterIndex = samples.findCluster();
7595
+ samples.removePointIndex( clusterIndex );
7596
+
7597
+ ditherArray[ clusterIndex ] = rank;
7598
+ rank --;
7599
+
7600
+ }
7601
+
7602
+ // 4. PHASE II: Do the same thing for the largest voids up to half of the total pixels using
7603
+ // the initial binary pattern.
7604
+ const totalSize = size * size;
7605
+ rank = savedSamples.count;
7606
+ while ( rank < totalSize / 2 ) {
7607
+
7608
+ const voidIndex = savedSamples.findVoid();
7609
+ savedSamples.addPointIndex( voidIndex );
7610
+ ditherArray[ voidIndex ] = rank;
7611
+ rank ++;
7612
+
7613
+ }
7614
+
7615
+ // 5. PHASE III: Invert the pattern and finish out by assigning a rank to the remaining
7616
+ // and iteratively removing them.
7617
+ savedSamples.invert();
7618
+
7619
+ while ( rank < totalSize ) {
7620
+
7621
+ const clusterIndex = savedSamples.findCluster();
7622
+ savedSamples.removePointIndex( clusterIndex );
7623
+ ditherArray[ clusterIndex ] = rank;
7624
+ rank ++;
7625
+
7626
+ }
7627
+
7628
+ return { data: ditherArray, maxValue: totalSize };
7629
+
7630
+ }
7631
+
7632
+ }
7633
+
7634
+ function getStride( channels ) {
7635
+
7636
+ if ( channels >= 3 ) {
7637
+
7638
+ return 4;
7639
+
7640
+ } else {
7641
+
7642
+ return channels;
7643
+
7644
+ }
7645
+
7646
+ }
7647
+
7648
+ function getFormat( channels ) {
7649
+
7650
+ switch ( channels ) {
7651
+
7652
+ case 1:
7653
+ return RedFormat;
7654
+ case 2:
7655
+ return RGFormat;
7656
+ default:
7657
+ return RGBAFormat;
7658
+
7659
+ }
7660
+
7661
+ }
7662
+
7663
+ class BlueNoiseTexture extends DataTexture {
7664
+
7665
+ constructor( size = 64, channels = 1 ) {
7666
+
7667
+ super( new Float32Array( 4 ), 1, 1, RGBAFormat, FloatType );
7668
+ this.minFilter = NearestFilter;
7669
+ this.magFilter = NearestFilter;
7670
+
7671
+ this.size = size;
7672
+ this.channels = channels;
7673
+ this.update();
7674
+
7675
+ }
7676
+
7677
+ update() {
7678
+
7679
+ const channels = this.channels;
7680
+ const size = this.size;
7681
+ const generator = new BlueNoiseGenerator();
7682
+ generator.channels = channels;
7683
+ generator.size = size;
7684
+
7685
+ const stride = getStride( channels );
7686
+ const format = getFormat( stride );
7687
+ if ( this.image.width !== size || format !== this.format ) {
7688
+
7689
+ this.image.width = size;
7690
+ this.image.height = size;
7691
+ this.image.data = new Float32Array( ( size ** 2 ) * stride );
7692
+ this.format = format;
7693
+ this.dispose();
7694
+
7695
+ }
7696
+
7697
+ const data = this.image.data;
7698
+ for ( let i = 0, l = channels; i < l; i ++ ) {
7699
+
7700
+ const result = generator.generate();
7701
+ const bin = result.data;
7702
+ const maxValue = result.maxValue;
7703
+
7704
+ for ( let j = 0, l2 = bin.length; j < l2; j ++ ) {
7705
+
7706
+ const value = bin[ j ] / maxValue;
7707
+ data[ j * stride + i ] = value;
7708
+
7709
+ }
7710
+
7711
+ }
7712
+
7713
+ this.needsUpdate = true;
7714
+
7715
+ }
7716
+
7717
+ }
7718
+
7050
7719
  class PhysicalPathTracingMaterial extends MaterialBase {
7051
7720
 
7052
7721
  onBeforeRender() {
@@ -7070,7 +7739,12 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7070
7739
  FEATURE_DOF: 1,
7071
7740
  FEATURE_BACKGROUND_MAP: 0,
7072
7741
  FEATURE_FOG: 1,
7073
- FEATURE_SOBOL: 0,
7742
+
7743
+ // 0 = PCG
7744
+ // 1 = Sobol
7745
+ // 2 = Stratified List
7746
+ RANDOM_TYPE: 2,
7747
+
7074
7748
  // 0 = Perspective
7075
7749
  // 1 = Orthographic
7076
7750
  // 2 = Equirectangular
@@ -7112,6 +7786,8 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7112
7786
 
7113
7787
  backgroundAlpha: { value: 1.0 },
7114
7788
  sobolTexture: { value: null },
7789
+ stratifiedTexture: { value: new StratifiedSamplesTexture() },
7790
+ stratifiedOffsetTexture: { value: new BlueNoiseTexture( 64, 1 ) },
7115
7791
  },
7116
7792
 
7117
7793
  vertexShader: /* glsl */`
@@ -7151,23 +7827,35 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7151
7827
  ${ materialStructGLSL }
7152
7828
 
7153
7829
  // random
7154
- ${ pcgGLSL }
7155
- #if FEATURE_SOBOL
7830
+ #if RANDOM_TYPE == 2 // Stratified List
7156
7831
 
7832
+ ${ stratifiedTextureGLSL }
7833
+
7834
+ #elif RANDOM_TYPE == 1 // Sobol
7835
+
7836
+ ${ pcgGLSL }
7157
7837
  ${ sobolCommonGLSL }
7158
7838
  ${ sobolSamplingGLSL }
7159
7839
 
7160
- #else
7840
+ #define rand(v) sobol(v)
7841
+ #define rand2(v) sobol2(v)
7842
+ #define rand3(v) sobol3(v)
7843
+ #define rand4(v) sobol4(v)
7844
+
7845
+ #else // PCG
7846
+
7847
+ ${ pcgGLSL }
7161
7848
 
7162
7849
  // Using the sobol functions seems to break the the compiler on MacOS
7163
7850
  // - specifically the "sobolReverseBits" function.
7164
7851
  uint sobolPixelIndex = 0u;
7165
7852
  uint sobolPathIndex = 0u;
7166
7853
  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(); }
7854
+
7855
+ #define rand(v) pcgRand()
7856
+ #define rand2(v) pcgRand2()
7857
+ #define rand3(v) pcgRand3()
7858
+ #define rand4(v) pcgRand4()
7171
7859
 
7172
7860
  #endif
7173
7861
 
@@ -7285,7 +7973,10 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7285
7973
  // inverse environment rotation
7286
7974
  envRotation3x3 = mat3( environmentRotation );
7287
7975
  invEnvRotation3x3 = inverse( envRotation3x3 );
7288
- lightsDenom = environmentIntensity == 0.0 && lights.count != 0u ? float( lights.count ) : float( lights.count + 1u );
7976
+ lightsDenom =
7977
+ ( environmentIntensity == 0.0 || envMapInfo.totalSum == 0.0 ) && lights.count != 0u ?
7978
+ float( lights.count ) :
7979
+ float( lights.count + 1u );
7289
7980
 
7290
7981
  // final color
7291
7982
  gl_FragColor = vec4( 0, 0, 0, 1 );
@@ -7354,7 +8045,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7354
8045
 
7355
8046
  if ( state.firstRay || state.transmissiveRay ) {
7356
8047
 
7357
- gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, sobol2( 2 ) ) * state.throughputColor;
8048
+ gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, rand2( 2 ) ) * state.throughputColor;
7358
8049
  gl_FragColor.a = backgroundAlpha;
7359
8050
 
7360
8051
  } else {
@@ -7445,7 +8136,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7445
8136
  }
7446
8137
 
7447
8138
  scatterRec = bsdfSample( - ray.direction, surf );
7448
- state.isShadowRay = scatterRec.specularPdf < sobol( 4 );
8139
+ state.isShadowRay = scatterRec.specularPdf < rand( 4 );
7449
8140
 
7450
8141
  bool isBelowSurface = ! surf.volumeParticle && dot( scatterRec.direction, surf.faceNormal ) < 0.0;
7451
8142
  vec3 hitPoint = stepRayOrigin( ray.origin, ray.direction, isBelowSurface ? - surf.faceNormal : surf.faceNormal, surfaceHit.dist );
@@ -7515,7 +8206,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
7515
8206
  rrProb = sqrt( rrProb );
7516
8207
  rrProb = max( rrProb, depthProb );
7517
8208
  rrProb = min( rrProb, 1.0 );
7518
- if ( sobol( 8 ) > rrProb ) {
8209
+ if ( rand( 8 ) > rrProb ) {
7519
8210
 
7520
8211
  break;
7521
8212
 
@@ -7920,5 +8611,5 @@ class CompatibilityDetector {
7920
8611
 
7921
8612
  // core
7922
8613
 
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 };
8614
+ 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
8615
  //# sourceMappingURL=index.module.js.map