three-gpu-pathtracer 0.0.17 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.module.js +1013 -322
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +1010 -320
- package/build/index.umd.cjs.map +1 -1
- package/package.json +2 -2
- package/src/core/DynamicPathTracingSceneGenerator.js +80 -40
- package/src/core/PathTracingRenderer.js +28 -34
- package/src/core/PathTracingSceneGenerator.js +11 -60
- package/src/materials/pathtracing/LambertPathTracingMaterial.js +1 -1
- package/src/materials/pathtracing/PhysicalPathTracingMaterial.js +37 -12
- package/src/materials/pathtracing/glsl/attenuateHit.glsl.js +19 -9
- package/src/materials/pathtracing/glsl/cameraUtils.glsl.js +2 -2
- package/src/materials/pathtracing/glsl/directLightContribution.glsl.js +4 -4
- package/src/materials/pathtracing/glsl/getSurfaceRecord.glsl.js +2 -1
- package/src/materials/pathtracing/glsl/traceScene.glsl.js +1 -1
- package/src/shader/bsdf/bsdfSampling.glsl.js +14 -10
- package/src/shader/common/fresnel.glsl.js +15 -9
- package/src/shader/rand/pcg.glsl.js +4 -4
- package/src/shader/rand/stratifiedTexture.glsl.js +45 -0
- package/src/shader/sampling/equirectSampling.glsl.js +8 -1
- package/src/shader/structs/lightsStruct.glsl.js +5 -7
- package/src/textures/BlueNoiseTexture.js +87 -0
- package/src/textures/ProceduralEquirectTexture.js +7 -8
- package/src/textures/blueNoise/BlueNoiseGenerator.js +115 -0
- package/src/textures/blueNoise/BlueNoiseSamples.js +214 -0
- package/src/textures/blueNoise/utils.js +24 -0
- package/src/uniforms/EquirectHdrInfoUniform.js +45 -8
- package/src/uniforms/LightsInfoUniformStruct.js +11 -7
- package/src/uniforms/MaterialsTexture.js +1 -1
- package/src/uniforms/RenderTarget2DArray.js +50 -3
- package/src/uniforms/StratifiedSamplesTexture.js +49 -0
- package/src/uniforms/stratified/StratifiedSampler.js +73 -0
- package/src/uniforms/stratified/StratifiedSamplerCombined.js +59 -0
- package/src/uniforms/utils.js +1 -1
- package/src/utils/GeometryPreparationUtils.js +8 -101
- package/src/workers/PathTracingSceneWorker.js +18 -8
package/build/index.umd.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
|
@@ -1078,25 +1072,6 @@
|
|
|
1078
1072
|
|
|
1079
1073
|
}
|
|
1080
1074
|
|
|
1081
|
-
function trimToAttributes( geometry, attributes ) {
|
|
1082
|
-
|
|
1083
|
-
// trim any unneeded attributes
|
|
1084
|
-
if ( attributes ) {
|
|
1085
|
-
|
|
1086
|
-
for ( const key in geometry.attributes ) {
|
|
1087
|
-
|
|
1088
|
-
if ( ! attributes.includes( key ) ) {
|
|
1089
|
-
|
|
1090
|
-
geometry.deleteAttribute( key );
|
|
1091
|
-
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
1075
|
function setCommonAttributes( geometry, options ) {
|
|
1101
1076
|
|
|
1102
1077
|
const { attributes = [], normalMapRequired = false } = options;
|
|
@@ -1114,6 +1089,13 @@
|
|
|
1114
1089
|
|
|
1115
1090
|
}
|
|
1116
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
|
+
|
|
1117
1099
|
if ( ! geometry.attributes.tangent && ( attributes && attributes.includes( 'tangent' ) ) ) {
|
|
1118
1100
|
|
|
1119
1101
|
if ( normalMapRequired ) {
|
|
@@ -1162,169 +1144,46 @@
|
|
|
1162
1144
|
|
|
1163
1145
|
}
|
|
1164
1146
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
options = { attributes: null, cloneGeometry: true, ...options };
|
|
1147
|
+
const dummyMaterial = new three.MeshBasicMaterial();
|
|
1148
|
+
function getDummyMesh() {
|
|
1168
1149
|
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
|
|
1150
|
+
const emptyGeometry = new three.BufferGeometry();
|
|
1151
|
+
emptyGeometry.setAttribute( 'position', new three.BufferAttribute( new Float32Array( 9 ), 3 ) );
|
|
1152
|
+
return new three.Mesh( emptyGeometry, dummyMaterial );
|
|
1172
1153
|
|
|
1173
|
-
|
|
1174
|
-
const mesh = meshes[ i ];
|
|
1175
|
-
if ( mesh.visible === false ) continue;
|
|
1176
|
-
|
|
1177
|
-
if ( Array.isArray( mesh.material ) ) {
|
|
1178
|
-
|
|
1179
|
-
mesh.material.forEach( m => materialSet.add( m ) );
|
|
1180
|
-
|
|
1181
|
-
} else {
|
|
1182
|
-
|
|
1183
|
-
materialSet.add( mesh.material );
|
|
1184
|
-
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
const materials = Array.from( materialSet );
|
|
1190
|
-
for ( let i = 0, l = meshes.length; i < l; i ++ ) {
|
|
1191
|
-
|
|
1192
|
-
// ensure the matrix world is up to date
|
|
1193
|
-
const mesh = meshes[ i ];
|
|
1194
|
-
if ( mesh.visible === false ) continue;
|
|
1195
|
-
|
|
1196
|
-
mesh.updateMatrixWorld();
|
|
1197
|
-
|
|
1198
|
-
// apply the matrix world to the geometry
|
|
1199
|
-
const originalGeometry = meshes[ i ].geometry;
|
|
1200
|
-
const geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
|
|
1201
|
-
geometry.applyMatrix4( mesh.matrixWorld );
|
|
1202
|
-
|
|
1203
|
-
if ( mesh.matrixWorld.determinant() < 0 ) {
|
|
1204
|
-
|
|
1205
|
-
geometry.index.array.reverse();
|
|
1206
|
-
|
|
1207
|
-
}
|
|
1154
|
+
}
|
|
1208
1155
|
|
|
1209
|
-
|
|
1210
|
-
setCommonAttributes( geometry, {
|
|
1211
|
-
attributes: options.attributes,
|
|
1212
|
-
normalMapRequired: ! ! mesh.material.normalMap,
|
|
1213
|
-
} );
|
|
1214
|
-
trimToAttributes( geometry, options.attributes );
|
|
1156
|
+
class DynamicPathTracingSceneGenerator {
|
|
1215
1157
|
|
|
1216
|
-
|
|
1217
|
-
const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
|
|
1218
|
-
geometry.setAttribute( 'materialIndex', materialIndexAttribute );
|
|
1158
|
+
get initialized() {
|
|
1219
1159
|
|
|
1220
|
-
|
|
1160
|
+
return Boolean( this.bvh );
|
|
1221
1161
|
|
|
1222
1162
|
}
|
|
1223
1163
|
|
|
1224
|
-
|
|
1225
|
-
materials.forEach( material => {
|
|
1226
|
-
|
|
1227
|
-
for ( const key in material ) {
|
|
1164
|
+
constructor( objects ) {
|
|
1228
1165
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1166
|
+
// ensure the objects is an array
|
|
1167
|
+
if ( ! Array.isArray( objects ) ) {
|
|
1231
1168
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
}
|
|
1169
|
+
objects = [ objects ];
|
|
1235
1170
|
|
|
1236
1171
|
}
|
|
1237
1172
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
const textures = Array.from( textureSet );
|
|
1242
|
-
return { geometry, materials, textures };
|
|
1243
|
-
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
class PathTracingSceneGenerator {
|
|
1247
|
-
|
|
1248
|
-
prepScene( scene ) {
|
|
1173
|
+
// use a dummy object for a fallback
|
|
1174
|
+
const finalObjects = [ ...objects ];
|
|
1175
|
+
if ( finalObjects.length === 0 ) {
|
|
1249
1176
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
const meshes = [];
|
|
1253
|
-
const lights = [];
|
|
1254
|
-
|
|
1255
|
-
for ( let i = 0, l = scene.length; i < l; i ++ ) {
|
|
1256
|
-
|
|
1257
|
-
scene[ i ].traverseVisible( c => {
|
|
1258
|
-
|
|
1259
|
-
if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
|
|
1260
|
-
|
|
1261
|
-
const generator = new threeMeshBvh.StaticGeometryGenerator( c );
|
|
1262
|
-
generator.attributes = [ 'position', 'color', 'normal', 'tangent', 'uv', 'uv2' ];
|
|
1263
|
-
generator.applyWorldTransforms = false;
|
|
1264
|
-
const mesh = new three.Mesh(
|
|
1265
|
-
generator.generate(),
|
|
1266
|
-
c.material,
|
|
1267
|
-
);
|
|
1268
|
-
mesh.matrixWorld.copy( c.matrixWorld );
|
|
1269
|
-
mesh.matrix.copy( c.matrixWorld );
|
|
1270
|
-
mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
|
|
1271
|
-
meshes.push( mesh );
|
|
1272
|
-
|
|
1273
|
-
} else if ( c.isMesh ) {
|
|
1274
|
-
|
|
1275
|
-
meshes.push( c );
|
|
1276
|
-
|
|
1277
|
-
} else if (
|
|
1278
|
-
c.isRectAreaLight ||
|
|
1279
|
-
c.isSpotLight ||
|
|
1280
|
-
c.isPointLight ||
|
|
1281
|
-
c.isDirectionalLight
|
|
1282
|
-
) {
|
|
1283
|
-
|
|
1284
|
-
lights.push( c );
|
|
1285
|
-
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
} );
|
|
1177
|
+
finalObjects.push( getDummyMesh() );
|
|
1289
1178
|
|
|
1290
1179
|
}
|
|
1291
1180
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
} ),
|
|
1296
|
-
lights,
|
|
1297
|
-
};
|
|
1298
|
-
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
generate( scene, options = {} ) {
|
|
1302
|
-
|
|
1303
|
-
const { materials, textures, geometry, lights } = this.prepScene( scene );
|
|
1304
|
-
const bvhOptions = { strategy: threeMeshBvh.SAH, ...options, maxLeafTris: 1 };
|
|
1305
|
-
return {
|
|
1306
|
-
scene,
|
|
1307
|
-
materials,
|
|
1308
|
-
textures,
|
|
1309
|
-
lights,
|
|
1310
|
-
bvh: new threeMeshBvh.MeshBVH( geometry, bvhOptions ),
|
|
1311
|
-
};
|
|
1312
|
-
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
class DynamicPathTracingSceneGenerator {
|
|
1318
|
-
|
|
1319
|
-
get initialized() {
|
|
1320
|
-
|
|
1321
|
-
return Boolean( this.bvh );
|
|
1322
|
-
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
constructor( scene ) {
|
|
1181
|
+
// options
|
|
1182
|
+
this.bvhOptions = {};
|
|
1183
|
+
this.attributes = [ 'position', 'normal', 'tangent', 'color', 'uv', 'uv2' ];
|
|
1326
1184
|
|
|
1327
|
-
|
|
1185
|
+
// state
|
|
1186
|
+
this.objects = finalObjects;
|
|
1328
1187
|
this.bvh = null;
|
|
1329
1188
|
this.geometry = new three.BufferGeometry();
|
|
1330
1189
|
this.materials = null;
|
|
@@ -1348,64 +1207,75 @@
|
|
|
1348
1207
|
|
|
1349
1208
|
dispose() {}
|
|
1350
1209
|
|
|
1351
|
-
|
|
1210
|
+
prepScene() {
|
|
1352
1211
|
|
|
1353
|
-
|
|
1354
|
-
if ( this.bvh === null ) {
|
|
1212
|
+
if ( this.bvh !== null ) {
|
|
1355
1213
|
|
|
1356
|
-
|
|
1214
|
+
return;
|
|
1357
1215
|
|
|
1358
|
-
|
|
1216
|
+
}
|
|
1359
1217
|
|
|
1360
|
-
|
|
1218
|
+
const { objects, staticGeometryGenerator, geometry, lights, attributes } = this;
|
|
1219
|
+
for ( let i = 0, l = objects.length; i < l; i ++ ) {
|
|
1361
1220
|
|
|
1362
|
-
|
|
1221
|
+
objects[ i ].traverse( c => {
|
|
1363
1222
|
|
|
1364
|
-
|
|
1365
|
-
setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
|
|
1223
|
+
if ( c.isMesh ) {
|
|
1366
1224
|
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
c.isSpotLight ||
|
|
1370
|
-
c.isPointLight ||
|
|
1371
|
-
c.isDirectionalLight
|
|
1372
|
-
) {
|
|
1225
|
+
const normalMapRequired = ! ! c.material.normalMap;
|
|
1226
|
+
setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
|
|
1373
1227
|
|
|
1374
|
-
|
|
1228
|
+
} else if (
|
|
1229
|
+
c.isRectAreaLight ||
|
|
1230
|
+
c.isSpotLight ||
|
|
1231
|
+
c.isPointLight ||
|
|
1232
|
+
c.isDirectionalLight
|
|
1233
|
+
) {
|
|
1375
1234
|
|
|
1376
|
-
|
|
1235
|
+
lights.push( c );
|
|
1377
1236
|
|
|
1378
|
-
}
|
|
1237
|
+
}
|
|
1379
1238
|
|
|
1380
|
-
}
|
|
1239
|
+
} );
|
|
1381
1240
|
|
|
1382
|
-
|
|
1383
|
-
const materials = staticGeometryGenerator.getMaterials();
|
|
1384
|
-
materials.forEach( material => {
|
|
1241
|
+
}
|
|
1385
1242
|
|
|
1386
|
-
|
|
1243
|
+
const textureSet = new Set();
|
|
1244
|
+
const materials = staticGeometryGenerator.getMaterials();
|
|
1245
|
+
materials.forEach( material => {
|
|
1387
1246
|
|
|
1388
|
-
|
|
1389
|
-
if ( value && value.isTexture ) {
|
|
1247
|
+
for ( const key in material ) {
|
|
1390
1248
|
|
|
1391
|
-
|
|
1249
|
+
const value = material[ key ];
|
|
1250
|
+
if ( value && value.isTexture ) {
|
|
1392
1251
|
|
|
1393
|
-
|
|
1252
|
+
textureSet.add( value );
|
|
1394
1253
|
|
|
1395
1254
|
}
|
|
1396
1255
|
|
|
1397
|
-
}
|
|
1256
|
+
}
|
|
1398
1257
|
|
|
1399
|
-
|
|
1400
|
-
|
|
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 );
|
|
1269
|
+
|
|
1270
|
+
}
|
|
1401
1271
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1272
|
+
generate() {
|
|
1273
|
+
|
|
1274
|
+
const { objects, staticGeometryGenerator, geometry, bvhOptions } = this;
|
|
1275
|
+
if ( this.bvh === null ) {
|
|
1405
1276
|
|
|
1406
|
-
this.
|
|
1407
|
-
this.
|
|
1408
|
-
this.textures = Array.from( textureSet );
|
|
1277
|
+
this.prepScene();
|
|
1278
|
+
this.bvh = new threeMeshBvh.MeshBVH( geometry, { strategy: threeMeshBvh.SAH, maxLeafTris: 1, ...bvhOptions } );
|
|
1409
1279
|
|
|
1410
1280
|
return {
|
|
1411
1281
|
lights: this.lights,
|
|
@@ -1435,6 +1305,30 @@
|
|
|
1435
1305
|
|
|
1436
1306
|
}
|
|
1437
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
|
+
|
|
1438
1332
|
// https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
|
|
1439
1333
|
|
|
1440
1334
|
function isTypedArray( arr ) {
|
|
@@ -1799,8 +1693,8 @@
|
|
|
1799
1693
|
constructor( width = 512, height = 512 ) {
|
|
1800
1694
|
|
|
1801
1695
|
super(
|
|
1802
|
-
new
|
|
1803
|
-
width, height, three.RGBAFormat, three.
|
|
1696
|
+
new Float32Array( width * height * 4 ),
|
|
1697
|
+
width, height, three.RGBAFormat, three.FloatType, three.EquirectangularReflectionMapping,
|
|
1804
1698
|
three.RepeatWrapping, three.ClampToEdgeWrapping, three.LinearFilter, three.LinearFilter,
|
|
1805
1699
|
);
|
|
1806
1700
|
|
|
@@ -1832,10 +1726,10 @@
|
|
|
1832
1726
|
|
|
1833
1727
|
const i = y * width + x;
|
|
1834
1728
|
const i4 = 4 * i;
|
|
1835
|
-
data[ i4 + 0 ] =
|
|
1836
|
-
data[ i4 + 1 ] =
|
|
1837
|
-
data[ i4 + 2 ] =
|
|
1838
|
-
data[ i4 + 3 ] =
|
|
1729
|
+
data[ i4 + 0 ] = ( _color.r );
|
|
1730
|
+
data[ i4 + 1 ] = ( _color.g );
|
|
1731
|
+
data[ i4 + 2 ] = ( _color.b );
|
|
1732
|
+
data[ i4 + 3 ] = ( 1.0 );
|
|
1839
1733
|
|
|
1840
1734
|
}
|
|
1841
1735
|
|
|
@@ -1890,7 +1784,7 @@
|
|
|
1890
1784
|
// when rendering each texture to the texture array they must have a consistent color space.
|
|
1891
1785
|
function getTextureHash( t ) {
|
|
1892
1786
|
|
|
1893
|
-
return `${ t.source.uuid }:${ t.
|
|
1787
|
+
return `${ t.source.uuid }:${ t.colorSpace }`;
|
|
1894
1788
|
|
|
1895
1789
|
}
|
|
1896
1790
|
|
|
@@ -2102,7 +1996,7 @@
|
|
|
2102
1996
|
|
|
2103
1997
|
let index = 0;
|
|
2104
1998
|
const pixelCount = materials.length * MATERIAL_PIXELS;
|
|
2105
|
-
const dimension = Math.ceil( Math.sqrt( pixelCount ) );
|
|
1999
|
+
const dimension = Math.ceil( Math.sqrt( pixelCount ) ) || 1;
|
|
2106
2000
|
const { threeCompatibilityTransforms, image, features } = this;
|
|
2107
2001
|
|
|
2108
2002
|
// get the list of textures with unique sources
|
|
@@ -2440,7 +2334,7 @@
|
|
|
2440
2334
|
|
|
2441
2335
|
};
|
|
2442
2336
|
|
|
2443
|
-
const fsQuad = new Pass_js.FullScreenQuad( new
|
|
2337
|
+
const fsQuad = new Pass_js.FullScreenQuad( new CopyMaterial() );
|
|
2444
2338
|
this.fsQuad = fsQuad;
|
|
2445
2339
|
|
|
2446
2340
|
}
|
|
@@ -2475,7 +2369,6 @@
|
|
|
2475
2369
|
texture.matrix.identity();
|
|
2476
2370
|
|
|
2477
2371
|
fsQuad.material.map = texture;
|
|
2478
|
-
fsQuad.material.transparent = true;
|
|
2479
2372
|
|
|
2480
2373
|
renderer.setRenderTarget( this, i );
|
|
2481
2374
|
fsQuad.render( renderer );
|
|
@@ -2505,6 +2398,54 @@
|
|
|
2505
2398
|
|
|
2506
2399
|
}
|
|
2507
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
|
+
|
|
2508
2449
|
function toHalfFloatArray( f32Array ) {
|
|
2509
2450
|
|
|
2510
2451
|
const f16Array = new Uint16Array( f32Array.length );
|
|
@@ -2556,7 +2497,7 @@
|
|
|
2556
2497
|
}
|
|
2557
2498
|
|
|
2558
2499
|
// ensures the data is all floating point values and flipY is false
|
|
2559
|
-
function preprocessEnvMap( envMap ) {
|
|
2500
|
+
function preprocessEnvMap( envMap, targetType = three.HalfFloatType ) {
|
|
2560
2501
|
|
|
2561
2502
|
const map = envMap.clone();
|
|
2562
2503
|
map.source = new three.Source( { ...map.image } );
|
|
@@ -2565,17 +2506,54 @@
|
|
|
2565
2506
|
// TODO: is there a simple way to avoid cloning and adjusting the env map data here?
|
|
2566
2507
|
// convert the data from half float uint 16 arrays to float arrays for cdf computation
|
|
2567
2508
|
let newData = data;
|
|
2568
|
-
if ( map.type
|
|
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 ++ ) {
|
|
2533
|
+
|
|
2534
|
+
let v = data[ i ];
|
|
2535
|
+
if ( map.type === three.HalfFloatType ) {
|
|
2536
|
+
|
|
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 ) {
|
|
2569
2548
|
|
|
2570
|
-
|
|
2571
|
-
for ( const i in data ) {
|
|
2549
|
+
newData[ i ] = three.DataUtils.toHalfFloat( v );
|
|
2572
2550
|
|
|
2573
|
-
|
|
2551
|
+
}
|
|
2574
2552
|
|
|
2575
2553
|
}
|
|
2576
2554
|
|
|
2577
2555
|
map.image.data = newData;
|
|
2578
|
-
map.type =
|
|
2556
|
+
map.type = targetType;
|
|
2579
2557
|
|
|
2580
2558
|
}
|
|
2581
2559
|
|
|
@@ -2667,7 +2645,7 @@
|
|
|
2667
2645
|
// https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
|
|
2668
2646
|
const map = preprocessEnvMap( hdr );
|
|
2669
2647
|
map.wrapS = three.RepeatWrapping;
|
|
2670
|
-
map.wrapT = three.
|
|
2648
|
+
map.wrapT = three.ClampToEdgeWrapping;
|
|
2671
2649
|
|
|
2672
2650
|
const { width, height, data } = map.image;
|
|
2673
2651
|
|
|
@@ -2879,6 +2857,13 @@
|
|
|
2879
2857
|
const baseIndex = i * LIGHT_PIXELS * 4;
|
|
2880
2858
|
let index = 0;
|
|
2881
2859
|
|
|
2860
|
+
// initialize to 0
|
|
2861
|
+
for ( let p = 0; p < LIGHT_PIXELS * 4; p ++ ) {
|
|
2862
|
+
|
|
2863
|
+
floatArray[ baseIndex + p ] = 0;
|
|
2864
|
+
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2882
2867
|
// sample 1
|
|
2883
2868
|
// position
|
|
2884
2869
|
l.getWorldPosition( v );
|
|
@@ -2943,7 +2928,7 @@
|
|
|
2943
2928
|
|
|
2944
2929
|
} else if ( l.isSpotLight ) {
|
|
2945
2930
|
|
|
2946
|
-
const radius = l.radius;
|
|
2931
|
+
const radius = l.radius || 0;
|
|
2947
2932
|
eye.setFromMatrixPosition( l.matrixWorld );
|
|
2948
2933
|
target.setFromMatrixPosition( l.target.matrixWorld );
|
|
2949
2934
|
m.lookAt( eye, target, up );
|
|
@@ -2973,24 +2958,21 @@
|
|
|
2973
2958
|
// radius
|
|
2974
2959
|
floatArray[ baseIndex + ( index ++ ) ] = radius;
|
|
2975
2960
|
|
|
2976
|
-
// near
|
|
2977
|
-
floatArray[ baseIndex + ( index ++ ) ] = l.shadow.camera.near;
|
|
2978
|
-
|
|
2979
2961
|
// decay
|
|
2980
2962
|
floatArray[ baseIndex + ( index ++ ) ] = l.decay;
|
|
2981
2963
|
|
|
2982
2964
|
// distance
|
|
2983
2965
|
floatArray[ baseIndex + ( index ++ ) ] = l.distance;
|
|
2984
2966
|
|
|
2985
|
-
// sample 6
|
|
2986
2967
|
// coneCos
|
|
2987
2968
|
floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle );
|
|
2988
2969
|
|
|
2970
|
+
// sample 6
|
|
2989
2971
|
// penumbraCos
|
|
2990
2972
|
floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle * ( 1 - l.penumbra ) );
|
|
2991
2973
|
|
|
2992
2974
|
// iesProfile
|
|
2993
|
-
floatArray[ baseIndex + ( index ++ ) ] = iesTextures.indexOf( l.iesTexture );
|
|
2975
|
+
floatArray[ baseIndex + ( index ++ ) ] = l.iesTexture ? iesTextures.indexOf( l.iesTexture ) : - 1;
|
|
2994
2976
|
|
|
2995
2977
|
} else if ( l.isPointLight ) {
|
|
2996
2978
|
|
|
@@ -3007,7 +2989,7 @@
|
|
|
3007
2989
|
index += 4;
|
|
3008
2990
|
|
|
3009
2991
|
// sample 5
|
|
3010
|
-
index +=
|
|
2992
|
+
index += 1;
|
|
3011
2993
|
|
|
3012
2994
|
floatArray[ baseIndex + ( index ++ ) ] = l.decay;
|
|
3013
2995
|
floatArray[ baseIndex + ( index ++ ) ] = l.distance;
|
|
@@ -4421,18 +4403,16 @@
|
|
|
4421
4403
|
vec4 s4 = texelFetch1D( tex, i + 4u );
|
|
4422
4404
|
vec4 s5 = texelFetch1D( tex, i + 5u );
|
|
4423
4405
|
l.radius = s4.r;
|
|
4424
|
-
l.
|
|
4425
|
-
l.
|
|
4426
|
-
l.
|
|
4406
|
+
l.decay = s4.g;
|
|
4407
|
+
l.distance = s4.b;
|
|
4408
|
+
l.coneCos = s4.a;
|
|
4427
4409
|
|
|
4428
|
-
l.
|
|
4429
|
-
l.
|
|
4430
|
-
l.iesProfile = int( round( s5.b ) );
|
|
4410
|
+
l.penumbraCos = s5.r;
|
|
4411
|
+
l.iesProfile = int( round( s5.g ) );
|
|
4431
4412
|
|
|
4432
4413
|
} else {
|
|
4433
4414
|
|
|
4434
4415
|
l.radius = 0.0;
|
|
4435
|
-
l.near = 0.0;
|
|
4436
4416
|
l.decay = 0.0;
|
|
4437
4417
|
l.distance = 0.0;
|
|
4438
4418
|
|
|
@@ -5150,15 +5130,17 @@ bool bvhIntersectFogVolumeHit(
|
|
|
5150
5130
|
|
|
5151
5131
|
// TODO: subsurface approx?
|
|
5152
5132
|
|
|
5153
|
-
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 );
|
|
5154
5135
|
color = ( 1.0 - F ) * transFactor * metalFactor * wi.z * surf.color * ( retro + lambert ) / PI;
|
|
5136
|
+
|
|
5155
5137
|
return wi.z / PI;
|
|
5156
5138
|
|
|
5157
5139
|
}
|
|
5158
5140
|
|
|
5159
5141
|
vec3 diffuseDirection( vec3 wo, SurfaceRecord surf ) {
|
|
5160
5142
|
|
|
5161
|
-
vec3 lightDirection = sampleSphere(
|
|
5143
|
+
vec3 lightDirection = sampleSphere( rand2( 11 ) );
|
|
5162
5144
|
lightDirection.z += 1.0;
|
|
5163
5145
|
lightDirection = normalize( lightDirection );
|
|
5164
5146
|
|
|
@@ -5203,7 +5185,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
5203
5185
|
vec3 halfVector = ggxDirection(
|
|
5204
5186
|
wo,
|
|
5205
5187
|
vec2( roughness ),
|
|
5206
|
-
|
|
5188
|
+
rand2( 12 )
|
|
5207
5189
|
);
|
|
5208
5190
|
|
|
5209
5191
|
// apply to new ray by reflecting off the new normal
|
|
@@ -5240,7 +5222,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
5240
5222
|
vec3 halfVector = ggxDirection(
|
|
5241
5223
|
wo,
|
|
5242
5224
|
vec2( filteredRoughness ),
|
|
5243
|
-
|
|
5225
|
+
rand2( 13 )
|
|
5244
5226
|
);
|
|
5245
5227
|
|
|
5246
5228
|
vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
|
|
@@ -5262,7 +5244,8 @@ bool bvhIntersectFogVolumeHit(
|
|
|
5262
5244
|
color = surf.transmission * surf.color;
|
|
5263
5245
|
|
|
5264
5246
|
// PDF
|
|
5265
|
-
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 );
|
|
5266
5249
|
if ( F >= 1.0 ) {
|
|
5267
5250
|
|
|
5268
5251
|
return 0.0;
|
|
@@ -5277,7 +5260,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
5277
5260
|
|
|
5278
5261
|
float roughness = surf.filteredRoughness;
|
|
5279
5262
|
float eta = surf.eta;
|
|
5280
|
-
vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere(
|
|
5263
|
+
vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere( rand2( 13 ) ) * roughness );
|
|
5281
5264
|
vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
|
|
5282
5265
|
|
|
5283
5266
|
if ( surf.thinFilm ) {
|
|
@@ -5318,7 +5301,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
5318
5301
|
vec3 halfVector = ggxDirection(
|
|
5319
5302
|
wo,
|
|
5320
5303
|
vec2( roughness ),
|
|
5321
|
-
|
|
5304
|
+
rand2( 14 )
|
|
5322
5305
|
);
|
|
5323
5306
|
|
|
5324
5307
|
// apply to new ray by reflecting off the new normal
|
|
@@ -5353,7 +5336,8 @@ bool bvhIntersectFogVolumeHit(
|
|
|
5353
5336
|
|
|
5354
5337
|
float metalness = surf.metalness;
|
|
5355
5338
|
float transmission = surf.transmission;
|
|
5356
|
-
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 );
|
|
5357
5341
|
|
|
5358
5342
|
float transSpecularProb = mix( max( 0.25, fEstimate ), 1.0, metalness );
|
|
5359
5343
|
float diffSpecularProb = 0.5 + 0.5 * metalness;
|
|
@@ -5469,7 +5453,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
5469
5453
|
ScatterRecord sampleRec;
|
|
5470
5454
|
sampleRec.specularPdf = 0.0;
|
|
5471
5455
|
sampleRec.pdf = 1.0 / ( 4.0 * PI );
|
|
5472
|
-
sampleRec.direction = sampleSphere(
|
|
5456
|
+
sampleRec.direction = sampleSphere( rand2( 16 ) );
|
|
5473
5457
|
sampleRec.color = surf.color / ( 4.0 * PI );
|
|
5474
5458
|
return sampleRec;
|
|
5475
5459
|
|
|
@@ -5521,7 +5505,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
5521
5505
|
vec3 wi;
|
|
5522
5506
|
vec3 clearcoatWi;
|
|
5523
5507
|
|
|
5524
|
-
float r =
|
|
5508
|
+
float r = rand( 15 );
|
|
5525
5509
|
if ( r <= cdf[0] ) { // diffuse
|
|
5526
5510
|
|
|
5527
5511
|
wi = diffuseDirection( wo, surf );
|
|
@@ -5605,10 +5589,17 @@ bool bvhIntersectFogVolumeHit(
|
|
|
5605
5589
|
// samples the color given env map with CDF and returns the pdf of the direction
|
|
5606
5590
|
float sampleEquirect( vec3 direction, inout vec3 color ) {
|
|
5607
5591
|
|
|
5592
|
+
float totalSum = envMapInfo.totalSum;
|
|
5593
|
+
if ( totalSum == 0.0 ) {
|
|
5594
|
+
|
|
5595
|
+
color = vec3( 0.0 );
|
|
5596
|
+
return 1.0;
|
|
5597
|
+
|
|
5598
|
+
}
|
|
5599
|
+
|
|
5608
5600
|
vec2 uv = equirectDirectionToUv( direction );
|
|
5609
5601
|
color = texture2D( envMapInfo.map, uv ).rgb;
|
|
5610
5602
|
|
|
5611
|
-
float totalSum = envMapInfo.totalSum;
|
|
5612
5603
|
float lum = luminance( color );
|
|
5613
5604
|
ivec2 resolution = textureSize( envMapInfo.map, 0 );
|
|
5614
5605
|
float pdf = lum / totalSum;
|
|
@@ -6160,32 +6151,38 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6160
6151
|
|
|
6161
6152
|
}
|
|
6162
6153
|
|
|
6163
|
-
|
|
6154
|
+
// TODO: disney fresnel was removed and replaced with this fresnel function to better align with
|
|
6155
|
+
// the glTF but is causing blown out pixels. Should be revisited
|
|
6156
|
+
// float evaluateFresnelWeight( float cosTheta, float eta, float f0 ) {
|
|
6164
6157
|
|
|
6165
|
-
|
|
6158
|
+
// if ( totalInternalReflection( cosTheta, eta ) ) {
|
|
6166
6159
|
|
|
6167
|
-
|
|
6160
|
+
// return 1.0;
|
|
6168
6161
|
|
|
6169
|
-
|
|
6162
|
+
// }
|
|
6170
6163
|
|
|
6171
|
-
|
|
6164
|
+
// return schlickFresnel( cosTheta, f0 );
|
|
6172
6165
|
|
|
6173
|
-
}
|
|
6166
|
+
// }
|
|
6174
6167
|
|
|
6175
|
-
/*
|
|
6176
6168
|
// https://schuttejoe.github.io/post/disneybsdf/
|
|
6177
6169
|
float disneyFresnel( vec3 wo, vec3 wi, vec3 wh, float f0, float eta, float metalness ) {
|
|
6178
6170
|
|
|
6179
6171
|
float dotHV = dot( wo, wh );
|
|
6180
|
-
|
|
6172
|
+
if ( totalInternalReflection( dotHV, eta ) ) {
|
|
6173
|
+
|
|
6174
|
+
return 1.0;
|
|
6181
6175
|
|
|
6176
|
+
}
|
|
6177
|
+
|
|
6178
|
+
float dotHL = dot( wi, wh );
|
|
6182
6179
|
float dielectricFresnel = dielectricFresnel( abs( dotHV ), eta );
|
|
6183
6180
|
float metallicFresnel = schlickFresnel( dotHL, f0 );
|
|
6184
6181
|
|
|
6185
6182
|
return mix( dielectricFresnel, metallicFresnel, metalness );
|
|
6186
6183
|
|
|
6187
6184
|
}
|
|
6188
|
-
|
|
6185
|
+
|
|
6189
6186
|
`;
|
|
6190
6187
|
|
|
6191
6188
|
const arraySamplerTexelFetchGLSL = /*glsl */`
|
|
@@ -6242,28 +6239,28 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6242
6239
|
}
|
|
6243
6240
|
|
|
6244
6241
|
// returns [ 0, 1 ]
|
|
6245
|
-
float
|
|
6242
|
+
float pcgRand() {
|
|
6246
6243
|
|
|
6247
6244
|
pcg4d( WHITE_NOISE_SEED );
|
|
6248
6245
|
return float( WHITE_NOISE_SEED.x ) / float( 0xffffffffu );
|
|
6249
6246
|
|
|
6250
6247
|
}
|
|
6251
6248
|
|
|
6252
|
-
vec2
|
|
6249
|
+
vec2 pcgRand2() {
|
|
6253
6250
|
|
|
6254
6251
|
pcg4d( WHITE_NOISE_SEED );
|
|
6255
6252
|
return vec2( WHITE_NOISE_SEED.xy ) / float(0xffffffffu);
|
|
6256
6253
|
|
|
6257
6254
|
}
|
|
6258
6255
|
|
|
6259
|
-
vec3
|
|
6256
|
+
vec3 pcgRand3() {
|
|
6260
6257
|
|
|
6261
6258
|
pcg4d( WHITE_NOISE_SEED );
|
|
6262
6259
|
return vec3( WHITE_NOISE_SEED.xyz ) / float( 0xffffffffu );
|
|
6263
6260
|
|
|
6264
6261
|
}
|
|
6265
6262
|
|
|
6266
|
-
vec4
|
|
6263
|
+
vec4 pcgRand4() {
|
|
6267
6264
|
|
|
6268
6265
|
pcg4d( WHITE_NOISE_SEED );
|
|
6269
6266
|
return vec4( WHITE_NOISE_SEED ) / float( 0xffffffffu );
|
|
@@ -6336,7 +6333,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6336
6333
|
|
|
6337
6334
|
// Jitter the camera ray by finding a uv coordinate at a random sample
|
|
6338
6335
|
// around this pixel's UV coordinate for AA
|
|
6339
|
-
vec2 ruv =
|
|
6336
|
+
vec2 ruv = rand2( 0 );
|
|
6340
6337
|
vec2 jitteredUv = vUv + vec2( tentFilter( ruv.x ) * ssd.x, tentFilter( ruv.y ) * ssd.y );
|
|
6341
6338
|
Ray ray;
|
|
6342
6339
|
|
|
@@ -6381,7 +6378,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6381
6378
|
|
|
6382
6379
|
// get the aperture sample
|
|
6383
6380
|
// if blades === 0 then we assume a circle
|
|
6384
|
-
vec3 shapeUVW=
|
|
6381
|
+
vec3 shapeUVW= rand3( 1 );
|
|
6385
6382
|
int blades = physicalCamera.apertureBlades;
|
|
6386
6383
|
float anamorphicRatio = physicalCamera.anamorphicRatio;
|
|
6387
6384
|
vec2 apertureSample = blades == 0 ? sampleCircle( shapeUVW.xy ) : sampleRegularPolygon( blades, shapeUVW );
|
|
@@ -6417,6 +6414,9 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6417
6414
|
out vec3 color
|
|
6418
6415
|
) {
|
|
6419
6416
|
|
|
6417
|
+
// store the original bounce index so we can reset it after
|
|
6418
|
+
uint originalBounceIndex = sobolBounceIndex;
|
|
6419
|
+
|
|
6420
6420
|
int traversals = state.traversals;
|
|
6421
6421
|
int transmissiveTraversals = state.transmissiveTraversals;
|
|
6422
6422
|
bool isShadowRay = state.isShadowRay;
|
|
@@ -6429,22 +6429,25 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6429
6429
|
|
|
6430
6430
|
color = vec3( 1.0 );
|
|
6431
6431
|
|
|
6432
|
-
|
|
6433
|
-
// and then reset.
|
|
6432
|
+
bool result = true;
|
|
6434
6433
|
for ( int i = 0; i < traversals; i ++ ) {
|
|
6435
6434
|
|
|
6435
|
+
sobolBounceIndex ++;
|
|
6436
|
+
|
|
6436
6437
|
int hitType = traceScene( ray, fogMaterial, surfaceHit );
|
|
6437
6438
|
|
|
6438
6439
|
if ( hitType == FOG_HIT ) {
|
|
6439
6440
|
|
|
6440
|
-
|
|
6441
|
+
result = true;
|
|
6442
|
+
break;
|
|
6441
6443
|
|
|
6442
6444
|
} else if ( hitType == SURFACE_HIT ) {
|
|
6443
6445
|
|
|
6444
6446
|
float totalDist = distance( startPoint, ray.origin + ray.direction * surfaceHit.dist );
|
|
6445
6447
|
if ( totalDist > rayDist ) {
|
|
6446
6448
|
|
|
6447
|
-
|
|
6449
|
+
result = false;
|
|
6450
|
+
break;
|
|
6448
6451
|
|
|
6449
6452
|
}
|
|
6450
6453
|
|
|
@@ -6526,7 +6529,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6526
6529
|
bool useAlphaTest = alphaTest != 0.0;
|
|
6527
6530
|
float transmissionFactor = ( 1.0 - metalness ) * transmission;
|
|
6528
6531
|
if (
|
|
6529
|
-
transmissionFactor < rand() && ! (
|
|
6532
|
+
transmissionFactor < rand( 9 ) && ! (
|
|
6530
6533
|
// material sidedness
|
|
6531
6534
|
material.side != 0.0 && surfaceHit.side == material.side
|
|
6532
6535
|
|
|
@@ -6534,11 +6537,12 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6534
6537
|
|| useAlphaTest && albedo.a < alphaTest
|
|
6535
6538
|
|
|
6536
6539
|
// opacity
|
|
6537
|
-
|| material.transparent && ! useAlphaTest && albedo.a < rand()
|
|
6540
|
+
|| material.transparent && ! useAlphaTest && albedo.a < rand( 10 )
|
|
6538
6541
|
)
|
|
6539
6542
|
) {
|
|
6540
6543
|
|
|
6541
|
-
|
|
6544
|
+
result = true;
|
|
6545
|
+
break;
|
|
6542
6546
|
|
|
6543
6547
|
}
|
|
6544
6548
|
|
|
@@ -6564,13 +6568,16 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6564
6568
|
|
|
6565
6569
|
} else {
|
|
6566
6570
|
|
|
6567
|
-
|
|
6571
|
+
result = false;
|
|
6572
|
+
break;
|
|
6568
6573
|
|
|
6569
6574
|
}
|
|
6570
6575
|
|
|
6571
6576
|
}
|
|
6572
6577
|
|
|
6573
|
-
|
|
6578
|
+
// reset the bounce index
|
|
6579
|
+
sobolBounceIndex = originalBounceIndex;
|
|
6580
|
+
return result;
|
|
6574
6581
|
|
|
6575
6582
|
}
|
|
6576
6583
|
|
|
@@ -6601,7 +6608,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6601
6608
|
|
|
6602
6609
|
// offset the distance so we don't run into issues with particles on the same surface
|
|
6603
6610
|
// as other objects
|
|
6604
|
-
float particleDist = intersectFogVolume( fogMaterial,
|
|
6611
|
+
float particleDist = intersectFogVolume( fogMaterial, rand( 1 ) );
|
|
6605
6612
|
if ( particleDist + RAY_OFFSET < surfaceHit.dist ) {
|
|
6606
6613
|
|
|
6607
6614
|
surfaceHit.side = 1.0;
|
|
@@ -6664,6 +6671,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6664
6671
|
|
|
6665
6672
|
vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
|
|
6666
6673
|
albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
|
|
6674
|
+
|
|
6667
6675
|
}
|
|
6668
6676
|
|
|
6669
6677
|
if ( material.vertexColors ) {
|
|
@@ -6695,7 +6703,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6695
6703
|
|| useAlphaTest && albedo.a < alphaTest
|
|
6696
6704
|
|
|
6697
6705
|
// opacity
|
|
6698
|
-
|| material.transparent && ! useAlphaTest && albedo.a <
|
|
6706
|
+
|| material.transparent && ! useAlphaTest && albedo.a < rand( 3 )
|
|
6699
6707
|
) {
|
|
6700
6708
|
|
|
6701
6709
|
return SKIP_SURFACE;
|
|
@@ -6957,10 +6965,10 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6957
6965
|
vec3 result = vec3( 0.0 );
|
|
6958
6966
|
|
|
6959
6967
|
// uniformly pick a light or environment map
|
|
6960
|
-
if( lightsDenom != 0.0 &&
|
|
6968
|
+
if( lightsDenom != 0.0 && rand( 5 ) < float( lights.count ) / lightsDenom ) {
|
|
6961
6969
|
|
|
6962
6970
|
// sample a light or environment
|
|
6963
|
-
LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin,
|
|
6971
|
+
LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, rand3( 6 ) );
|
|
6964
6972
|
|
|
6965
6973
|
bool isSampleBelowSurface = ! surf.volumeParticle && dot( surf.faceNormal, lightRec.direction ) < 0.0;
|
|
6966
6974
|
if ( isSampleBelowSurface ) {
|
|
@@ -6995,11 +7003,11 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6995
7003
|
|
|
6996
7004
|
}
|
|
6997
7005
|
|
|
6998
|
-
} else {
|
|
7006
|
+
} else if ( envMapInfo.totalSum != 0.0 && environmentIntensity != 0.0 ) {
|
|
6999
7007
|
|
|
7000
7008
|
// find a sample in the environment map to include in the contribution
|
|
7001
7009
|
vec3 envColor, envDirection;
|
|
7002
|
-
float envPdf = sampleEquirectProbability(
|
|
7010
|
+
float envPdf = sampleEquirectProbability( rand2( 7 ), envColor, envDirection );
|
|
7003
7011
|
envDirection = invEnvRotation3x3 * envDirection;
|
|
7004
7012
|
|
|
7005
7013
|
// this env sampling is not set up for transmissive sampling and yields overly bright
|
|
@@ -7048,6 +7056,667 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7048
7056
|
|
|
7049
7057
|
`;
|
|
7050
7058
|
|
|
7059
|
+
const stratifiedTextureGLSL = /* glsl */`
|
|
7060
|
+
|
|
7061
|
+
uniform sampler2D stratifiedTexture;
|
|
7062
|
+
uniform sampler2D stratifiedOffsetTexture;
|
|
7063
|
+
|
|
7064
|
+
uint sobolPixelIndex = 0u;
|
|
7065
|
+
uint sobolPathIndex = 0u;
|
|
7066
|
+
uint sobolBounceIndex = 0u;
|
|
7067
|
+
vec4 pixelSeed = vec4( 0 );
|
|
7068
|
+
|
|
7069
|
+
vec4 rand4( int v ) {
|
|
7070
|
+
|
|
7071
|
+
ivec2 uv = ivec2( v, sobolBounceIndex );
|
|
7072
|
+
vec4 stratifiedSample = texelFetch( stratifiedTexture, uv, 0 );
|
|
7073
|
+
return fract( stratifiedSample + pixelSeed.r ); // blue noise + stratified samples
|
|
7074
|
+
|
|
7075
|
+
}
|
|
7076
|
+
|
|
7077
|
+
vec3 rand3( int v ) {
|
|
7078
|
+
|
|
7079
|
+
return rand4( v ).xyz;
|
|
7080
|
+
|
|
7081
|
+
}
|
|
7082
|
+
|
|
7083
|
+
vec2 rand2( int v ) {
|
|
7084
|
+
|
|
7085
|
+
return rand4( v ).xy;
|
|
7086
|
+
|
|
7087
|
+
}
|
|
7088
|
+
|
|
7089
|
+
float rand( int v ) {
|
|
7090
|
+
|
|
7091
|
+
return rand4( v ).x;
|
|
7092
|
+
|
|
7093
|
+
}
|
|
7094
|
+
|
|
7095
|
+
void rng_initialize( vec2 screenCoord, int frame ) {
|
|
7096
|
+
|
|
7097
|
+
// tile the small noise texture across the entire screen
|
|
7098
|
+
ivec2 noiseSize = ivec2( textureSize( stratifiedOffsetTexture, 0 ) );
|
|
7099
|
+
pixelSeed = texelFetch( stratifiedOffsetTexture, ivec2( screenCoord.xy ) % noiseSize, 0 );
|
|
7100
|
+
|
|
7101
|
+
}
|
|
7102
|
+
|
|
7103
|
+
`;
|
|
7104
|
+
|
|
7105
|
+
// Stratified Sampling based on implementation from hoverinc pathtracer
|
|
7106
|
+
// - https://github.com/hoverinc/ray-tracing-renderer
|
|
7107
|
+
// - http://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Stratified_Sampling.html
|
|
7108
|
+
|
|
7109
|
+
function shuffle( arr ) {
|
|
7110
|
+
|
|
7111
|
+
for ( let i = arr.length - 1; i > 0; i -- ) {
|
|
7112
|
+
|
|
7113
|
+
const j = Math.floor( Math.random() * ( i + 1 ) );
|
|
7114
|
+
const x = arr[ i ];
|
|
7115
|
+
arr[ i ] = arr[ j ];
|
|
7116
|
+
arr[ j ] = x;
|
|
7117
|
+
|
|
7118
|
+
}
|
|
7119
|
+
|
|
7120
|
+
return arr;
|
|
7121
|
+
|
|
7122
|
+
}
|
|
7123
|
+
|
|
7124
|
+
// strataCount : The number of bins per dimension
|
|
7125
|
+
// dimensions : The number of dimensions to generate stratified values for
|
|
7126
|
+
class StratifiedSampler {
|
|
7127
|
+
|
|
7128
|
+
constructor( strataCount, dimensions ) {
|
|
7129
|
+
|
|
7130
|
+
const l = strataCount ** dimensions;
|
|
7131
|
+
const strata = new Uint16Array( l );
|
|
7132
|
+
let index = l;
|
|
7133
|
+
|
|
7134
|
+
// each integer represents a statum bin
|
|
7135
|
+
for ( let i = 0; i < l; i ++ ) {
|
|
7136
|
+
|
|
7137
|
+
strata[ i ] = i;
|
|
7138
|
+
|
|
7139
|
+
}
|
|
7140
|
+
|
|
7141
|
+
this.samples = new Float32Array( dimensions );
|
|
7142
|
+
|
|
7143
|
+
this.strataCount = strataCount;
|
|
7144
|
+
|
|
7145
|
+
this.restart = function () {
|
|
7146
|
+
|
|
7147
|
+
index = 0;
|
|
7148
|
+
|
|
7149
|
+
};
|
|
7150
|
+
|
|
7151
|
+
this.next = function () {
|
|
7152
|
+
|
|
7153
|
+
const { samples } = this;
|
|
7154
|
+
|
|
7155
|
+
if ( index >= strata.length ) {
|
|
7156
|
+
|
|
7157
|
+
shuffle( strata );
|
|
7158
|
+
this.restart();
|
|
7159
|
+
|
|
7160
|
+
}
|
|
7161
|
+
|
|
7162
|
+
let stratum = strata[ index ++ ];
|
|
7163
|
+
|
|
7164
|
+
for ( let i = 0; i < dimensions; i ++ ) {
|
|
7165
|
+
|
|
7166
|
+
samples[ i ] = ( stratum % strataCount + Math.random() ) / strataCount;
|
|
7167
|
+
stratum = Math.floor( stratum / strataCount );
|
|
7168
|
+
|
|
7169
|
+
}
|
|
7170
|
+
|
|
7171
|
+
return samples;
|
|
7172
|
+
|
|
7173
|
+
};
|
|
7174
|
+
|
|
7175
|
+
}
|
|
7176
|
+
|
|
7177
|
+
}
|
|
7178
|
+
|
|
7179
|
+
// Stratified Sampling based on implementation from hoverinc pathtracer
|
|
7180
|
+
|
|
7181
|
+
// Stratified set of data with each tuple stratified separately and combined
|
|
7182
|
+
class StratifiedSamplerCombined {
|
|
7183
|
+
|
|
7184
|
+
constructor( strataCount, listOfDimensions ) {
|
|
7185
|
+
|
|
7186
|
+
let totalDim = 0;
|
|
7187
|
+
for ( const dim of listOfDimensions ) {
|
|
7188
|
+
|
|
7189
|
+
totalDim += dim;
|
|
7190
|
+
|
|
7191
|
+
}
|
|
7192
|
+
|
|
7193
|
+
const combined = new Float32Array( totalDim );
|
|
7194
|
+
const strataObjs = [];
|
|
7195
|
+
let offset = 0;
|
|
7196
|
+
for ( const dim of listOfDimensions ) {
|
|
7197
|
+
|
|
7198
|
+
const sampler = new StratifiedSampler( strataCount, dim );
|
|
7199
|
+
sampler.samples = new Float32Array( combined.buffer, offset, sampler.samples.length );
|
|
7200
|
+
offset += sampler.samples.length * 4;
|
|
7201
|
+
strataObjs.push( sampler );
|
|
7202
|
+
|
|
7203
|
+
}
|
|
7204
|
+
|
|
7205
|
+
this.samples = combined;
|
|
7206
|
+
|
|
7207
|
+
this.strataCount = strataCount;
|
|
7208
|
+
|
|
7209
|
+
this.next = function () {
|
|
7210
|
+
|
|
7211
|
+
for ( const strata of strataObjs ) {
|
|
7212
|
+
|
|
7213
|
+
strata.next();
|
|
7214
|
+
|
|
7215
|
+
}
|
|
7216
|
+
|
|
7217
|
+
return combined;
|
|
7218
|
+
|
|
7219
|
+
};
|
|
7220
|
+
|
|
7221
|
+
this.restart = function () {
|
|
7222
|
+
|
|
7223
|
+
for ( const strata of strataObjs ) {
|
|
7224
|
+
|
|
7225
|
+
strata.restart();
|
|
7226
|
+
|
|
7227
|
+
}
|
|
7228
|
+
|
|
7229
|
+
};
|
|
7230
|
+
|
|
7231
|
+
}
|
|
7232
|
+
|
|
7233
|
+
}
|
|
7234
|
+
|
|
7235
|
+
class StratifiedSamplesTexture extends three.DataTexture {
|
|
7236
|
+
|
|
7237
|
+
constructor( count = 1, depth = 1, strata = 8 ) {
|
|
7238
|
+
|
|
7239
|
+
super( new Float32Array( 1 ), 1, 1, three.RGBAFormat, three.FloatType );
|
|
7240
|
+
this.minFilter = three.NearestFilter;
|
|
7241
|
+
this.magFilter = three.NearestFilter;
|
|
7242
|
+
|
|
7243
|
+
this.strata = strata;
|
|
7244
|
+
this.sampler = null;
|
|
7245
|
+
|
|
7246
|
+
this.init( count, depth, strata );
|
|
7247
|
+
|
|
7248
|
+
}
|
|
7249
|
+
|
|
7250
|
+
init( count, depth, strata = this.strata ) {
|
|
7251
|
+
|
|
7252
|
+
const { image } = this;
|
|
7253
|
+
if ( image.width === depth && image.height === count ) {
|
|
7254
|
+
|
|
7255
|
+
return;
|
|
7256
|
+
|
|
7257
|
+
}
|
|
7258
|
+
|
|
7259
|
+
const dimensions = new Array( count * depth ).fill( 4 );
|
|
7260
|
+
const sampler = new StratifiedSamplerCombined( strata, dimensions );
|
|
7261
|
+
|
|
7262
|
+
image.width = depth;
|
|
7263
|
+
image.height = count;
|
|
7264
|
+
image.data = sampler.samples;
|
|
7265
|
+
|
|
7266
|
+
this.sampler = sampler;
|
|
7267
|
+
|
|
7268
|
+
this.dispose();
|
|
7269
|
+
this.next();
|
|
7270
|
+
|
|
7271
|
+
}
|
|
7272
|
+
|
|
7273
|
+
next() {
|
|
7274
|
+
|
|
7275
|
+
this.sampler.next();
|
|
7276
|
+
this.needsUpdate = true;
|
|
7277
|
+
|
|
7278
|
+
}
|
|
7279
|
+
|
|
7280
|
+
}
|
|
7281
|
+
|
|
7282
|
+
function shuffleArray( array, random = Math.random ) {
|
|
7283
|
+
|
|
7284
|
+
for ( let i = array.length - 1; i > 0; i -- ) {
|
|
7285
|
+
|
|
7286
|
+
const replaceIndex = ~ ~ ( ( random() - 1e-6 ) * i );
|
|
7287
|
+
const tmp = array[ i ];
|
|
7288
|
+
array[ i ] = array[ replaceIndex ];
|
|
7289
|
+
array[ replaceIndex ] = tmp;
|
|
7290
|
+
|
|
7291
|
+
}
|
|
7292
|
+
|
|
7293
|
+
}
|
|
7294
|
+
|
|
7295
|
+
function fillWithOnes( array, count ) {
|
|
7296
|
+
|
|
7297
|
+
array.fill( 0 );
|
|
7298
|
+
|
|
7299
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
7300
|
+
|
|
7301
|
+
array[ i ] = 1;
|
|
7302
|
+
|
|
7303
|
+
}
|
|
7304
|
+
|
|
7305
|
+
}
|
|
7306
|
+
|
|
7307
|
+
class BlueNoiseSamples {
|
|
7308
|
+
|
|
7309
|
+
constructor( size ) {
|
|
7310
|
+
|
|
7311
|
+
this.count = 0;
|
|
7312
|
+
this.size = - 1;
|
|
7313
|
+
this.sigma = - 1;
|
|
7314
|
+
this.radius = - 1;
|
|
7315
|
+
this.lookupTable = null;
|
|
7316
|
+
this.score = null;
|
|
7317
|
+
this.binaryPattern = null;
|
|
7318
|
+
|
|
7319
|
+
this.resize( size );
|
|
7320
|
+
this.setSigma( 1.5 );
|
|
7321
|
+
|
|
7322
|
+
}
|
|
7323
|
+
|
|
7324
|
+
findVoid() {
|
|
7325
|
+
|
|
7326
|
+
const { score, binaryPattern } = this;
|
|
7327
|
+
|
|
7328
|
+
let currValue = Infinity;
|
|
7329
|
+
let currIndex = - 1;
|
|
7330
|
+
for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
|
|
7331
|
+
|
|
7332
|
+
if ( binaryPattern[ i ] !== 0 ) {
|
|
7333
|
+
|
|
7334
|
+
continue;
|
|
7335
|
+
|
|
7336
|
+
}
|
|
7337
|
+
|
|
7338
|
+
const pScore = score[ i ];
|
|
7339
|
+
if ( pScore < currValue ) {
|
|
7340
|
+
|
|
7341
|
+
currValue = pScore;
|
|
7342
|
+
currIndex = i;
|
|
7343
|
+
|
|
7344
|
+
}
|
|
7345
|
+
|
|
7346
|
+
}
|
|
7347
|
+
|
|
7348
|
+
return currIndex;
|
|
7349
|
+
|
|
7350
|
+
}
|
|
7351
|
+
|
|
7352
|
+
findCluster() {
|
|
7353
|
+
|
|
7354
|
+
const { score, binaryPattern } = this;
|
|
7355
|
+
|
|
7356
|
+
let currValue = - Infinity;
|
|
7357
|
+
let currIndex = - 1;
|
|
7358
|
+
for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
|
|
7359
|
+
|
|
7360
|
+
if ( binaryPattern[ i ] !== 1 ) {
|
|
7361
|
+
|
|
7362
|
+
continue;
|
|
7363
|
+
|
|
7364
|
+
}
|
|
7365
|
+
|
|
7366
|
+
const pScore = score[ i ];
|
|
7367
|
+
if ( pScore > currValue ) {
|
|
7368
|
+
|
|
7369
|
+
currValue = pScore;
|
|
7370
|
+
currIndex = i;
|
|
7371
|
+
|
|
7372
|
+
}
|
|
7373
|
+
|
|
7374
|
+
}
|
|
7375
|
+
|
|
7376
|
+
return currIndex;
|
|
7377
|
+
|
|
7378
|
+
}
|
|
7379
|
+
|
|
7380
|
+
setSigma( sigma ) {
|
|
7381
|
+
|
|
7382
|
+
if ( sigma === this.sigma ) {
|
|
7383
|
+
|
|
7384
|
+
return;
|
|
7385
|
+
|
|
7386
|
+
}
|
|
7387
|
+
|
|
7388
|
+
// generate a radius in which the score will be updated under the
|
|
7389
|
+
// assumption that e^-10 is insignificant enough to be the border at
|
|
7390
|
+
// which we drop off.
|
|
7391
|
+
const radius = ~ ~ ( Math.sqrt( 10 * 2 * ( sigma ** 2 ) ) + 1 );
|
|
7392
|
+
const lookupWidth = 2 * radius + 1;
|
|
7393
|
+
const lookupTable = new Float32Array( lookupWidth * lookupWidth );
|
|
7394
|
+
const sigma2 = sigma * sigma;
|
|
7395
|
+
for ( let x = - radius; x <= radius; x ++ ) {
|
|
7396
|
+
|
|
7397
|
+
for ( let y = - radius; y <= radius; y ++ ) {
|
|
7398
|
+
|
|
7399
|
+
const index = ( radius + y ) * lookupWidth + x + radius;
|
|
7400
|
+
const dist2 = x * x + y * y;
|
|
7401
|
+
lookupTable[ index ] = Math.E ** ( - dist2 / ( 2 * sigma2 ) );
|
|
7402
|
+
|
|
7403
|
+
}
|
|
7404
|
+
|
|
7405
|
+
}
|
|
7406
|
+
|
|
7407
|
+
this.lookupTable = lookupTable;
|
|
7408
|
+
this.sigma = sigma;
|
|
7409
|
+
this.radius = radius;
|
|
7410
|
+
|
|
7411
|
+
}
|
|
7412
|
+
|
|
7413
|
+
resize( size ) {
|
|
7414
|
+
|
|
7415
|
+
if ( this.size !== size ) {
|
|
7416
|
+
|
|
7417
|
+
this.size = size;
|
|
7418
|
+
this.score = new Float32Array( size * size );
|
|
7419
|
+
this.binaryPattern = new Uint8Array( size * size );
|
|
7420
|
+
|
|
7421
|
+
}
|
|
7422
|
+
|
|
7423
|
+
|
|
7424
|
+
}
|
|
7425
|
+
|
|
7426
|
+
invert() {
|
|
7427
|
+
|
|
7428
|
+
const { binaryPattern, score, size } = this;
|
|
7429
|
+
|
|
7430
|
+
score.fill( 0 );
|
|
7431
|
+
|
|
7432
|
+
for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
|
|
7433
|
+
|
|
7434
|
+
if ( binaryPattern[ i ] === 0 ) {
|
|
7435
|
+
|
|
7436
|
+
const y = ~ ~ ( i / size );
|
|
7437
|
+
const x = i - y * size;
|
|
7438
|
+
this.updateScore( x, y, 1 );
|
|
7439
|
+
binaryPattern[ i ] = 1;
|
|
7440
|
+
|
|
7441
|
+
} else {
|
|
7442
|
+
|
|
7443
|
+
binaryPattern[ i ] = 0;
|
|
7444
|
+
|
|
7445
|
+
}
|
|
7446
|
+
|
|
7447
|
+
}
|
|
7448
|
+
|
|
7449
|
+
}
|
|
7450
|
+
|
|
7451
|
+
updateScore( x, y, multiplier ) {
|
|
7452
|
+
|
|
7453
|
+
// TODO: Is there a way to keep track of the highest and lowest scores here to avoid have to search over
|
|
7454
|
+
// everything in the buffer?
|
|
7455
|
+
const { size, score, lookupTable } = this;
|
|
7456
|
+
|
|
7457
|
+
// const sigma2 = sigma * sigma;
|
|
7458
|
+
// const radius = Math.floor( size / 2 );
|
|
7459
|
+
const radius = this.radius;
|
|
7460
|
+
const lookupWidth = 2 * radius + 1;
|
|
7461
|
+
for ( let px = - radius; px <= radius; px ++ ) {
|
|
7462
|
+
|
|
7463
|
+
for ( let py = - radius; py <= radius; py ++ ) {
|
|
7464
|
+
|
|
7465
|
+
// const dist2 = px * px + py * py;
|
|
7466
|
+
// const value = Math.E ** ( - dist2 / ( 2 * sigma2 ) );
|
|
7467
|
+
|
|
7468
|
+
const lookupIndex = ( radius + py ) * lookupWidth + px + radius;
|
|
7469
|
+
const value = lookupTable[ lookupIndex ];
|
|
7470
|
+
|
|
7471
|
+
let sx = ( x + px );
|
|
7472
|
+
sx = sx < 0 ? size + sx : sx % size;
|
|
7473
|
+
|
|
7474
|
+
let sy = ( y + py );
|
|
7475
|
+
sy = sy < 0 ? size + sy : sy % size;
|
|
7476
|
+
|
|
7477
|
+
const sindex = sy * size + sx;
|
|
7478
|
+
score[ sindex ] += multiplier * value;
|
|
7479
|
+
|
|
7480
|
+
}
|
|
7481
|
+
|
|
7482
|
+
}
|
|
7483
|
+
|
|
7484
|
+
}
|
|
7485
|
+
|
|
7486
|
+
addPointIndex( index ) {
|
|
7487
|
+
|
|
7488
|
+
this.binaryPattern[ index ] = 1;
|
|
7489
|
+
|
|
7490
|
+
const size = this.size;
|
|
7491
|
+
const y = ~ ~ ( index / size );
|
|
7492
|
+
const x = index - y * size;
|
|
7493
|
+
this.updateScore( x, y, 1 );
|
|
7494
|
+
this.count ++;
|
|
7495
|
+
|
|
7496
|
+
}
|
|
7497
|
+
|
|
7498
|
+
removePointIndex( index ) {
|
|
7499
|
+
|
|
7500
|
+
this.binaryPattern[ index ] = 0;
|
|
7501
|
+
|
|
7502
|
+
const size = this.size;
|
|
7503
|
+
const y = ~ ~ ( index / size );
|
|
7504
|
+
const x = index - y * size;
|
|
7505
|
+
this.updateScore( x, y, - 1 );
|
|
7506
|
+
this.count --;
|
|
7507
|
+
|
|
7508
|
+
}
|
|
7509
|
+
|
|
7510
|
+
copy( source ) {
|
|
7511
|
+
|
|
7512
|
+
this.resize( source.size );
|
|
7513
|
+
this.score.set( source.score );
|
|
7514
|
+
this.binaryPattern.set( source.binaryPattern );
|
|
7515
|
+
this.setSigma( source.sigma );
|
|
7516
|
+
this.count = source.count;
|
|
7517
|
+
|
|
7518
|
+
}
|
|
7519
|
+
|
|
7520
|
+
}
|
|
7521
|
+
|
|
7522
|
+
class BlueNoiseGenerator {
|
|
7523
|
+
|
|
7524
|
+
constructor() {
|
|
7525
|
+
|
|
7526
|
+
this.random = Math.random;
|
|
7527
|
+
this.sigma = 1.5;
|
|
7528
|
+
this.size = 64;
|
|
7529
|
+
this.majorityPointsRatio = 0.1;
|
|
7530
|
+
|
|
7531
|
+
this.samples = new BlueNoiseSamples( 1 );
|
|
7532
|
+
this.savedSamples = new BlueNoiseSamples( 1 );
|
|
7533
|
+
|
|
7534
|
+
}
|
|
7535
|
+
|
|
7536
|
+
generate() {
|
|
7537
|
+
|
|
7538
|
+
// http://cv.ulichney.com/papers/1993-void-cluster.pdf
|
|
7539
|
+
|
|
7540
|
+
const {
|
|
7541
|
+
samples,
|
|
7542
|
+
savedSamples,
|
|
7543
|
+
sigma,
|
|
7544
|
+
majorityPointsRatio,
|
|
7545
|
+
size,
|
|
7546
|
+
} = this;
|
|
7547
|
+
|
|
7548
|
+
samples.resize( size );
|
|
7549
|
+
samples.setSigma( sigma );
|
|
7550
|
+
|
|
7551
|
+
// 1. Randomly place the minority points.
|
|
7552
|
+
const pointCount = Math.floor( size * size * majorityPointsRatio );
|
|
7553
|
+
const initialSamples = samples.binaryPattern;
|
|
7554
|
+
|
|
7555
|
+
fillWithOnes( initialSamples, pointCount );
|
|
7556
|
+
shuffleArray( initialSamples, this.random );
|
|
7557
|
+
|
|
7558
|
+
for ( let i = 0, l = initialSamples.length; i < l; i ++ ) {
|
|
7559
|
+
|
|
7560
|
+
if ( initialSamples[ i ] === 1 ) {
|
|
7561
|
+
|
|
7562
|
+
samples.addPointIndex( i );
|
|
7563
|
+
|
|
7564
|
+
}
|
|
7565
|
+
|
|
7566
|
+
}
|
|
7567
|
+
|
|
7568
|
+
// 2. Remove minority point that is in densest cluster and place it in the largest void.
|
|
7569
|
+
while ( true ) {
|
|
7570
|
+
|
|
7571
|
+
const clusterIndex = samples.findCluster();
|
|
7572
|
+
samples.removePointIndex( clusterIndex );
|
|
7573
|
+
|
|
7574
|
+
const voidIndex = samples.findVoid();
|
|
7575
|
+
if ( clusterIndex === voidIndex ) {
|
|
7576
|
+
|
|
7577
|
+
samples.addPointIndex( clusterIndex );
|
|
7578
|
+
break;
|
|
7579
|
+
|
|
7580
|
+
}
|
|
7581
|
+
|
|
7582
|
+
samples.addPointIndex( voidIndex );
|
|
7583
|
+
|
|
7584
|
+
}
|
|
7585
|
+
|
|
7586
|
+
// 3. PHASE I: Assign a rank to each progressively less dense cluster point and put it
|
|
7587
|
+
// in the dither array.
|
|
7588
|
+
const ditherArray = new Uint32Array( size * size );
|
|
7589
|
+
savedSamples.copy( samples );
|
|
7590
|
+
|
|
7591
|
+
let rank;
|
|
7592
|
+
rank = samples.count - 1;
|
|
7593
|
+
while ( rank >= 0 ) {
|
|
7594
|
+
|
|
7595
|
+
const clusterIndex = samples.findCluster();
|
|
7596
|
+
samples.removePointIndex( clusterIndex );
|
|
7597
|
+
|
|
7598
|
+
ditherArray[ clusterIndex ] = rank;
|
|
7599
|
+
rank --;
|
|
7600
|
+
|
|
7601
|
+
}
|
|
7602
|
+
|
|
7603
|
+
// 4. PHASE II: Do the same thing for the largest voids up to half of the total pixels using
|
|
7604
|
+
// the initial binary pattern.
|
|
7605
|
+
const totalSize = size * size;
|
|
7606
|
+
rank = savedSamples.count;
|
|
7607
|
+
while ( rank < totalSize / 2 ) {
|
|
7608
|
+
|
|
7609
|
+
const voidIndex = savedSamples.findVoid();
|
|
7610
|
+
savedSamples.addPointIndex( voidIndex );
|
|
7611
|
+
ditherArray[ voidIndex ] = rank;
|
|
7612
|
+
rank ++;
|
|
7613
|
+
|
|
7614
|
+
}
|
|
7615
|
+
|
|
7616
|
+
// 5. PHASE III: Invert the pattern and finish out by assigning a rank to the remaining
|
|
7617
|
+
// and iteratively removing them.
|
|
7618
|
+
savedSamples.invert();
|
|
7619
|
+
|
|
7620
|
+
while ( rank < totalSize ) {
|
|
7621
|
+
|
|
7622
|
+
const clusterIndex = savedSamples.findCluster();
|
|
7623
|
+
savedSamples.removePointIndex( clusterIndex );
|
|
7624
|
+
ditherArray[ clusterIndex ] = rank;
|
|
7625
|
+
rank ++;
|
|
7626
|
+
|
|
7627
|
+
}
|
|
7628
|
+
|
|
7629
|
+
return { data: ditherArray, maxValue: totalSize };
|
|
7630
|
+
|
|
7631
|
+
}
|
|
7632
|
+
|
|
7633
|
+
}
|
|
7634
|
+
|
|
7635
|
+
function getStride( channels ) {
|
|
7636
|
+
|
|
7637
|
+
if ( channels >= 3 ) {
|
|
7638
|
+
|
|
7639
|
+
return 4;
|
|
7640
|
+
|
|
7641
|
+
} else {
|
|
7642
|
+
|
|
7643
|
+
return channels;
|
|
7644
|
+
|
|
7645
|
+
}
|
|
7646
|
+
|
|
7647
|
+
}
|
|
7648
|
+
|
|
7649
|
+
function getFormat( channels ) {
|
|
7650
|
+
|
|
7651
|
+
switch ( channels ) {
|
|
7652
|
+
|
|
7653
|
+
case 1:
|
|
7654
|
+
return three.RedFormat;
|
|
7655
|
+
case 2:
|
|
7656
|
+
return three.RGFormat;
|
|
7657
|
+
default:
|
|
7658
|
+
return three.RGBAFormat;
|
|
7659
|
+
|
|
7660
|
+
}
|
|
7661
|
+
|
|
7662
|
+
}
|
|
7663
|
+
|
|
7664
|
+
class BlueNoiseTexture extends three.DataTexture {
|
|
7665
|
+
|
|
7666
|
+
constructor( size = 64, channels = 1 ) {
|
|
7667
|
+
|
|
7668
|
+
super( new Float32Array( 4 ), 1, 1, three.RGBAFormat, three.FloatType );
|
|
7669
|
+
this.minFilter = three.NearestFilter;
|
|
7670
|
+
this.magFilter = three.NearestFilter;
|
|
7671
|
+
|
|
7672
|
+
this.size = size;
|
|
7673
|
+
this.channels = channels;
|
|
7674
|
+
this.update();
|
|
7675
|
+
|
|
7676
|
+
}
|
|
7677
|
+
|
|
7678
|
+
update() {
|
|
7679
|
+
|
|
7680
|
+
const channels = this.channels;
|
|
7681
|
+
const size = this.size;
|
|
7682
|
+
const generator = new BlueNoiseGenerator();
|
|
7683
|
+
generator.channels = channels;
|
|
7684
|
+
generator.size = size;
|
|
7685
|
+
|
|
7686
|
+
const stride = getStride( channels );
|
|
7687
|
+
const format = getFormat( stride );
|
|
7688
|
+
if ( this.image.width !== size || format !== this.format ) {
|
|
7689
|
+
|
|
7690
|
+
this.image.width = size;
|
|
7691
|
+
this.image.height = size;
|
|
7692
|
+
this.image.data = new Float32Array( ( size ** 2 ) * stride );
|
|
7693
|
+
this.format = format;
|
|
7694
|
+
this.dispose();
|
|
7695
|
+
|
|
7696
|
+
}
|
|
7697
|
+
|
|
7698
|
+
const data = this.image.data;
|
|
7699
|
+
for ( let i = 0, l = channels; i < l; i ++ ) {
|
|
7700
|
+
|
|
7701
|
+
const result = generator.generate();
|
|
7702
|
+
const bin = result.data;
|
|
7703
|
+
const maxValue = result.maxValue;
|
|
7704
|
+
|
|
7705
|
+
for ( let j = 0, l2 = bin.length; j < l2; j ++ ) {
|
|
7706
|
+
|
|
7707
|
+
const value = bin[ j ] / maxValue;
|
|
7708
|
+
data[ j * stride + i ] = value;
|
|
7709
|
+
|
|
7710
|
+
}
|
|
7711
|
+
|
|
7712
|
+
}
|
|
7713
|
+
|
|
7714
|
+
this.needsUpdate = true;
|
|
7715
|
+
|
|
7716
|
+
}
|
|
7717
|
+
|
|
7718
|
+
}
|
|
7719
|
+
|
|
7051
7720
|
class PhysicalPathTracingMaterial extends MaterialBase {
|
|
7052
7721
|
|
|
7053
7722
|
onBeforeRender() {
|
|
@@ -7071,7 +7740,12 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7071
7740
|
FEATURE_DOF: 1,
|
|
7072
7741
|
FEATURE_BACKGROUND_MAP: 0,
|
|
7073
7742
|
FEATURE_FOG: 1,
|
|
7074
|
-
|
|
7743
|
+
|
|
7744
|
+
// 0 = PCG
|
|
7745
|
+
// 1 = Sobol
|
|
7746
|
+
// 2 = Stratified List
|
|
7747
|
+
RANDOM_TYPE: 2,
|
|
7748
|
+
|
|
7075
7749
|
// 0 = Perspective
|
|
7076
7750
|
// 1 = Orthographic
|
|
7077
7751
|
// 2 = Equirectangular
|
|
@@ -7113,6 +7787,8 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7113
7787
|
|
|
7114
7788
|
backgroundAlpha: { value: 1.0 },
|
|
7115
7789
|
sobolTexture: { value: null },
|
|
7790
|
+
stratifiedTexture: { value: new StratifiedSamplesTexture() },
|
|
7791
|
+
stratifiedOffsetTexture: { value: new BlueNoiseTexture( 64, 1 ) },
|
|
7116
7792
|
},
|
|
7117
7793
|
|
|
7118
7794
|
vertexShader: /* glsl */`
|
|
@@ -7152,23 +7828,35 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7152
7828
|
${ materialStructGLSL }
|
|
7153
7829
|
|
|
7154
7830
|
// random
|
|
7155
|
-
|
|
7156
|
-
#if FEATURE_SOBOL
|
|
7831
|
+
#if RANDOM_TYPE == 2 // Stratified List
|
|
7157
7832
|
|
|
7833
|
+
${ stratifiedTextureGLSL }
|
|
7834
|
+
|
|
7835
|
+
#elif RANDOM_TYPE == 1 // Sobol
|
|
7836
|
+
|
|
7837
|
+
${ pcgGLSL }
|
|
7158
7838
|
${ sobolCommonGLSL }
|
|
7159
7839
|
${ sobolSamplingGLSL }
|
|
7160
7840
|
|
|
7161
|
-
|
|
7841
|
+
#define rand(v) sobol(v)
|
|
7842
|
+
#define rand2(v) sobol2(v)
|
|
7843
|
+
#define rand3(v) sobol3(v)
|
|
7844
|
+
#define rand4(v) sobol4(v)
|
|
7845
|
+
|
|
7846
|
+
#else // PCG
|
|
7847
|
+
|
|
7848
|
+
${ pcgGLSL }
|
|
7162
7849
|
|
|
7163
7850
|
// Using the sobol functions seems to break the the compiler on MacOS
|
|
7164
7851
|
// - specifically the "sobolReverseBits" function.
|
|
7165
7852
|
uint sobolPixelIndex = 0u;
|
|
7166
7853
|
uint sobolPathIndex = 0u;
|
|
7167
7854
|
uint sobolBounceIndex = 0u;
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7855
|
+
|
|
7856
|
+
#define rand(v) pcgRand()
|
|
7857
|
+
#define rand2(v) pcgRand2()
|
|
7858
|
+
#define rand3(v) pcgRand3()
|
|
7859
|
+
#define rand4(v) pcgRand4()
|
|
7172
7860
|
|
|
7173
7861
|
#endif
|
|
7174
7862
|
|
|
@@ -7286,7 +7974,10 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7286
7974
|
// inverse environment rotation
|
|
7287
7975
|
envRotation3x3 = mat3( environmentRotation );
|
|
7288
7976
|
invEnvRotation3x3 = inverse( envRotation3x3 );
|
|
7289
|
-
lightsDenom =
|
|
7977
|
+
lightsDenom =
|
|
7978
|
+
( environmentIntensity == 0.0 || envMapInfo.totalSum == 0.0 ) && lights.count != 0u ?
|
|
7979
|
+
float( lights.count ) :
|
|
7980
|
+
float( lights.count + 1u );
|
|
7290
7981
|
|
|
7291
7982
|
// final color
|
|
7292
7983
|
gl_FragColor = vec4( 0, 0, 0, 1 );
|
|
@@ -7355,7 +8046,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7355
8046
|
|
|
7356
8047
|
if ( state.firstRay || state.transmissiveRay ) {
|
|
7357
8048
|
|
|
7358
|
-
gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction,
|
|
8049
|
+
gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, rand2( 2 ) ) * state.throughputColor;
|
|
7359
8050
|
gl_FragColor.a = backgroundAlpha;
|
|
7360
8051
|
|
|
7361
8052
|
} else {
|
|
@@ -7446,7 +8137,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7446
8137
|
}
|
|
7447
8138
|
|
|
7448
8139
|
scatterRec = bsdfSample( - ray.direction, surf );
|
|
7449
|
-
state.isShadowRay = scatterRec.specularPdf <
|
|
8140
|
+
state.isShadowRay = scatterRec.specularPdf < rand( 4 );
|
|
7450
8141
|
|
|
7451
8142
|
bool isBelowSurface = ! surf.volumeParticle && dot( scatterRec.direction, surf.faceNormal ) < 0.0;
|
|
7452
8143
|
vec3 hitPoint = stepRayOrigin( ray.origin, ray.direction, isBelowSurface ? - surf.faceNormal : surf.faceNormal, surfaceHit.dist );
|
|
@@ -7516,7 +8207,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7516
8207
|
rrProb = sqrt( rrProb );
|
|
7517
8208
|
rrProb = max( rrProb, depthProb );
|
|
7518
8209
|
rrProb = min( rrProb, 1.0 );
|
|
7519
|
-
if (
|
|
8210
|
+
if ( rand( 8 ) > rrProb ) {
|
|
7520
8211
|
|
|
7521
8212
|
break;
|
|
7522
8213
|
|
|
@@ -7947,10 +8638,9 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7947
8638
|
exports.QuiltPathTracingRenderer = QuiltPathTracingRenderer;
|
|
7948
8639
|
exports.RenderTarget2DArray = RenderTarget2DArray;
|
|
7949
8640
|
exports.ShapedAreaLight = ShapedAreaLight;
|
|
8641
|
+
exports.getDummyMesh = getDummyMesh;
|
|
7950
8642
|
exports.getGroupMaterialIndicesAttribute = getGroupMaterialIndicesAttribute;
|
|
7951
|
-
exports.mergeMeshes = mergeMeshes;
|
|
7952
8643
|
exports.setCommonAttributes = setCommonAttributes;
|
|
7953
|
-
exports.trimToAttributes = trimToAttributes;
|
|
7954
8644
|
|
|
7955
8645
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
7956
8646
|
|