three-gpu-pathtracer 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.module.js +1000 -319
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +996 -316
- 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 +33 -11
- 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 +3 -3
- 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/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/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
|
|
|
@@ -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;
|
|
2569
2525
|
|
|
2570
|
-
|
|
2571
|
-
|
|
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 ) {
|
|
2572
2548
|
|
|
2573
|
-
|
|
2549
|
+
newData[ i ] = three.DataUtils.toHalfFloat( v );
|
|
2550
|
+
|
|
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; 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 );
|
|
@@ -6160,32 +6144,38 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6160
6144
|
|
|
6161
6145
|
}
|
|
6162
6146
|
|
|
6163
|
-
|
|
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 ) {
|
|
6164
6150
|
|
|
6165
|
-
|
|
6151
|
+
// if ( totalInternalReflection( cosTheta, eta ) ) {
|
|
6166
6152
|
|
|
6167
|
-
|
|
6153
|
+
// return 1.0;
|
|
6168
6154
|
|
|
6169
|
-
|
|
6155
|
+
// }
|
|
6170
6156
|
|
|
6171
|
-
|
|
6157
|
+
// return schlickFresnel( cosTheta, f0 );
|
|
6172
6158
|
|
|
6173
|
-
}
|
|
6159
|
+
// }
|
|
6174
6160
|
|
|
6175
|
-
/*
|
|
6176
6161
|
// https://schuttejoe.github.io/post/disneybsdf/
|
|
6177
6162
|
float disneyFresnel( vec3 wo, vec3 wi, vec3 wh, float f0, float eta, float metalness ) {
|
|
6178
6163
|
|
|
6179
6164
|
float dotHV = dot( wo, wh );
|
|
6180
|
-
|
|
6165
|
+
if ( totalInternalReflection( dotHV, eta ) ) {
|
|
6166
|
+
|
|
6167
|
+
return 1.0;
|
|
6181
6168
|
|
|
6169
|
+
}
|
|
6170
|
+
|
|
6171
|
+
float dotHL = dot( wi, wh );
|
|
6182
6172
|
float dielectricFresnel = dielectricFresnel( abs( dotHV ), eta );
|
|
6183
6173
|
float metallicFresnel = schlickFresnel( dotHL, f0 );
|
|
6184
6174
|
|
|
6185
6175
|
return mix( dielectricFresnel, metallicFresnel, metalness );
|
|
6186
6176
|
|
|
6187
6177
|
}
|
|
6188
|
-
|
|
6178
|
+
|
|
6189
6179
|
`;
|
|
6190
6180
|
|
|
6191
6181
|
const arraySamplerTexelFetchGLSL = /*glsl */`
|
|
@@ -6242,28 +6232,28 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6242
6232
|
}
|
|
6243
6233
|
|
|
6244
6234
|
// returns [ 0, 1 ]
|
|
6245
|
-
float
|
|
6235
|
+
float pcgRand() {
|
|
6246
6236
|
|
|
6247
6237
|
pcg4d( WHITE_NOISE_SEED );
|
|
6248
6238
|
return float( WHITE_NOISE_SEED.x ) / float( 0xffffffffu );
|
|
6249
6239
|
|
|
6250
6240
|
}
|
|
6251
6241
|
|
|
6252
|
-
vec2
|
|
6242
|
+
vec2 pcgRand2() {
|
|
6253
6243
|
|
|
6254
6244
|
pcg4d( WHITE_NOISE_SEED );
|
|
6255
6245
|
return vec2( WHITE_NOISE_SEED.xy ) / float(0xffffffffu);
|
|
6256
6246
|
|
|
6257
6247
|
}
|
|
6258
6248
|
|
|
6259
|
-
vec3
|
|
6249
|
+
vec3 pcgRand3() {
|
|
6260
6250
|
|
|
6261
6251
|
pcg4d( WHITE_NOISE_SEED );
|
|
6262
6252
|
return vec3( WHITE_NOISE_SEED.xyz ) / float( 0xffffffffu );
|
|
6263
6253
|
|
|
6264
6254
|
}
|
|
6265
6255
|
|
|
6266
|
-
vec4
|
|
6256
|
+
vec4 pcgRand4() {
|
|
6267
6257
|
|
|
6268
6258
|
pcg4d( WHITE_NOISE_SEED );
|
|
6269
6259
|
return vec4( WHITE_NOISE_SEED ) / float( 0xffffffffu );
|
|
@@ -6336,7 +6326,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6336
6326
|
|
|
6337
6327
|
// Jitter the camera ray by finding a uv coordinate at a random sample
|
|
6338
6328
|
// around this pixel's UV coordinate for AA
|
|
6339
|
-
vec2 ruv =
|
|
6329
|
+
vec2 ruv = rand2( 0 );
|
|
6340
6330
|
vec2 jitteredUv = vUv + vec2( tentFilter( ruv.x ) * ssd.x, tentFilter( ruv.y ) * ssd.y );
|
|
6341
6331
|
Ray ray;
|
|
6342
6332
|
|
|
@@ -6381,7 +6371,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6381
6371
|
|
|
6382
6372
|
// get the aperture sample
|
|
6383
6373
|
// if blades === 0 then we assume a circle
|
|
6384
|
-
vec3 shapeUVW=
|
|
6374
|
+
vec3 shapeUVW= rand3( 1 );
|
|
6385
6375
|
int blades = physicalCamera.apertureBlades;
|
|
6386
6376
|
float anamorphicRatio = physicalCamera.anamorphicRatio;
|
|
6387
6377
|
vec2 apertureSample = blades == 0 ? sampleCircle( shapeUVW.xy ) : sampleRegularPolygon( blades, shapeUVW );
|
|
@@ -6417,6 +6407,9 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6417
6407
|
out vec3 color
|
|
6418
6408
|
) {
|
|
6419
6409
|
|
|
6410
|
+
// store the original bounce index so we can reset it after
|
|
6411
|
+
uint originalBounceIndex = sobolBounceIndex;
|
|
6412
|
+
|
|
6420
6413
|
int traversals = state.traversals;
|
|
6421
6414
|
int transmissiveTraversals = state.transmissiveTraversals;
|
|
6422
6415
|
bool isShadowRay = state.isShadowRay;
|
|
@@ -6429,22 +6422,25 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6429
6422
|
|
|
6430
6423
|
color = vec3( 1.0 );
|
|
6431
6424
|
|
|
6432
|
-
|
|
6433
|
-
// and then reset.
|
|
6425
|
+
bool result = true;
|
|
6434
6426
|
for ( int i = 0; i < traversals; i ++ ) {
|
|
6435
6427
|
|
|
6428
|
+
sobolBounceIndex ++;
|
|
6429
|
+
|
|
6436
6430
|
int hitType = traceScene( ray, fogMaterial, surfaceHit );
|
|
6437
6431
|
|
|
6438
6432
|
if ( hitType == FOG_HIT ) {
|
|
6439
6433
|
|
|
6440
|
-
|
|
6434
|
+
result = true;
|
|
6435
|
+
break;
|
|
6441
6436
|
|
|
6442
6437
|
} else if ( hitType == SURFACE_HIT ) {
|
|
6443
6438
|
|
|
6444
6439
|
float totalDist = distance( startPoint, ray.origin + ray.direction * surfaceHit.dist );
|
|
6445
6440
|
if ( totalDist > rayDist ) {
|
|
6446
6441
|
|
|
6447
|
-
|
|
6442
|
+
result = false;
|
|
6443
|
+
break;
|
|
6448
6444
|
|
|
6449
6445
|
}
|
|
6450
6446
|
|
|
@@ -6526,7 +6522,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6526
6522
|
bool useAlphaTest = alphaTest != 0.0;
|
|
6527
6523
|
float transmissionFactor = ( 1.0 - metalness ) * transmission;
|
|
6528
6524
|
if (
|
|
6529
|
-
transmissionFactor < rand() && ! (
|
|
6525
|
+
transmissionFactor < rand( 9 ) && ! (
|
|
6530
6526
|
// material sidedness
|
|
6531
6527
|
material.side != 0.0 && surfaceHit.side == material.side
|
|
6532
6528
|
|
|
@@ -6534,11 +6530,12 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6534
6530
|
|| useAlphaTest && albedo.a < alphaTest
|
|
6535
6531
|
|
|
6536
6532
|
// opacity
|
|
6537
|
-
|| material.transparent && ! useAlphaTest && albedo.a < rand()
|
|
6533
|
+
|| material.transparent && ! useAlphaTest && albedo.a < rand( 10 )
|
|
6538
6534
|
)
|
|
6539
6535
|
) {
|
|
6540
6536
|
|
|
6541
|
-
|
|
6537
|
+
result = true;
|
|
6538
|
+
break;
|
|
6542
6539
|
|
|
6543
6540
|
}
|
|
6544
6541
|
|
|
@@ -6564,13 +6561,16 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6564
6561
|
|
|
6565
6562
|
} else {
|
|
6566
6563
|
|
|
6567
|
-
|
|
6564
|
+
result = false;
|
|
6565
|
+
break;
|
|
6568
6566
|
|
|
6569
6567
|
}
|
|
6570
6568
|
|
|
6571
6569
|
}
|
|
6572
6570
|
|
|
6573
|
-
|
|
6571
|
+
// reset the bounce index
|
|
6572
|
+
sobolBounceIndex = originalBounceIndex;
|
|
6573
|
+
return result;
|
|
6574
6574
|
|
|
6575
6575
|
}
|
|
6576
6576
|
|
|
@@ -6601,7 +6601,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6601
6601
|
|
|
6602
6602
|
// offset the distance so we don't run into issues with particles on the same surface
|
|
6603
6603
|
// as other objects
|
|
6604
|
-
float particleDist = intersectFogVolume( fogMaterial,
|
|
6604
|
+
float particleDist = intersectFogVolume( fogMaterial, rand( 1 ) );
|
|
6605
6605
|
if ( particleDist + RAY_OFFSET < surfaceHit.dist ) {
|
|
6606
6606
|
|
|
6607
6607
|
surfaceHit.side = 1.0;
|
|
@@ -6664,6 +6664,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6664
6664
|
|
|
6665
6665
|
vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
|
|
6666
6666
|
albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
|
|
6667
|
+
|
|
6667
6668
|
}
|
|
6668
6669
|
|
|
6669
6670
|
if ( material.vertexColors ) {
|
|
@@ -6695,7 +6696,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6695
6696
|
|| useAlphaTest && albedo.a < alphaTest
|
|
6696
6697
|
|
|
6697
6698
|
// opacity
|
|
6698
|
-
|| material.transparent && ! useAlphaTest && albedo.a <
|
|
6699
|
+
|| material.transparent && ! useAlphaTest && albedo.a < rand( 3 )
|
|
6699
6700
|
) {
|
|
6700
6701
|
|
|
6701
6702
|
return SKIP_SURFACE;
|
|
@@ -6957,10 +6958,10 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6957
6958
|
vec3 result = vec3( 0.0 );
|
|
6958
6959
|
|
|
6959
6960
|
// uniformly pick a light or environment map
|
|
6960
|
-
if( lightsDenom != 0.0 &&
|
|
6961
|
+
if( lightsDenom != 0.0 && rand( 5 ) < float( lights.count ) / lightsDenom ) {
|
|
6961
6962
|
|
|
6962
6963
|
// sample a light or environment
|
|
6963
|
-
LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin,
|
|
6964
|
+
LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, rand3( 6 ) );
|
|
6964
6965
|
|
|
6965
6966
|
bool isSampleBelowSurface = ! surf.volumeParticle && dot( surf.faceNormal, lightRec.direction ) < 0.0;
|
|
6966
6967
|
if ( isSampleBelowSurface ) {
|
|
@@ -6999,7 +7000,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
6999
7000
|
|
|
7000
7001
|
// find a sample in the environment map to include in the contribution
|
|
7001
7002
|
vec3 envColor, envDirection;
|
|
7002
|
-
float envPdf = sampleEquirectProbability(
|
|
7003
|
+
float envPdf = sampleEquirectProbability( rand2( 7 ), envColor, envDirection );
|
|
7003
7004
|
envDirection = invEnvRotation3x3 * envDirection;
|
|
7004
7005
|
|
|
7005
7006
|
// this env sampling is not set up for transmissive sampling and yields overly bright
|
|
@@ -7048,6 +7049,667 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7048
7049
|
|
|
7049
7050
|
`;
|
|
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 );
|
|
7093
|
+
|
|
7094
|
+
}
|
|
7095
|
+
|
|
7096
|
+
`;
|
|
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
|
+
|
|
7051
7713
|
class PhysicalPathTracingMaterial extends MaterialBase {
|
|
7052
7714
|
|
|
7053
7715
|
onBeforeRender() {
|
|
@@ -7071,7 +7733,12 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7071
7733
|
FEATURE_DOF: 1,
|
|
7072
7734
|
FEATURE_BACKGROUND_MAP: 0,
|
|
7073
7735
|
FEATURE_FOG: 1,
|
|
7074
|
-
|
|
7736
|
+
|
|
7737
|
+
// 0 = PCG
|
|
7738
|
+
// 1 = Sobol
|
|
7739
|
+
// 2 = Stratified List
|
|
7740
|
+
RANDOM_TYPE: 2,
|
|
7741
|
+
|
|
7075
7742
|
// 0 = Perspective
|
|
7076
7743
|
// 1 = Orthographic
|
|
7077
7744
|
// 2 = Equirectangular
|
|
@@ -7113,6 +7780,8 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7113
7780
|
|
|
7114
7781
|
backgroundAlpha: { value: 1.0 },
|
|
7115
7782
|
sobolTexture: { value: null },
|
|
7783
|
+
stratifiedTexture: { value: new StratifiedSamplesTexture() },
|
|
7784
|
+
stratifiedOffsetTexture: { value: new BlueNoiseTexture( 64, 1 ) },
|
|
7116
7785
|
},
|
|
7117
7786
|
|
|
7118
7787
|
vertexShader: /* glsl */`
|
|
@@ -7152,23 +7821,35 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7152
7821
|
${ materialStructGLSL }
|
|
7153
7822
|
|
|
7154
7823
|
// random
|
|
7155
|
-
|
|
7156
|
-
#if FEATURE_SOBOL
|
|
7824
|
+
#if RANDOM_TYPE == 2 // Stratified List
|
|
7157
7825
|
|
|
7826
|
+
${ stratifiedTextureGLSL }
|
|
7827
|
+
|
|
7828
|
+
#elif RANDOM_TYPE == 1 // Sobol
|
|
7829
|
+
|
|
7830
|
+
${ pcgGLSL }
|
|
7158
7831
|
${ sobolCommonGLSL }
|
|
7159
7832
|
${ sobolSamplingGLSL }
|
|
7160
7833
|
|
|
7161
|
-
|
|
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 }
|
|
7162
7842
|
|
|
7163
7843
|
// Using the sobol functions seems to break the the compiler on MacOS
|
|
7164
7844
|
// - specifically the "sobolReverseBits" function.
|
|
7165
7845
|
uint sobolPixelIndex = 0u;
|
|
7166
7846
|
uint sobolPathIndex = 0u;
|
|
7167
7847
|
uint sobolBounceIndex = 0u;
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7848
|
+
|
|
7849
|
+
#define rand(v) pcgRand()
|
|
7850
|
+
#define rand2(v) pcgRand2()
|
|
7851
|
+
#define rand3(v) pcgRand3()
|
|
7852
|
+
#define rand4(v) pcgRand4()
|
|
7172
7853
|
|
|
7173
7854
|
#endif
|
|
7174
7855
|
|
|
@@ -7355,7 +8036,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7355
8036
|
|
|
7356
8037
|
if ( state.firstRay || state.transmissiveRay ) {
|
|
7357
8038
|
|
|
7358
|
-
gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction,
|
|
8039
|
+
gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, rand2( 2 ) ) * state.throughputColor;
|
|
7359
8040
|
gl_FragColor.a = backgroundAlpha;
|
|
7360
8041
|
|
|
7361
8042
|
} else {
|
|
@@ -7446,7 +8127,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7446
8127
|
}
|
|
7447
8128
|
|
|
7448
8129
|
scatterRec = bsdfSample( - ray.direction, surf );
|
|
7449
|
-
state.isShadowRay = scatterRec.specularPdf <
|
|
8130
|
+
state.isShadowRay = scatterRec.specularPdf < rand( 4 );
|
|
7450
8131
|
|
|
7451
8132
|
bool isBelowSurface = ! surf.volumeParticle && dot( scatterRec.direction, surf.faceNormal ) < 0.0;
|
|
7452
8133
|
vec3 hitPoint = stepRayOrigin( ray.origin, ray.direction, isBelowSurface ? - surf.faceNormal : surf.faceNormal, surfaceHit.dist );
|
|
@@ -7516,7 +8197,7 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7516
8197
|
rrProb = sqrt( rrProb );
|
|
7517
8198
|
rrProb = max( rrProb, depthProb );
|
|
7518
8199
|
rrProb = min( rrProb, 1.0 );
|
|
7519
|
-
if (
|
|
8200
|
+
if ( rand( 8 ) > rrProb ) {
|
|
7520
8201
|
|
|
7521
8202
|
break;
|
|
7522
8203
|
|
|
@@ -7947,10 +8628,9 @@ bool bvhIntersectFogVolumeHit(
|
|
|
7947
8628
|
exports.QuiltPathTracingRenderer = QuiltPathTracingRenderer;
|
|
7948
8629
|
exports.RenderTarget2DArray = RenderTarget2DArray;
|
|
7949
8630
|
exports.ShapedAreaLight = ShapedAreaLight;
|
|
8631
|
+
exports.getDummyMesh = getDummyMesh;
|
|
7950
8632
|
exports.getGroupMaterialIndicesAttribute = getGroupMaterialIndicesAttribute;
|
|
7951
|
-
exports.mergeMeshes = mergeMeshes;
|
|
7952
8633
|
exports.setCommonAttributes = setCommonAttributes;
|
|
7953
|
-
exports.trimToAttributes = trimToAttributes;
|
|
7954
8634
|
|
|
7955
8635
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
7956
8636
|
|