three-gpu-pathtracer 0.0.16 → 0.0.18

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