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.module.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ShaderMaterial, NoBlending, Vector2, WebGLRenderTarget, FloatType, RGBAFormat, NearestFilter, Vector4, NormalBlending, Color, HalfFloatType, Vector3, MathUtils, Matrix4, PerspectiveCamera, BufferAttribute,
|
|
1
|
+
import { ShaderMaterial, NoBlending, Vector2, WebGLRenderTarget, FloatType, RGBAFormat, NearestFilter, Vector4, NormalBlending, Color, HalfFloatType, Vector3, MathUtils, Matrix4, PerspectiveCamera, BufferAttribute, MeshBasicMaterial, BufferGeometry, Mesh, Camera, SpotLight, RectAreaLight, Spherical, DataTexture, EquirectangularReflectionMapping, RepeatWrapping, ClampToEdgeWrapping, LinearFilter, DoubleSide, BackSide, FrontSide, WebGLArrayRenderTarget, UnsignedByteType, NoToneMapping, DataUtils, Source, RedFormat, Quaternion, Loader, FileLoader, PMREMGenerator, DataArrayTexture, RGFormat, MeshStandardMaterial, BoxGeometry } from 'three';
|
|
2
2
|
import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass.js';
|
|
3
|
-
import { StaticGeometryGenerator,
|
|
4
|
-
import { mergeVertices
|
|
3
|
+
import { StaticGeometryGenerator, MeshBVH, SAH, FloatVertexAttributeTexture, MeshBVHUniformStruct, UIntVertexAttributeTexture, BVHShaderGLSL } from 'three-mesh-bvh';
|
|
4
|
+
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
|
5
5
|
|
|
6
6
|
class MaterialBase extends ShaderMaterial {
|
|
7
7
|
|
|
@@ -456,9 +456,6 @@ class SobolNumberMapGenerator {
|
|
|
456
456
|
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
-
const _scissor = new Vector4();
|
|
460
|
-
const _viewport = new Vector4();
|
|
461
|
-
|
|
462
459
|
function* renderTask() {
|
|
463
460
|
|
|
464
461
|
const {
|
|
@@ -500,12 +497,21 @@ function* renderTask() {
|
|
|
500
497
|
const h = _primaryTarget.height;
|
|
501
498
|
material.resolution.set( w * subW, h * subH );
|
|
502
499
|
material.sobolTexture = _sobolTarget.texture;
|
|
500
|
+
material.stratifiedTexture.init( 20, material.bounces + material.transmissiveBounces + 5 );
|
|
501
|
+
material.stratifiedTexture.next();
|
|
503
502
|
material.seed ++;
|
|
504
503
|
|
|
505
504
|
const tilesX = this.tiles.x || 1;
|
|
506
505
|
const tilesY = this.tiles.y || 1;
|
|
507
506
|
const totalTiles = tilesX * tilesY;
|
|
508
|
-
|
|
507
|
+
|
|
508
|
+
const pxSubW = Math.ceil( w * subW );
|
|
509
|
+
const pxSubH = Math.ceil( h * subH );
|
|
510
|
+
const pxSubX = Math.floor( subX * w );
|
|
511
|
+
const pxSubY = Math.floor( subY * h );
|
|
512
|
+
|
|
513
|
+
const pxTileW = Math.ceil( pxSubW / tilesX );
|
|
514
|
+
const pxTileH = Math.ceil( pxSubH / tilesY );
|
|
509
515
|
|
|
510
516
|
for ( let y = 0; y < tilesY; y ++ ) {
|
|
511
517
|
|
|
@@ -554,40 +560,28 @@ function* renderTask() {
|
|
|
554
560
|
|
|
555
561
|
}
|
|
556
562
|
|
|
563
|
+
// set the scissor and the viewport on the render target
|
|
564
|
+
// note that when using the webgl renderer set viewport the device pixel ratio
|
|
565
|
+
// is multiplied into the field causing some pixels to not be rendered
|
|
566
|
+
const reverseTy = tilesY - ty - 1;
|
|
567
|
+
_primaryTarget.scissor.set(
|
|
568
|
+
pxSubX + tx * pxTileW,
|
|
569
|
+
pxSubY + reverseTy * pxTileH,
|
|
570
|
+
Math.min( pxTileW, pxSubW - tx * pxTileW ),
|
|
571
|
+
Math.min( pxTileH, pxSubH - reverseTy * pxTileH ),
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
_primaryTarget.viewport.set(
|
|
575
|
+
pxSubX,
|
|
576
|
+
pxSubY,
|
|
577
|
+
pxSubW,
|
|
578
|
+
pxSubH,
|
|
579
|
+
);
|
|
580
|
+
|
|
557
581
|
// three.js renderer takes values relative to the current pixel ratio
|
|
558
582
|
_renderer.setRenderTarget( _primaryTarget );
|
|
559
583
|
_renderer.setScissorTest( true );
|
|
560
584
|
|
|
561
|
-
// set the scissor window for a subtile
|
|
562
|
-
_scissor.x = tx * w / tilesX;
|
|
563
|
-
_scissor.y = ( tilesY - ty - 1 ) * h / tilesY;
|
|
564
|
-
_scissor.z = w / tilesX;
|
|
565
|
-
_scissor.w = h / tilesY;
|
|
566
|
-
|
|
567
|
-
// adjust for the subframe
|
|
568
|
-
_scissor.x = subX * w + subW * _scissor.x;
|
|
569
|
-
_scissor.y = subY * h + subH * _scissor.y;
|
|
570
|
-
_scissor.z = subW * _scissor.z;
|
|
571
|
-
_scissor.w = subH * _scissor.w;
|
|
572
|
-
|
|
573
|
-
// round for floating point cases
|
|
574
|
-
_scissor.x = _scissor.x;
|
|
575
|
-
_scissor.y = _scissor.y;
|
|
576
|
-
_scissor.z = _scissor.z;
|
|
577
|
-
_scissor.w = _scissor.w;
|
|
578
|
-
|
|
579
|
-
// multiply inverse of DPR in because threes multiplies it in
|
|
580
|
-
_scissor.multiplyScalar( dprInv ).ceil();
|
|
581
|
-
|
|
582
|
-
_viewport.x = subX * w;
|
|
583
|
-
_viewport.y = subY * h;
|
|
584
|
-
_viewport.z = subW * w;
|
|
585
|
-
_viewport.w = subH * h;
|
|
586
|
-
_viewport.multiplyScalar( dprInv ).ceil();
|
|
587
|
-
|
|
588
|
-
_renderer.setScissor( _scissor );
|
|
589
|
-
_renderer.setViewport( _viewport );
|
|
590
|
-
|
|
591
585
|
_renderer.autoClear = false;
|
|
592
586
|
_fsQuad.render( _renderer );
|
|
593
587
|
|
|
@@ -1077,25 +1071,6 @@ function getGroupMaterialIndicesAttribute( geometry, materials, allMaterials ) {
|
|
|
1077
1071
|
|
|
1078
1072
|
}
|
|
1079
1073
|
|
|
1080
|
-
function trimToAttributes( geometry, attributes ) {
|
|
1081
|
-
|
|
1082
|
-
// trim any unneeded attributes
|
|
1083
|
-
if ( attributes ) {
|
|
1084
|
-
|
|
1085
|
-
for ( const key in geometry.attributes ) {
|
|
1086
|
-
|
|
1087
|
-
if ( ! attributes.includes( key ) ) {
|
|
1088
|
-
|
|
1089
|
-
geometry.deleteAttribute( key );
|
|
1090
|
-
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
1074
|
function setCommonAttributes( geometry, options ) {
|
|
1100
1075
|
|
|
1101
1076
|
const { attributes = [], normalMapRequired = false } = options;
|
|
@@ -1113,6 +1088,13 @@ function setCommonAttributes( geometry, options ) {
|
|
|
1113
1088
|
|
|
1114
1089
|
}
|
|
1115
1090
|
|
|
1091
|
+
if ( ! geometry.attributes.uv2 && ( attributes && attributes.includes( 'uv2' ) ) ) {
|
|
1092
|
+
|
|
1093
|
+
const vertCount = geometry.attributes.position.count;
|
|
1094
|
+
geometry.setAttribute( 'uv2', new BufferAttribute( new Float32Array( vertCount * 2 ), 2, false ) );
|
|
1095
|
+
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1116
1098
|
if ( ! geometry.attributes.tangent && ( attributes && attributes.includes( 'tangent' ) ) ) {
|
|
1117
1099
|
|
|
1118
1100
|
if ( normalMapRequired ) {
|
|
@@ -1161,169 +1143,46 @@ function setCommonAttributes( geometry, options ) {
|
|
|
1161
1143
|
|
|
1162
1144
|
}
|
|
1163
1145
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
options = { attributes: null, cloneGeometry: true, ...options };
|
|
1146
|
+
const dummyMaterial = new MeshBasicMaterial();
|
|
1147
|
+
function getDummyMesh() {
|
|
1167
1148
|
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1170
|
-
|
|
1149
|
+
const emptyGeometry = new BufferGeometry();
|
|
1150
|
+
emptyGeometry.setAttribute( 'position', new BufferAttribute( new Float32Array( 9 ), 3 ) );
|
|
1151
|
+
return new Mesh( emptyGeometry, dummyMaterial );
|
|
1171
1152
|
|
|
1172
|
-
|
|
1173
|
-
const mesh = meshes[ i ];
|
|
1174
|
-
if ( mesh.visible === false ) continue;
|
|
1175
|
-
|
|
1176
|
-
if ( Array.isArray( mesh.material ) ) {
|
|
1177
|
-
|
|
1178
|
-
mesh.material.forEach( m => materialSet.add( m ) );
|
|
1179
|
-
|
|
1180
|
-
} else {
|
|
1181
|
-
|
|
1182
|
-
materialSet.add( mesh.material );
|
|
1183
|
-
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
const materials = Array.from( materialSet );
|
|
1189
|
-
for ( let i = 0, l = meshes.length; i < l; i ++ ) {
|
|
1190
|
-
|
|
1191
|
-
// ensure the matrix world is up to date
|
|
1192
|
-
const mesh = meshes[ i ];
|
|
1193
|
-
if ( mesh.visible === false ) continue;
|
|
1194
|
-
|
|
1195
|
-
mesh.updateMatrixWorld();
|
|
1196
|
-
|
|
1197
|
-
// apply the matrix world to the geometry
|
|
1198
|
-
const originalGeometry = meshes[ i ].geometry;
|
|
1199
|
-
const geometry = options.cloneGeometry ? originalGeometry.clone() : originalGeometry;
|
|
1200
|
-
geometry.applyMatrix4( mesh.matrixWorld );
|
|
1201
|
-
|
|
1202
|
-
if ( mesh.matrixWorld.determinant() < 0 ) {
|
|
1203
|
-
|
|
1204
|
-
geometry.index.array.reverse();
|
|
1205
|
-
|
|
1206
|
-
}
|
|
1153
|
+
}
|
|
1207
1154
|
|
|
1208
|
-
|
|
1209
|
-
setCommonAttributes( geometry, {
|
|
1210
|
-
attributes: options.attributes,
|
|
1211
|
-
normalMapRequired: ! ! mesh.material.normalMap,
|
|
1212
|
-
} );
|
|
1213
|
-
trimToAttributes( geometry, options.attributes );
|
|
1155
|
+
class DynamicPathTracingSceneGenerator {
|
|
1214
1156
|
|
|
1215
|
-
|
|
1216
|
-
const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, mesh.material, materials );
|
|
1217
|
-
geometry.setAttribute( 'materialIndex', materialIndexAttribute );
|
|
1157
|
+
get initialized() {
|
|
1218
1158
|
|
|
1219
|
-
|
|
1159
|
+
return Boolean( this.bvh );
|
|
1220
1160
|
|
|
1221
1161
|
}
|
|
1222
1162
|
|
|
1223
|
-
|
|
1224
|
-
materials.forEach( material => {
|
|
1225
|
-
|
|
1226
|
-
for ( const key in material ) {
|
|
1163
|
+
constructor( objects ) {
|
|
1227
1164
|
|
|
1228
|
-
|
|
1229
|
-
|
|
1165
|
+
// ensure the objects is an array
|
|
1166
|
+
if ( ! Array.isArray( objects ) ) {
|
|
1230
1167
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1168
|
+
objects = [ objects ];
|
|
1234
1169
|
|
|
1235
1170
|
}
|
|
1236
1171
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
const textures = Array.from( textureSet );
|
|
1241
|
-
return { geometry, materials, textures };
|
|
1242
|
-
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
class PathTracingSceneGenerator {
|
|
1246
|
-
|
|
1247
|
-
prepScene( scene ) {
|
|
1172
|
+
// use a dummy object for a fallback
|
|
1173
|
+
const finalObjects = [ ...objects ];
|
|
1174
|
+
if ( finalObjects.length === 0 ) {
|
|
1248
1175
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
const meshes = [];
|
|
1252
|
-
const lights = [];
|
|
1253
|
-
|
|
1254
|
-
for ( let i = 0, l = scene.length; i < l; i ++ ) {
|
|
1255
|
-
|
|
1256
|
-
scene[ i ].traverseVisible( c => {
|
|
1257
|
-
|
|
1258
|
-
if ( c.isSkinnedMesh || c.isMesh && c.morphTargetInfluences ) {
|
|
1259
|
-
|
|
1260
|
-
const generator = new StaticGeometryGenerator( c );
|
|
1261
|
-
generator.attributes = [ 'position', 'color', 'normal', 'tangent', 'uv', 'uv2' ];
|
|
1262
|
-
generator.applyWorldTransforms = false;
|
|
1263
|
-
const mesh = new Mesh(
|
|
1264
|
-
generator.generate(),
|
|
1265
|
-
c.material,
|
|
1266
|
-
);
|
|
1267
|
-
mesh.matrixWorld.copy( c.matrixWorld );
|
|
1268
|
-
mesh.matrix.copy( c.matrixWorld );
|
|
1269
|
-
mesh.matrix.decompose( mesh.position, mesh.quaternion, mesh.scale );
|
|
1270
|
-
meshes.push( mesh );
|
|
1271
|
-
|
|
1272
|
-
} else if ( c.isMesh ) {
|
|
1273
|
-
|
|
1274
|
-
meshes.push( c );
|
|
1275
|
-
|
|
1276
|
-
} else if (
|
|
1277
|
-
c.isRectAreaLight ||
|
|
1278
|
-
c.isSpotLight ||
|
|
1279
|
-
c.isPointLight ||
|
|
1280
|
-
c.isDirectionalLight
|
|
1281
|
-
) {
|
|
1282
|
-
|
|
1283
|
-
lights.push( c );
|
|
1284
|
-
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
} );
|
|
1176
|
+
finalObjects.push( getDummyMesh() );
|
|
1288
1177
|
|
|
1289
1178
|
}
|
|
1290
1179
|
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
} ),
|
|
1295
|
-
lights,
|
|
1296
|
-
};
|
|
1297
|
-
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
generate( scene, options = {} ) {
|
|
1301
|
-
|
|
1302
|
-
const { materials, textures, geometry, lights } = this.prepScene( scene );
|
|
1303
|
-
const bvhOptions = { strategy: SAH, ...options, maxLeafTris: 1 };
|
|
1304
|
-
return {
|
|
1305
|
-
scene,
|
|
1306
|
-
materials,
|
|
1307
|
-
textures,
|
|
1308
|
-
lights,
|
|
1309
|
-
bvh: new MeshBVH( geometry, bvhOptions ),
|
|
1310
|
-
};
|
|
1311
|
-
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
class DynamicPathTracingSceneGenerator {
|
|
1317
|
-
|
|
1318
|
-
get initialized() {
|
|
1319
|
-
|
|
1320
|
-
return Boolean( this.bvh );
|
|
1321
|
-
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
constructor( scene ) {
|
|
1180
|
+
// options
|
|
1181
|
+
this.bvhOptions = {};
|
|
1182
|
+
this.attributes = [ 'position', 'normal', 'tangent', 'color', 'uv', 'uv2' ];
|
|
1325
1183
|
|
|
1326
|
-
|
|
1184
|
+
// state
|
|
1185
|
+
this.objects = finalObjects;
|
|
1327
1186
|
this.bvh = null;
|
|
1328
1187
|
this.geometry = new BufferGeometry();
|
|
1329
1188
|
this.materials = null;
|
|
@@ -1347,64 +1206,75 @@ class DynamicPathTracingSceneGenerator {
|
|
|
1347
1206
|
|
|
1348
1207
|
dispose() {}
|
|
1349
1208
|
|
|
1350
|
-
|
|
1209
|
+
prepScene() {
|
|
1351
1210
|
|
|
1352
|
-
|
|
1353
|
-
if ( this.bvh === null ) {
|
|
1211
|
+
if ( this.bvh !== null ) {
|
|
1354
1212
|
|
|
1355
|
-
|
|
1213
|
+
return;
|
|
1356
1214
|
|
|
1357
|
-
|
|
1215
|
+
}
|
|
1358
1216
|
|
|
1359
|
-
|
|
1217
|
+
const { objects, staticGeometryGenerator, geometry, lights, attributes } = this;
|
|
1218
|
+
for ( let i = 0, l = objects.length; i < l; i ++ ) {
|
|
1360
1219
|
|
|
1361
|
-
|
|
1220
|
+
objects[ i ].traverse( c => {
|
|
1362
1221
|
|
|
1363
|
-
|
|
1364
|
-
setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
|
|
1222
|
+
if ( c.isMesh ) {
|
|
1365
1223
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
c.isSpotLight ||
|
|
1369
|
-
c.isPointLight ||
|
|
1370
|
-
c.isDirectionalLight
|
|
1371
|
-
) {
|
|
1224
|
+
const normalMapRequired = ! ! c.material.normalMap;
|
|
1225
|
+
setCommonAttributes( c.geometry, { attributes, normalMapRequired } );
|
|
1372
1226
|
|
|
1373
|
-
|
|
1227
|
+
} else if (
|
|
1228
|
+
c.isRectAreaLight ||
|
|
1229
|
+
c.isSpotLight ||
|
|
1230
|
+
c.isPointLight ||
|
|
1231
|
+
c.isDirectionalLight
|
|
1232
|
+
) {
|
|
1374
1233
|
|
|
1375
|
-
|
|
1234
|
+
lights.push( c );
|
|
1376
1235
|
|
|
1377
|
-
}
|
|
1236
|
+
}
|
|
1378
1237
|
|
|
1379
|
-
}
|
|
1238
|
+
} );
|
|
1380
1239
|
|
|
1381
|
-
|
|
1382
|
-
const materials = staticGeometryGenerator.getMaterials();
|
|
1383
|
-
materials.forEach( material => {
|
|
1240
|
+
}
|
|
1384
1241
|
|
|
1385
|
-
|
|
1242
|
+
const textureSet = new Set();
|
|
1243
|
+
const materials = staticGeometryGenerator.getMaterials();
|
|
1244
|
+
materials.forEach( material => {
|
|
1386
1245
|
|
|
1387
|
-
|
|
1388
|
-
if ( value && value.isTexture ) {
|
|
1246
|
+
for ( const key in material ) {
|
|
1389
1247
|
|
|
1390
|
-
|
|
1248
|
+
const value = material[ key ];
|
|
1249
|
+
if ( value && value.isTexture ) {
|
|
1391
1250
|
|
|
1392
|
-
|
|
1251
|
+
textureSet.add( value );
|
|
1393
1252
|
|
|
1394
1253
|
}
|
|
1395
1254
|
|
|
1396
|
-
}
|
|
1255
|
+
}
|
|
1397
1256
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1257
|
+
} );
|
|
1258
|
+
|
|
1259
|
+
staticGeometryGenerator.attributes = attributes;
|
|
1260
|
+
staticGeometryGenerator.generate( geometry );
|
|
1261
|
+
|
|
1262
|
+
const materialIndexAttribute = getGroupMaterialIndicesAttribute( geometry, materials, materials );
|
|
1263
|
+
geometry.setAttribute( 'materialIndex', materialIndexAttribute );
|
|
1264
|
+
geometry.clearGroups();
|
|
1265
|
+
|
|
1266
|
+
this.materials = materials;
|
|
1267
|
+
this.textures = Array.from( textureSet );
|
|
1268
|
+
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
generate() {
|
|
1400
1272
|
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
geometry.clearGroups();
|
|
1273
|
+
const { objects, staticGeometryGenerator, geometry, bvhOptions } = this;
|
|
1274
|
+
if ( this.bvh === null ) {
|
|
1404
1275
|
|
|
1405
|
-
this.
|
|
1406
|
-
this.
|
|
1407
|
-
this.textures = Array.from( textureSet );
|
|
1276
|
+
this.prepScene();
|
|
1277
|
+
this.bvh = new MeshBVH( geometry, { strategy: SAH, maxLeafTris: 1, ...bvhOptions } );
|
|
1408
1278
|
|
|
1409
1279
|
return {
|
|
1410
1280
|
lights: this.lights,
|
|
@@ -1434,6 +1304,30 @@ class DynamicPathTracingSceneGenerator {
|
|
|
1434
1304
|
|
|
1435
1305
|
}
|
|
1436
1306
|
|
|
1307
|
+
class PathTracingSceneGenerator {
|
|
1308
|
+
|
|
1309
|
+
generate( scene, options = {} ) {
|
|
1310
|
+
|
|
1311
|
+
// ensure scene transforms are up to date
|
|
1312
|
+
// TODO: remove this?
|
|
1313
|
+
if ( Array.isArray( scene ) ) {
|
|
1314
|
+
|
|
1315
|
+
scene.forEach( s => s.updateMatrixWorld( true ) );
|
|
1316
|
+
|
|
1317
|
+
} else {
|
|
1318
|
+
|
|
1319
|
+
scene.updateMatrixWorld( true );
|
|
1320
|
+
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
const generator = new DynamicPathTracingSceneGenerator( scene );
|
|
1324
|
+
generator.bvhOptions = options;
|
|
1325
|
+
return generator.generate();
|
|
1326
|
+
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1437
1331
|
// https://github.com/gkjohnson/webxr-sandbox/blob/main/skinned-mesh-batching/src/MaterialReducer.js
|
|
1438
1332
|
|
|
1439
1333
|
function isTypedArray( arr ) {
|
|
@@ -1798,8 +1692,8 @@ class ProceduralEquirectTexture extends DataTexture {
|
|
|
1798
1692
|
constructor( width = 512, height = 512 ) {
|
|
1799
1693
|
|
|
1800
1694
|
super(
|
|
1801
|
-
new
|
|
1802
|
-
width, height, RGBAFormat,
|
|
1695
|
+
new Float32Array( width * height * 4 ),
|
|
1696
|
+
width, height, RGBAFormat, FloatType, EquirectangularReflectionMapping,
|
|
1803
1697
|
RepeatWrapping, ClampToEdgeWrapping, LinearFilter, LinearFilter,
|
|
1804
1698
|
);
|
|
1805
1699
|
|
|
@@ -1831,10 +1725,10 @@ class ProceduralEquirectTexture extends DataTexture {
|
|
|
1831
1725
|
|
|
1832
1726
|
const i = y * width + x;
|
|
1833
1727
|
const i4 = 4 * i;
|
|
1834
|
-
data[ i4 + 0 ] =
|
|
1835
|
-
data[ i4 + 1 ] =
|
|
1836
|
-
data[ i4 + 2 ] =
|
|
1837
|
-
data[ i4 + 3 ] =
|
|
1728
|
+
data[ i4 + 0 ] = ( _color.r );
|
|
1729
|
+
data[ i4 + 1 ] = ( _color.g );
|
|
1730
|
+
data[ i4 + 2 ] = ( _color.b );
|
|
1731
|
+
data[ i4 + 3 ] = ( 1.0 );
|
|
1838
1732
|
|
|
1839
1733
|
}
|
|
1840
1734
|
|
|
@@ -1889,7 +1783,7 @@ class GradientEquirectTexture extends ProceduralEquirectTexture {
|
|
|
1889
1783
|
// when rendering each texture to the texture array they must have a consistent color space.
|
|
1890
1784
|
function getTextureHash( t ) {
|
|
1891
1785
|
|
|
1892
|
-
return `${ t.source.uuid }:${ t.
|
|
1786
|
+
return `${ t.source.uuid }:${ t.colorSpace }`;
|
|
1893
1787
|
|
|
1894
1788
|
}
|
|
1895
1789
|
|
|
@@ -2439,7 +2333,7 @@ class RenderTarget2DArray extends WebGLArrayRenderTarget {
|
|
|
2439
2333
|
|
|
2440
2334
|
};
|
|
2441
2335
|
|
|
2442
|
-
const fsQuad = new FullScreenQuad( new
|
|
2336
|
+
const fsQuad = new FullScreenQuad( new CopyMaterial() );
|
|
2443
2337
|
this.fsQuad = fsQuad;
|
|
2444
2338
|
|
|
2445
2339
|
}
|
|
@@ -2474,7 +2368,6 @@ class RenderTarget2DArray extends WebGLArrayRenderTarget {
|
|
|
2474
2368
|
texture.matrix.identity();
|
|
2475
2369
|
|
|
2476
2370
|
fsQuad.material.map = texture;
|
|
2477
|
-
fsQuad.material.transparent = true;
|
|
2478
2371
|
|
|
2479
2372
|
renderer.setRenderTarget( this, i );
|
|
2480
2373
|
fsQuad.render( renderer );
|
|
@@ -2504,6 +2397,54 @@ class RenderTarget2DArray extends WebGLArrayRenderTarget {
|
|
|
2504
2397
|
|
|
2505
2398
|
}
|
|
2506
2399
|
|
|
2400
|
+
class CopyMaterial extends ShaderMaterial {
|
|
2401
|
+
|
|
2402
|
+
get map() {
|
|
2403
|
+
|
|
2404
|
+
return this.uniforms.map.value;
|
|
2405
|
+
|
|
2406
|
+
}
|
|
2407
|
+
set map( v ) {
|
|
2408
|
+
|
|
2409
|
+
this.uniforms.map.value = v;
|
|
2410
|
+
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
constructor() {
|
|
2414
|
+
|
|
2415
|
+
super( {
|
|
2416
|
+
uniforms: {
|
|
2417
|
+
|
|
2418
|
+
map: { value: null },
|
|
2419
|
+
|
|
2420
|
+
},
|
|
2421
|
+
|
|
2422
|
+
vertexShader: /* glsl */`
|
|
2423
|
+
varying vec2 vUv;
|
|
2424
|
+
void main() {
|
|
2425
|
+
|
|
2426
|
+
vUv = uv;
|
|
2427
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
|
2428
|
+
|
|
2429
|
+
}
|
|
2430
|
+
`,
|
|
2431
|
+
|
|
2432
|
+
fragmentShader: /* glsl */`
|
|
2433
|
+
uniform sampler2D map;
|
|
2434
|
+
varying vec2 vUv;
|
|
2435
|
+
void main() {
|
|
2436
|
+
|
|
2437
|
+
gl_FragColor = texture2D( map, vUv );
|
|
2438
|
+
|
|
2439
|
+
}
|
|
2440
|
+
`
|
|
2441
|
+
} );
|
|
2442
|
+
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2507
2448
|
function toHalfFloatArray( f32Array ) {
|
|
2508
2449
|
|
|
2509
2450
|
const f16Array = new Uint16Array( f32Array.length );
|
|
@@ -2555,7 +2496,7 @@ function colorToLuminance( r, g, b ) {
|
|
|
2555
2496
|
}
|
|
2556
2497
|
|
|
2557
2498
|
// ensures the data is all floating point values and flipY is false
|
|
2558
|
-
function preprocessEnvMap( envMap ) {
|
|
2499
|
+
function preprocessEnvMap( envMap, targetType = HalfFloatType ) {
|
|
2559
2500
|
|
|
2560
2501
|
const map = envMap.clone();
|
|
2561
2502
|
map.source = new Source( { ...map.image } );
|
|
@@ -2564,17 +2505,54 @@ function preprocessEnvMap( envMap ) {
|
|
|
2564
2505
|
// TODO: is there a simple way to avoid cloning and adjusting the env map data here?
|
|
2565
2506
|
// convert the data from half float uint 16 arrays to float arrays for cdf computation
|
|
2566
2507
|
let newData = data;
|
|
2567
|
-
if ( map.type
|
|
2508
|
+
if ( map.type !== targetType ) {
|
|
2509
|
+
|
|
2510
|
+
if ( targetType === HalfFloatType ) {
|
|
2511
|
+
|
|
2512
|
+
newData = new Uint16Array( data.length );
|
|
2513
|
+
|
|
2514
|
+
} else {
|
|
2515
|
+
|
|
2516
|
+
newData = new Float32Array( data.length );
|
|
2517
|
+
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
let maxIntValue;
|
|
2521
|
+
if ( data instanceof Int8Array || data instanceof Int16Array || data instanceof Int32Array ) {
|
|
2522
|
+
|
|
2523
|
+
maxIntValue = 2 ** ( 8 * data.BYTES_PER_ELEMENT - 1 ) - 1;
|
|
2524
|
+
|
|
2525
|
+
} else {
|
|
2526
|
+
|
|
2527
|
+
maxIntValue = 2 ** ( 8 * data.BYTES_PER_ELEMENT ) - 1;
|
|
2528
|
+
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
for ( let i = 0, l = data.length; i < l; i ++ ) {
|
|
2532
|
+
|
|
2533
|
+
let v = data[ i ];
|
|
2534
|
+
if ( map.type === HalfFloatType ) {
|
|
2535
|
+
|
|
2536
|
+
v = DataUtils.fromHalfFloat( data[ i ] );
|
|
2537
|
+
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2540
|
+
if ( map.type !== FloatType && map.type !== HalfFloatType ) {
|
|
2541
|
+
|
|
2542
|
+
v /= maxIntValue;
|
|
2543
|
+
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
if ( targetType === HalfFloatType ) {
|
|
2568
2547
|
|
|
2569
|
-
|
|
2570
|
-
for ( const i in data ) {
|
|
2548
|
+
newData[ i ] = DataUtils.toHalfFloat( v );
|
|
2571
2549
|
|
|
2572
|
-
|
|
2550
|
+
}
|
|
2573
2551
|
|
|
2574
2552
|
}
|
|
2575
2553
|
|
|
2576
2554
|
map.image.data = newData;
|
|
2577
|
-
map.type =
|
|
2555
|
+
map.type = targetType;
|
|
2578
2556
|
|
|
2579
2557
|
}
|
|
2580
2558
|
|
|
@@ -2666,7 +2644,7 @@ class EquirectHdrInfoUniform {
|
|
|
2666
2644
|
// https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/Sampling_Light_Sources#InfiniteAreaLights
|
|
2667
2645
|
const map = preprocessEnvMap( hdr );
|
|
2668
2646
|
map.wrapS = RepeatWrapping;
|
|
2669
|
-
map.wrapT =
|
|
2647
|
+
map.wrapT = ClampToEdgeWrapping;
|
|
2670
2648
|
|
|
2671
2649
|
const { width, height, data } = map.image;
|
|
2672
2650
|
|
|
@@ -2878,6 +2856,13 @@ class LightsInfoUniformStruct {
|
|
|
2878
2856
|
const baseIndex = i * LIGHT_PIXELS * 4;
|
|
2879
2857
|
let index = 0;
|
|
2880
2858
|
|
|
2859
|
+
// initialize to 0
|
|
2860
|
+
for ( let p = 0; p < LIGHT_PIXELS; p ++ ) {
|
|
2861
|
+
|
|
2862
|
+
floatArray[ baseIndex + p ] = 0;
|
|
2863
|
+
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2881
2866
|
// sample 1
|
|
2882
2867
|
// position
|
|
2883
2868
|
l.getWorldPosition( v );
|
|
@@ -2942,7 +2927,7 @@ class LightsInfoUniformStruct {
|
|
|
2942
2927
|
|
|
2943
2928
|
} else if ( l.isSpotLight ) {
|
|
2944
2929
|
|
|
2945
|
-
const radius = l.radius;
|
|
2930
|
+
const radius = l.radius || 0;
|
|
2946
2931
|
eye.setFromMatrixPosition( l.matrixWorld );
|
|
2947
2932
|
target.setFromMatrixPosition( l.target.matrixWorld );
|
|
2948
2933
|
m.lookAt( eye, target, up );
|
|
@@ -2972,24 +2957,21 @@ class LightsInfoUniformStruct {
|
|
|
2972
2957
|
// radius
|
|
2973
2958
|
floatArray[ baseIndex + ( index ++ ) ] = radius;
|
|
2974
2959
|
|
|
2975
|
-
// near
|
|
2976
|
-
floatArray[ baseIndex + ( index ++ ) ] = l.shadow.camera.near;
|
|
2977
|
-
|
|
2978
2960
|
// decay
|
|
2979
2961
|
floatArray[ baseIndex + ( index ++ ) ] = l.decay;
|
|
2980
2962
|
|
|
2981
2963
|
// distance
|
|
2982
2964
|
floatArray[ baseIndex + ( index ++ ) ] = l.distance;
|
|
2983
2965
|
|
|
2984
|
-
// sample 6
|
|
2985
2966
|
// coneCos
|
|
2986
2967
|
floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle );
|
|
2987
2968
|
|
|
2969
|
+
// sample 6
|
|
2988
2970
|
// penumbraCos
|
|
2989
2971
|
floatArray[ baseIndex + ( index ++ ) ] = Math.cos( l.angle * ( 1 - l.penumbra ) );
|
|
2990
2972
|
|
|
2991
2973
|
// iesProfile
|
|
2992
|
-
floatArray[ baseIndex + ( index ++ ) ] = iesTextures.indexOf( l.iesTexture );
|
|
2974
|
+
floatArray[ baseIndex + ( index ++ ) ] = l.iesTexture ? iesTextures.indexOf( l.iesTexture ) : - 1;
|
|
2993
2975
|
|
|
2994
2976
|
} else if ( l.isPointLight ) {
|
|
2995
2977
|
|
|
@@ -3006,7 +2988,7 @@ class LightsInfoUniformStruct {
|
|
|
3006
2988
|
index += 4;
|
|
3007
2989
|
|
|
3008
2990
|
// sample 5
|
|
3009
|
-
index +=
|
|
2991
|
+
index += 1;
|
|
3010
2992
|
|
|
3011
2993
|
floatArray[ baseIndex + ( index ++ ) ] = l.decay;
|
|
3012
2994
|
floatArray[ baseIndex + ( index ++ ) ] = l.distance;
|
|
@@ -4420,18 +4402,16 @@ const lightsStructGLSL = /* glsl */`
|
|
|
4420
4402
|
vec4 s4 = texelFetch1D( tex, i + 4u );
|
|
4421
4403
|
vec4 s5 = texelFetch1D( tex, i + 5u );
|
|
4422
4404
|
l.radius = s4.r;
|
|
4423
|
-
l.
|
|
4424
|
-
l.
|
|
4425
|
-
l.
|
|
4405
|
+
l.decay = s4.g;
|
|
4406
|
+
l.distance = s4.b;
|
|
4407
|
+
l.coneCos = s4.a;
|
|
4426
4408
|
|
|
4427
|
-
l.
|
|
4428
|
-
l.
|
|
4429
|
-
l.iesProfile = int( round( s5.b ) );
|
|
4409
|
+
l.penumbraCos = s5.r;
|
|
4410
|
+
l.iesProfile = int( round( s5.g ) );
|
|
4430
4411
|
|
|
4431
4412
|
} else {
|
|
4432
4413
|
|
|
4433
4414
|
l.radius = 0.0;
|
|
4434
|
-
l.near = 0.0;
|
|
4435
4415
|
l.decay = 0.0;
|
|
4436
4416
|
l.distance = 0.0;
|
|
4437
4417
|
|
|
@@ -5149,15 +5129,17 @@ const bsdfSamplingGLSL = /* glsl */`
|
|
|
5149
5129
|
|
|
5150
5130
|
// TODO: subsurface approx?
|
|
5151
5131
|
|
|
5152
|
-
float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
5132
|
+
// float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
5133
|
+
float F = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
|
|
5153
5134
|
color = ( 1.0 - F ) * transFactor * metalFactor * wi.z * surf.color * ( retro + lambert ) / PI;
|
|
5135
|
+
|
|
5154
5136
|
return wi.z / PI;
|
|
5155
5137
|
|
|
5156
5138
|
}
|
|
5157
5139
|
|
|
5158
5140
|
vec3 diffuseDirection( vec3 wo, SurfaceRecord surf ) {
|
|
5159
5141
|
|
|
5160
|
-
vec3 lightDirection = sampleSphere(
|
|
5142
|
+
vec3 lightDirection = sampleSphere( rand2( 11 ) );
|
|
5161
5143
|
lightDirection.z += 1.0;
|
|
5162
5144
|
lightDirection = normalize( lightDirection );
|
|
5163
5145
|
|
|
@@ -5202,7 +5184,7 @@ const bsdfSamplingGLSL = /* glsl */`
|
|
|
5202
5184
|
vec3 halfVector = ggxDirection(
|
|
5203
5185
|
wo,
|
|
5204
5186
|
vec2( roughness ),
|
|
5205
|
-
|
|
5187
|
+
rand2( 12 )
|
|
5206
5188
|
);
|
|
5207
5189
|
|
|
5208
5190
|
// apply to new ray by reflecting off the new normal
|
|
@@ -5239,7 +5221,7 @@ const bsdfSamplingGLSL = /* glsl */`
|
|
|
5239
5221
|
vec3 halfVector = ggxDirection(
|
|
5240
5222
|
wo,
|
|
5241
5223
|
vec2( filteredRoughness ),
|
|
5242
|
-
|
|
5224
|
+
rand2( 13 )
|
|
5243
5225
|
);
|
|
5244
5226
|
|
|
5245
5227
|
vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
|
|
@@ -5261,7 +5243,8 @@ const bsdfSamplingGLSL = /* glsl */`
|
|
|
5261
5243
|
color = surf.transmission * surf.color;
|
|
5262
5244
|
|
|
5263
5245
|
// PDF
|
|
5264
|
-
float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
5246
|
+
// float F = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
5247
|
+
float F = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
|
|
5265
5248
|
if ( F >= 1.0 ) {
|
|
5266
5249
|
|
|
5267
5250
|
return 0.0;
|
|
@@ -5276,7 +5259,7 @@ const bsdfSamplingGLSL = /* glsl */`
|
|
|
5276
5259
|
|
|
5277
5260
|
float roughness = surf.filteredRoughness;
|
|
5278
5261
|
float eta = surf.eta;
|
|
5279
|
-
vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere(
|
|
5262
|
+
vec3 halfVector = normalize( vec3( 0.0, 0.0, 1.0 ) + sampleSphere( rand2( 13 ) ) * roughness );
|
|
5280
5263
|
vec3 lightDirection = refract( normalize( - wo ), halfVector, eta );
|
|
5281
5264
|
|
|
5282
5265
|
if ( surf.thinFilm ) {
|
|
@@ -5317,7 +5300,7 @@ const bsdfSamplingGLSL = /* glsl */`
|
|
|
5317
5300
|
vec3 halfVector = ggxDirection(
|
|
5318
5301
|
wo,
|
|
5319
5302
|
vec2( roughness ),
|
|
5320
|
-
|
|
5303
|
+
rand2( 14 )
|
|
5321
5304
|
);
|
|
5322
5305
|
|
|
5323
5306
|
// apply to new ray by reflecting off the new normal
|
|
@@ -5352,7 +5335,8 @@ const bsdfSamplingGLSL = /* glsl */`
|
|
|
5352
5335
|
|
|
5353
5336
|
float metalness = surf.metalness;
|
|
5354
5337
|
float transmission = surf.transmission;
|
|
5355
|
-
float fEstimate = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
5338
|
+
// float fEstimate = evaluateFresnelWeight( dot( wo, wh ), surf.eta, surf.f0 );
|
|
5339
|
+
float fEstimate = disneyFresnel( wo, wi, wh, surf.f0, surf.eta, surf.metalness );
|
|
5356
5340
|
|
|
5357
5341
|
float transSpecularProb = mix( max( 0.25, fEstimate ), 1.0, metalness );
|
|
5358
5342
|
float diffSpecularProb = 0.5 + 0.5 * metalness;
|
|
@@ -5468,7 +5452,7 @@ const bsdfSamplingGLSL = /* glsl */`
|
|
|
5468
5452
|
ScatterRecord sampleRec;
|
|
5469
5453
|
sampleRec.specularPdf = 0.0;
|
|
5470
5454
|
sampleRec.pdf = 1.0 / ( 4.0 * PI );
|
|
5471
|
-
sampleRec.direction = sampleSphere(
|
|
5455
|
+
sampleRec.direction = sampleSphere( rand2( 16 ) );
|
|
5472
5456
|
sampleRec.color = surf.color / ( 4.0 * PI );
|
|
5473
5457
|
return sampleRec;
|
|
5474
5458
|
|
|
@@ -5520,7 +5504,7 @@ const bsdfSamplingGLSL = /* glsl */`
|
|
|
5520
5504
|
vec3 wi;
|
|
5521
5505
|
vec3 clearcoatWi;
|
|
5522
5506
|
|
|
5523
|
-
float r =
|
|
5507
|
+
float r = rand( 15 );
|
|
5524
5508
|
if ( r <= cdf[0] ) { // diffuse
|
|
5525
5509
|
|
|
5526
5510
|
wi = diffuseDirection( wo, surf );
|
|
@@ -6159,32 +6143,38 @@ const fresnelGLSL = /* glsl */`
|
|
|
6159
6143
|
|
|
6160
6144
|
}
|
|
6161
6145
|
|
|
6162
|
-
|
|
6146
|
+
// TODO: disney fresnel was removed and replaced with this fresnel function to better align with
|
|
6147
|
+
// the glTF but is causing blown out pixels. Should be revisited
|
|
6148
|
+
// float evaluateFresnelWeight( float cosTheta, float eta, float f0 ) {
|
|
6163
6149
|
|
|
6164
|
-
|
|
6150
|
+
// if ( totalInternalReflection( cosTheta, eta ) ) {
|
|
6165
6151
|
|
|
6166
|
-
|
|
6152
|
+
// return 1.0;
|
|
6167
6153
|
|
|
6168
|
-
|
|
6154
|
+
// }
|
|
6169
6155
|
|
|
6170
|
-
|
|
6156
|
+
// return schlickFresnel( cosTheta, f0 );
|
|
6171
6157
|
|
|
6172
|
-
}
|
|
6158
|
+
// }
|
|
6173
6159
|
|
|
6174
|
-
/*
|
|
6175
6160
|
// https://schuttejoe.github.io/post/disneybsdf/
|
|
6176
6161
|
float disneyFresnel( vec3 wo, vec3 wi, vec3 wh, float f0, float eta, float metalness ) {
|
|
6177
6162
|
|
|
6178
6163
|
float dotHV = dot( wo, wh );
|
|
6179
|
-
|
|
6164
|
+
if ( totalInternalReflection( dotHV, eta ) ) {
|
|
6165
|
+
|
|
6166
|
+
return 1.0;
|
|
6167
|
+
|
|
6168
|
+
}
|
|
6180
6169
|
|
|
6170
|
+
float dotHL = dot( wi, wh );
|
|
6181
6171
|
float dielectricFresnel = dielectricFresnel( abs( dotHV ), eta );
|
|
6182
6172
|
float metallicFresnel = schlickFresnel( dotHL, f0 );
|
|
6183
6173
|
|
|
6184
6174
|
return mix( dielectricFresnel, metallicFresnel, metalness );
|
|
6185
6175
|
|
|
6186
6176
|
}
|
|
6187
|
-
|
|
6177
|
+
|
|
6188
6178
|
`;
|
|
6189
6179
|
|
|
6190
6180
|
const arraySamplerTexelFetchGLSL = /*glsl */`
|
|
@@ -6241,28 +6231,28 @@ const pcgGLSL = /* glsl */`
|
|
|
6241
6231
|
}
|
|
6242
6232
|
|
|
6243
6233
|
// returns [ 0, 1 ]
|
|
6244
|
-
float
|
|
6234
|
+
float pcgRand() {
|
|
6245
6235
|
|
|
6246
6236
|
pcg4d( WHITE_NOISE_SEED );
|
|
6247
6237
|
return float( WHITE_NOISE_SEED.x ) / float( 0xffffffffu );
|
|
6248
6238
|
|
|
6249
6239
|
}
|
|
6250
6240
|
|
|
6251
|
-
vec2
|
|
6241
|
+
vec2 pcgRand2() {
|
|
6252
6242
|
|
|
6253
6243
|
pcg4d( WHITE_NOISE_SEED );
|
|
6254
6244
|
return vec2( WHITE_NOISE_SEED.xy ) / float(0xffffffffu);
|
|
6255
6245
|
|
|
6256
6246
|
}
|
|
6257
6247
|
|
|
6258
|
-
vec3
|
|
6248
|
+
vec3 pcgRand3() {
|
|
6259
6249
|
|
|
6260
6250
|
pcg4d( WHITE_NOISE_SEED );
|
|
6261
6251
|
return vec3( WHITE_NOISE_SEED.xyz ) / float( 0xffffffffu );
|
|
6262
6252
|
|
|
6263
6253
|
}
|
|
6264
6254
|
|
|
6265
|
-
vec4
|
|
6255
|
+
vec4 pcgRand4() {
|
|
6266
6256
|
|
|
6267
6257
|
pcg4d( WHITE_NOISE_SEED );
|
|
6268
6258
|
return vec4( WHITE_NOISE_SEED ) / float( 0xffffffffu );
|
|
@@ -6335,7 +6325,7 @@ const cameraUtilsGLSL = /* glsl */`
|
|
|
6335
6325
|
|
|
6336
6326
|
// Jitter the camera ray by finding a uv coordinate at a random sample
|
|
6337
6327
|
// around this pixel's UV coordinate for AA
|
|
6338
|
-
vec2 ruv =
|
|
6328
|
+
vec2 ruv = rand2( 0 );
|
|
6339
6329
|
vec2 jitteredUv = vUv + vec2( tentFilter( ruv.x ) * ssd.x, tentFilter( ruv.y ) * ssd.y );
|
|
6340
6330
|
Ray ray;
|
|
6341
6331
|
|
|
@@ -6380,7 +6370,7 @@ const cameraUtilsGLSL = /* glsl */`
|
|
|
6380
6370
|
|
|
6381
6371
|
// get the aperture sample
|
|
6382
6372
|
// if blades === 0 then we assume a circle
|
|
6383
|
-
vec3 shapeUVW=
|
|
6373
|
+
vec3 shapeUVW= rand3( 1 );
|
|
6384
6374
|
int blades = physicalCamera.apertureBlades;
|
|
6385
6375
|
float anamorphicRatio = physicalCamera.anamorphicRatio;
|
|
6386
6376
|
vec2 apertureSample = blades == 0 ? sampleCircle( shapeUVW.xy ) : sampleRegularPolygon( blades, shapeUVW );
|
|
@@ -6416,6 +6406,9 @@ const attenuateHitGLSL = /* glsl */`
|
|
|
6416
6406
|
out vec3 color
|
|
6417
6407
|
) {
|
|
6418
6408
|
|
|
6409
|
+
// store the original bounce index so we can reset it after
|
|
6410
|
+
uint originalBounceIndex = sobolBounceIndex;
|
|
6411
|
+
|
|
6419
6412
|
int traversals = state.traversals;
|
|
6420
6413
|
int transmissiveTraversals = state.transmissiveTraversals;
|
|
6421
6414
|
bool isShadowRay = state.isShadowRay;
|
|
@@ -6428,22 +6421,25 @@ const attenuateHitGLSL = /* glsl */`
|
|
|
6428
6421
|
|
|
6429
6422
|
color = vec3( 1.0 );
|
|
6430
6423
|
|
|
6431
|
-
|
|
6432
|
-
// and then reset.
|
|
6424
|
+
bool result = true;
|
|
6433
6425
|
for ( int i = 0; i < traversals; i ++ ) {
|
|
6434
6426
|
|
|
6427
|
+
sobolBounceIndex ++;
|
|
6428
|
+
|
|
6435
6429
|
int hitType = traceScene( ray, fogMaterial, surfaceHit );
|
|
6436
6430
|
|
|
6437
6431
|
if ( hitType == FOG_HIT ) {
|
|
6438
6432
|
|
|
6439
|
-
|
|
6433
|
+
result = true;
|
|
6434
|
+
break;
|
|
6440
6435
|
|
|
6441
6436
|
} else if ( hitType == SURFACE_HIT ) {
|
|
6442
6437
|
|
|
6443
6438
|
float totalDist = distance( startPoint, ray.origin + ray.direction * surfaceHit.dist );
|
|
6444
6439
|
if ( totalDist > rayDist ) {
|
|
6445
6440
|
|
|
6446
|
-
|
|
6441
|
+
result = false;
|
|
6442
|
+
break;
|
|
6447
6443
|
|
|
6448
6444
|
}
|
|
6449
6445
|
|
|
@@ -6525,7 +6521,7 @@ const attenuateHitGLSL = /* glsl */`
|
|
|
6525
6521
|
bool useAlphaTest = alphaTest != 0.0;
|
|
6526
6522
|
float transmissionFactor = ( 1.0 - metalness ) * transmission;
|
|
6527
6523
|
if (
|
|
6528
|
-
transmissionFactor < rand() && ! (
|
|
6524
|
+
transmissionFactor < rand( 9 ) && ! (
|
|
6529
6525
|
// material sidedness
|
|
6530
6526
|
material.side != 0.0 && surfaceHit.side == material.side
|
|
6531
6527
|
|
|
@@ -6533,11 +6529,12 @@ const attenuateHitGLSL = /* glsl */`
|
|
|
6533
6529
|
|| useAlphaTest && albedo.a < alphaTest
|
|
6534
6530
|
|
|
6535
6531
|
// opacity
|
|
6536
|
-
|| material.transparent && ! useAlphaTest && albedo.a < rand()
|
|
6532
|
+
|| material.transparent && ! useAlphaTest && albedo.a < rand( 10 )
|
|
6537
6533
|
)
|
|
6538
6534
|
) {
|
|
6539
6535
|
|
|
6540
|
-
|
|
6536
|
+
result = true;
|
|
6537
|
+
break;
|
|
6541
6538
|
|
|
6542
6539
|
}
|
|
6543
6540
|
|
|
@@ -6563,13 +6560,16 @@ const attenuateHitGLSL = /* glsl */`
|
|
|
6563
6560
|
|
|
6564
6561
|
} else {
|
|
6565
6562
|
|
|
6566
|
-
|
|
6563
|
+
result = false;
|
|
6564
|
+
break;
|
|
6567
6565
|
|
|
6568
6566
|
}
|
|
6569
6567
|
|
|
6570
6568
|
}
|
|
6571
6569
|
|
|
6572
|
-
|
|
6570
|
+
// reset the bounce index
|
|
6571
|
+
sobolBounceIndex = originalBounceIndex;
|
|
6572
|
+
return result;
|
|
6573
6573
|
|
|
6574
6574
|
}
|
|
6575
6575
|
|
|
@@ -6600,7 +6600,7 @@ const traceSceneGLSL = /* glsl */`
|
|
|
6600
6600
|
|
|
6601
6601
|
// offset the distance so we don't run into issues with particles on the same surface
|
|
6602
6602
|
// as other objects
|
|
6603
|
-
float particleDist = intersectFogVolume( fogMaterial,
|
|
6603
|
+
float particleDist = intersectFogVolume( fogMaterial, rand( 1 ) );
|
|
6604
6604
|
if ( particleDist + RAY_OFFSET < surfaceHit.dist ) {
|
|
6605
6605
|
|
|
6606
6606
|
surfaceHit.side = 1.0;
|
|
@@ -6663,6 +6663,7 @@ const getSurfaceRecordGLSL = /* glsl */`
|
|
|
6663
6663
|
|
|
6664
6664
|
vec3 uvPrime = material.mapTransform * vec3( uv, 1 );
|
|
6665
6665
|
albedo *= texture2D( textures, vec3( uvPrime.xy, material.map ) );
|
|
6666
|
+
|
|
6666
6667
|
}
|
|
6667
6668
|
|
|
6668
6669
|
if ( material.vertexColors ) {
|
|
@@ -6694,7 +6695,7 @@ const getSurfaceRecordGLSL = /* glsl */`
|
|
|
6694
6695
|
|| useAlphaTest && albedo.a < alphaTest
|
|
6695
6696
|
|
|
6696
6697
|
// opacity
|
|
6697
|
-
|| material.transparent && ! useAlphaTest && albedo.a <
|
|
6698
|
+
|| material.transparent && ! useAlphaTest && albedo.a < rand( 3 )
|
|
6698
6699
|
) {
|
|
6699
6700
|
|
|
6700
6701
|
return SKIP_SURFACE;
|
|
@@ -6956,10 +6957,10 @@ const directLightContributionGLSL = /*glsl*/`
|
|
|
6956
6957
|
vec3 result = vec3( 0.0 );
|
|
6957
6958
|
|
|
6958
6959
|
// uniformly pick a light or environment map
|
|
6959
|
-
if( lightsDenom != 0.0 &&
|
|
6960
|
+
if( lightsDenom != 0.0 && rand( 5 ) < float( lights.count ) / lightsDenom ) {
|
|
6960
6961
|
|
|
6961
6962
|
// sample a light or environment
|
|
6962
|
-
LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin,
|
|
6963
|
+
LightRecord lightRec = randomLightSample( lights.tex, iesProfiles, lights.count, rayOrigin, rand3( 6 ) );
|
|
6963
6964
|
|
|
6964
6965
|
bool isSampleBelowSurface = ! surf.volumeParticle && dot( surf.faceNormal, lightRec.direction ) < 0.0;
|
|
6965
6966
|
if ( isSampleBelowSurface ) {
|
|
@@ -6998,7 +6999,7 @@ const directLightContributionGLSL = /*glsl*/`
|
|
|
6998
6999
|
|
|
6999
7000
|
// find a sample in the environment map to include in the contribution
|
|
7000
7001
|
vec3 envColor, envDirection;
|
|
7001
|
-
float envPdf = sampleEquirectProbability(
|
|
7002
|
+
float envPdf = sampleEquirectProbability( rand2( 7 ), envColor, envDirection );
|
|
7002
7003
|
envDirection = invEnvRotation3x3 * envDirection;
|
|
7003
7004
|
|
|
7004
7005
|
// this env sampling is not set up for transmissive sampling and yields overly bright
|
|
@@ -7047,7 +7048,668 @@ const directLightContributionGLSL = /*glsl*/`
|
|
|
7047
7048
|
|
|
7048
7049
|
`;
|
|
7049
7050
|
|
|
7050
|
-
|
|
7051
|
+
const stratifiedTextureGLSL = /* glsl */`
|
|
7052
|
+
|
|
7053
|
+
uniform sampler2D stratifiedTexture;
|
|
7054
|
+
uniform sampler2D stratifiedOffsetTexture;
|
|
7055
|
+
|
|
7056
|
+
uint sobolPixelIndex = 0u;
|
|
7057
|
+
uint sobolPathIndex = 0u;
|
|
7058
|
+
uint sobolBounceIndex = 0u;
|
|
7059
|
+
vec4 pixelSeed = vec4( 0 );
|
|
7060
|
+
|
|
7061
|
+
vec4 rand4( int v ) {
|
|
7062
|
+
|
|
7063
|
+
ivec2 uv = ivec2( v, sobolBounceIndex );
|
|
7064
|
+
vec4 stratifiedSample = texelFetch( stratifiedTexture, uv, 0 );
|
|
7065
|
+
return fract( stratifiedSample + pixelSeed.r ); // blue noise + stratified samples
|
|
7066
|
+
|
|
7067
|
+
}
|
|
7068
|
+
|
|
7069
|
+
vec3 rand3( int v ) {
|
|
7070
|
+
|
|
7071
|
+
return rand4( v ).xyz;
|
|
7072
|
+
|
|
7073
|
+
}
|
|
7074
|
+
|
|
7075
|
+
vec2 rand2( int v ) {
|
|
7076
|
+
|
|
7077
|
+
return rand4( v ).xy;
|
|
7078
|
+
|
|
7079
|
+
}
|
|
7080
|
+
|
|
7081
|
+
float rand( int v ) {
|
|
7082
|
+
|
|
7083
|
+
return rand4( v ).x;
|
|
7084
|
+
|
|
7085
|
+
}
|
|
7086
|
+
|
|
7087
|
+
void rng_initialize( vec2 screenCoord, int frame ) {
|
|
7088
|
+
|
|
7089
|
+
// tile the small noise texture across the entire screen
|
|
7090
|
+
ivec2 noiseSize = ivec2( textureSize( stratifiedOffsetTexture, 0 ) );
|
|
7091
|
+
pixelSeed = texelFetch( stratifiedOffsetTexture, ivec2( screenCoord.xy ) % noiseSize, 0 );
|
|
7092
|
+
|
|
7093
|
+
}
|
|
7094
|
+
|
|
7095
|
+
`;
|
|
7096
|
+
|
|
7097
|
+
// Stratified Sampling based on implementation from hoverinc pathtracer
|
|
7098
|
+
// - https://github.com/hoverinc/ray-tracing-renderer
|
|
7099
|
+
// - http://www.pbr-book.org/3ed-2018/Sampling_and_Reconstruction/Stratified_Sampling.html
|
|
7100
|
+
|
|
7101
|
+
function shuffle( arr ) {
|
|
7102
|
+
|
|
7103
|
+
for ( let i = arr.length - 1; i > 0; i -- ) {
|
|
7104
|
+
|
|
7105
|
+
const j = Math.floor( Math.random() * ( i + 1 ) );
|
|
7106
|
+
const x = arr[ i ];
|
|
7107
|
+
arr[ i ] = arr[ j ];
|
|
7108
|
+
arr[ j ] = x;
|
|
7109
|
+
|
|
7110
|
+
}
|
|
7111
|
+
|
|
7112
|
+
return arr;
|
|
7113
|
+
|
|
7114
|
+
}
|
|
7115
|
+
|
|
7116
|
+
// strataCount : The number of bins per dimension
|
|
7117
|
+
// dimensions : The number of dimensions to generate stratified values for
|
|
7118
|
+
class StratifiedSampler {
|
|
7119
|
+
|
|
7120
|
+
constructor( strataCount, dimensions ) {
|
|
7121
|
+
|
|
7122
|
+
const l = strataCount ** dimensions;
|
|
7123
|
+
const strata = new Uint16Array( l );
|
|
7124
|
+
let index = l;
|
|
7125
|
+
|
|
7126
|
+
// each integer represents a statum bin
|
|
7127
|
+
for ( let i = 0; i < l; i ++ ) {
|
|
7128
|
+
|
|
7129
|
+
strata[ i ] = i;
|
|
7130
|
+
|
|
7131
|
+
}
|
|
7132
|
+
|
|
7133
|
+
this.samples = new Float32Array( dimensions );
|
|
7134
|
+
|
|
7135
|
+
this.strataCount = strataCount;
|
|
7136
|
+
|
|
7137
|
+
this.restart = function () {
|
|
7138
|
+
|
|
7139
|
+
index = 0;
|
|
7140
|
+
|
|
7141
|
+
};
|
|
7142
|
+
|
|
7143
|
+
this.next = function () {
|
|
7144
|
+
|
|
7145
|
+
const { samples } = this;
|
|
7146
|
+
|
|
7147
|
+
if ( index >= strata.length ) {
|
|
7148
|
+
|
|
7149
|
+
shuffle( strata );
|
|
7150
|
+
this.restart();
|
|
7151
|
+
|
|
7152
|
+
}
|
|
7153
|
+
|
|
7154
|
+
let stratum = strata[ index ++ ];
|
|
7155
|
+
|
|
7156
|
+
for ( let i = 0; i < dimensions; i ++ ) {
|
|
7157
|
+
|
|
7158
|
+
samples[ i ] = ( stratum % strataCount + Math.random() ) / strataCount;
|
|
7159
|
+
stratum = Math.floor( stratum / strataCount );
|
|
7160
|
+
|
|
7161
|
+
}
|
|
7162
|
+
|
|
7163
|
+
return samples;
|
|
7164
|
+
|
|
7165
|
+
};
|
|
7166
|
+
|
|
7167
|
+
}
|
|
7168
|
+
|
|
7169
|
+
}
|
|
7170
|
+
|
|
7171
|
+
// Stratified Sampling based on implementation from hoverinc pathtracer
|
|
7172
|
+
|
|
7173
|
+
// Stratified set of data with each tuple stratified separately and combined
|
|
7174
|
+
class StratifiedSamplerCombined {
|
|
7175
|
+
|
|
7176
|
+
constructor( strataCount, listOfDimensions ) {
|
|
7177
|
+
|
|
7178
|
+
let totalDim = 0;
|
|
7179
|
+
for ( const dim of listOfDimensions ) {
|
|
7180
|
+
|
|
7181
|
+
totalDim += dim;
|
|
7182
|
+
|
|
7183
|
+
}
|
|
7184
|
+
|
|
7185
|
+
const combined = new Float32Array( totalDim );
|
|
7186
|
+
const strataObjs = [];
|
|
7187
|
+
let offset = 0;
|
|
7188
|
+
for ( const dim of listOfDimensions ) {
|
|
7189
|
+
|
|
7190
|
+
const sampler = new StratifiedSampler( strataCount, dim );
|
|
7191
|
+
sampler.samples = new Float32Array( combined.buffer, offset, sampler.samples.length );
|
|
7192
|
+
offset += sampler.samples.length * 4;
|
|
7193
|
+
strataObjs.push( sampler );
|
|
7194
|
+
|
|
7195
|
+
}
|
|
7196
|
+
|
|
7197
|
+
this.samples = combined;
|
|
7198
|
+
|
|
7199
|
+
this.strataCount = strataCount;
|
|
7200
|
+
|
|
7201
|
+
this.next = function () {
|
|
7202
|
+
|
|
7203
|
+
for ( const strata of strataObjs ) {
|
|
7204
|
+
|
|
7205
|
+
strata.next();
|
|
7206
|
+
|
|
7207
|
+
}
|
|
7208
|
+
|
|
7209
|
+
return combined;
|
|
7210
|
+
|
|
7211
|
+
};
|
|
7212
|
+
|
|
7213
|
+
this.restart = function () {
|
|
7214
|
+
|
|
7215
|
+
for ( const strata of strataObjs ) {
|
|
7216
|
+
|
|
7217
|
+
strata.restart();
|
|
7218
|
+
|
|
7219
|
+
}
|
|
7220
|
+
|
|
7221
|
+
};
|
|
7222
|
+
|
|
7223
|
+
}
|
|
7224
|
+
|
|
7225
|
+
}
|
|
7226
|
+
|
|
7227
|
+
class StratifiedSamplesTexture extends DataTexture {
|
|
7228
|
+
|
|
7229
|
+
constructor( count = 1, depth = 1, strata = 8 ) {
|
|
7230
|
+
|
|
7231
|
+
super( new Float32Array( 1 ), 1, 1, RGBAFormat, FloatType );
|
|
7232
|
+
this.minFilter = NearestFilter;
|
|
7233
|
+
this.magFilter = NearestFilter;
|
|
7234
|
+
|
|
7235
|
+
this.strata = strata;
|
|
7236
|
+
this.sampler = null;
|
|
7237
|
+
|
|
7238
|
+
this.init( count, depth, strata );
|
|
7239
|
+
|
|
7240
|
+
}
|
|
7241
|
+
|
|
7242
|
+
init( count, depth, strata = this.strata ) {
|
|
7243
|
+
|
|
7244
|
+
const { image } = this;
|
|
7245
|
+
if ( image.width === depth && image.height === count ) {
|
|
7246
|
+
|
|
7247
|
+
return;
|
|
7248
|
+
|
|
7249
|
+
}
|
|
7250
|
+
|
|
7251
|
+
const dimensions = new Array( count * depth ).fill( 4 );
|
|
7252
|
+
const sampler = new StratifiedSamplerCombined( strata, dimensions );
|
|
7253
|
+
|
|
7254
|
+
image.width = depth;
|
|
7255
|
+
image.height = count;
|
|
7256
|
+
image.data = sampler.samples;
|
|
7257
|
+
|
|
7258
|
+
this.sampler = sampler;
|
|
7259
|
+
|
|
7260
|
+
this.dispose();
|
|
7261
|
+
this.next();
|
|
7262
|
+
|
|
7263
|
+
}
|
|
7264
|
+
|
|
7265
|
+
next() {
|
|
7266
|
+
|
|
7267
|
+
this.sampler.next();
|
|
7268
|
+
this.needsUpdate = true;
|
|
7269
|
+
|
|
7270
|
+
}
|
|
7271
|
+
|
|
7272
|
+
}
|
|
7273
|
+
|
|
7274
|
+
function shuffleArray( array, random = Math.random ) {
|
|
7275
|
+
|
|
7276
|
+
for ( let i = array.length - 1; i > 0; i -- ) {
|
|
7277
|
+
|
|
7278
|
+
const replaceIndex = ~ ~ ( ( random() - 1e-6 ) * i );
|
|
7279
|
+
const tmp = array[ i ];
|
|
7280
|
+
array[ i ] = array[ replaceIndex ];
|
|
7281
|
+
array[ replaceIndex ] = tmp;
|
|
7282
|
+
|
|
7283
|
+
}
|
|
7284
|
+
|
|
7285
|
+
}
|
|
7286
|
+
|
|
7287
|
+
function fillWithOnes( array, count ) {
|
|
7288
|
+
|
|
7289
|
+
array.fill( 0 );
|
|
7290
|
+
|
|
7291
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
7292
|
+
|
|
7293
|
+
array[ i ] = 1;
|
|
7294
|
+
|
|
7295
|
+
}
|
|
7296
|
+
|
|
7297
|
+
}
|
|
7298
|
+
|
|
7299
|
+
class BlueNoiseSamples {
|
|
7300
|
+
|
|
7301
|
+
constructor( size ) {
|
|
7302
|
+
|
|
7303
|
+
this.count = 0;
|
|
7304
|
+
this.size = - 1;
|
|
7305
|
+
this.sigma = - 1;
|
|
7306
|
+
this.radius = - 1;
|
|
7307
|
+
this.lookupTable = null;
|
|
7308
|
+
this.score = null;
|
|
7309
|
+
this.binaryPattern = null;
|
|
7310
|
+
|
|
7311
|
+
this.resize( size );
|
|
7312
|
+
this.setSigma( 1.5 );
|
|
7313
|
+
|
|
7314
|
+
}
|
|
7315
|
+
|
|
7316
|
+
findVoid() {
|
|
7317
|
+
|
|
7318
|
+
const { score, binaryPattern } = this;
|
|
7319
|
+
|
|
7320
|
+
let currValue = Infinity;
|
|
7321
|
+
let currIndex = - 1;
|
|
7322
|
+
for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
|
|
7323
|
+
|
|
7324
|
+
if ( binaryPattern[ i ] !== 0 ) {
|
|
7325
|
+
|
|
7326
|
+
continue;
|
|
7327
|
+
|
|
7328
|
+
}
|
|
7329
|
+
|
|
7330
|
+
const pScore = score[ i ];
|
|
7331
|
+
if ( pScore < currValue ) {
|
|
7332
|
+
|
|
7333
|
+
currValue = pScore;
|
|
7334
|
+
currIndex = i;
|
|
7335
|
+
|
|
7336
|
+
}
|
|
7337
|
+
|
|
7338
|
+
}
|
|
7339
|
+
|
|
7340
|
+
return currIndex;
|
|
7341
|
+
|
|
7342
|
+
}
|
|
7343
|
+
|
|
7344
|
+
findCluster() {
|
|
7345
|
+
|
|
7346
|
+
const { score, binaryPattern } = this;
|
|
7347
|
+
|
|
7348
|
+
let currValue = - Infinity;
|
|
7349
|
+
let currIndex = - 1;
|
|
7350
|
+
for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
|
|
7351
|
+
|
|
7352
|
+
if ( binaryPattern[ i ] !== 1 ) {
|
|
7353
|
+
|
|
7354
|
+
continue;
|
|
7355
|
+
|
|
7356
|
+
}
|
|
7357
|
+
|
|
7358
|
+
const pScore = score[ i ];
|
|
7359
|
+
if ( pScore > currValue ) {
|
|
7360
|
+
|
|
7361
|
+
currValue = pScore;
|
|
7362
|
+
currIndex = i;
|
|
7363
|
+
|
|
7364
|
+
}
|
|
7365
|
+
|
|
7366
|
+
}
|
|
7367
|
+
|
|
7368
|
+
return currIndex;
|
|
7369
|
+
|
|
7370
|
+
}
|
|
7371
|
+
|
|
7372
|
+
setSigma( sigma ) {
|
|
7373
|
+
|
|
7374
|
+
if ( sigma === this.sigma ) {
|
|
7375
|
+
|
|
7376
|
+
return;
|
|
7377
|
+
|
|
7378
|
+
}
|
|
7379
|
+
|
|
7380
|
+
// generate a radius in which the score will be updated under the
|
|
7381
|
+
// assumption that e^-10 is insignificant enough to be the border at
|
|
7382
|
+
// which we drop off.
|
|
7383
|
+
const radius = ~ ~ ( Math.sqrt( 10 * 2 * ( sigma ** 2 ) ) + 1 );
|
|
7384
|
+
const lookupWidth = 2 * radius + 1;
|
|
7385
|
+
const lookupTable = new Float32Array( lookupWidth * lookupWidth );
|
|
7386
|
+
const sigma2 = sigma * sigma;
|
|
7387
|
+
for ( let x = - radius; x <= radius; x ++ ) {
|
|
7388
|
+
|
|
7389
|
+
for ( let y = - radius; y <= radius; y ++ ) {
|
|
7390
|
+
|
|
7391
|
+
const index = ( radius + y ) * lookupWidth + x + radius;
|
|
7392
|
+
const dist2 = x * x + y * y;
|
|
7393
|
+
lookupTable[ index ] = Math.E ** ( - dist2 / ( 2 * sigma2 ) );
|
|
7394
|
+
|
|
7395
|
+
}
|
|
7396
|
+
|
|
7397
|
+
}
|
|
7398
|
+
|
|
7399
|
+
this.lookupTable = lookupTable;
|
|
7400
|
+
this.sigma = sigma;
|
|
7401
|
+
this.radius = radius;
|
|
7402
|
+
|
|
7403
|
+
}
|
|
7404
|
+
|
|
7405
|
+
resize( size ) {
|
|
7406
|
+
|
|
7407
|
+
if ( this.size !== size ) {
|
|
7408
|
+
|
|
7409
|
+
this.size = size;
|
|
7410
|
+
this.score = new Float32Array( size * size );
|
|
7411
|
+
this.binaryPattern = new Uint8Array( size * size );
|
|
7412
|
+
|
|
7413
|
+
}
|
|
7414
|
+
|
|
7415
|
+
|
|
7416
|
+
}
|
|
7417
|
+
|
|
7418
|
+
invert() {
|
|
7419
|
+
|
|
7420
|
+
const { binaryPattern, score, size } = this;
|
|
7421
|
+
|
|
7422
|
+
score.fill( 0 );
|
|
7423
|
+
|
|
7424
|
+
for ( let i = 0, l = binaryPattern.length; i < l; i ++ ) {
|
|
7425
|
+
|
|
7426
|
+
if ( binaryPattern[ i ] === 0 ) {
|
|
7427
|
+
|
|
7428
|
+
const y = ~ ~ ( i / size );
|
|
7429
|
+
const x = i - y * size;
|
|
7430
|
+
this.updateScore( x, y, 1 );
|
|
7431
|
+
binaryPattern[ i ] = 1;
|
|
7432
|
+
|
|
7433
|
+
} else {
|
|
7434
|
+
|
|
7435
|
+
binaryPattern[ i ] = 0;
|
|
7436
|
+
|
|
7437
|
+
}
|
|
7438
|
+
|
|
7439
|
+
}
|
|
7440
|
+
|
|
7441
|
+
}
|
|
7442
|
+
|
|
7443
|
+
updateScore( x, y, multiplier ) {
|
|
7444
|
+
|
|
7445
|
+
// TODO: Is there a way to keep track of the highest and lowest scores here to avoid have to search over
|
|
7446
|
+
// everything in the buffer?
|
|
7447
|
+
const { size, score, lookupTable } = this;
|
|
7448
|
+
|
|
7449
|
+
// const sigma2 = sigma * sigma;
|
|
7450
|
+
// const radius = Math.floor( size / 2 );
|
|
7451
|
+
const radius = this.radius;
|
|
7452
|
+
const lookupWidth = 2 * radius + 1;
|
|
7453
|
+
for ( let px = - radius; px <= radius; px ++ ) {
|
|
7454
|
+
|
|
7455
|
+
for ( let py = - radius; py <= radius; py ++ ) {
|
|
7456
|
+
|
|
7457
|
+
// const dist2 = px * px + py * py;
|
|
7458
|
+
// const value = Math.E ** ( - dist2 / ( 2 * sigma2 ) );
|
|
7459
|
+
|
|
7460
|
+
const lookupIndex = ( radius + py ) * lookupWidth + px + radius;
|
|
7461
|
+
const value = lookupTable[ lookupIndex ];
|
|
7462
|
+
|
|
7463
|
+
let sx = ( x + px );
|
|
7464
|
+
sx = sx < 0 ? size + sx : sx % size;
|
|
7465
|
+
|
|
7466
|
+
let sy = ( y + py );
|
|
7467
|
+
sy = sy < 0 ? size + sy : sy % size;
|
|
7468
|
+
|
|
7469
|
+
const sindex = sy * size + sx;
|
|
7470
|
+
score[ sindex ] += multiplier * value;
|
|
7471
|
+
|
|
7472
|
+
}
|
|
7473
|
+
|
|
7474
|
+
}
|
|
7475
|
+
|
|
7476
|
+
}
|
|
7477
|
+
|
|
7478
|
+
addPointIndex( index ) {
|
|
7479
|
+
|
|
7480
|
+
this.binaryPattern[ index ] = 1;
|
|
7481
|
+
|
|
7482
|
+
const size = this.size;
|
|
7483
|
+
const y = ~ ~ ( index / size );
|
|
7484
|
+
const x = index - y * size;
|
|
7485
|
+
this.updateScore( x, y, 1 );
|
|
7486
|
+
this.count ++;
|
|
7487
|
+
|
|
7488
|
+
}
|
|
7489
|
+
|
|
7490
|
+
removePointIndex( index ) {
|
|
7491
|
+
|
|
7492
|
+
this.binaryPattern[ index ] = 0;
|
|
7493
|
+
|
|
7494
|
+
const size = this.size;
|
|
7495
|
+
const y = ~ ~ ( index / size );
|
|
7496
|
+
const x = index - y * size;
|
|
7497
|
+
this.updateScore( x, y, - 1 );
|
|
7498
|
+
this.count --;
|
|
7499
|
+
|
|
7500
|
+
}
|
|
7501
|
+
|
|
7502
|
+
copy( source ) {
|
|
7503
|
+
|
|
7504
|
+
this.resize( source.size );
|
|
7505
|
+
this.score.set( source.score );
|
|
7506
|
+
this.binaryPattern.set( source.binaryPattern );
|
|
7507
|
+
this.setSigma( source.sigma );
|
|
7508
|
+
this.count = source.count;
|
|
7509
|
+
|
|
7510
|
+
}
|
|
7511
|
+
|
|
7512
|
+
}
|
|
7513
|
+
|
|
7514
|
+
class BlueNoiseGenerator {
|
|
7515
|
+
|
|
7516
|
+
constructor() {
|
|
7517
|
+
|
|
7518
|
+
this.random = Math.random;
|
|
7519
|
+
this.sigma = 1.5;
|
|
7520
|
+
this.size = 64;
|
|
7521
|
+
this.majorityPointsRatio = 0.1;
|
|
7522
|
+
|
|
7523
|
+
this.samples = new BlueNoiseSamples( 1 );
|
|
7524
|
+
this.savedSamples = new BlueNoiseSamples( 1 );
|
|
7525
|
+
|
|
7526
|
+
}
|
|
7527
|
+
|
|
7528
|
+
generate() {
|
|
7529
|
+
|
|
7530
|
+
// http://cv.ulichney.com/papers/1993-void-cluster.pdf
|
|
7531
|
+
|
|
7532
|
+
const {
|
|
7533
|
+
samples,
|
|
7534
|
+
savedSamples,
|
|
7535
|
+
sigma,
|
|
7536
|
+
majorityPointsRatio,
|
|
7537
|
+
size,
|
|
7538
|
+
} = this;
|
|
7539
|
+
|
|
7540
|
+
samples.resize( size );
|
|
7541
|
+
samples.setSigma( sigma );
|
|
7542
|
+
|
|
7543
|
+
// 1. Randomly place the minority points.
|
|
7544
|
+
const pointCount = Math.floor( size * size * majorityPointsRatio );
|
|
7545
|
+
const initialSamples = samples.binaryPattern;
|
|
7546
|
+
|
|
7547
|
+
fillWithOnes( initialSamples, pointCount );
|
|
7548
|
+
shuffleArray( initialSamples, this.random );
|
|
7549
|
+
|
|
7550
|
+
for ( let i = 0, l = initialSamples.length; i < l; i ++ ) {
|
|
7551
|
+
|
|
7552
|
+
if ( initialSamples[ i ] === 1 ) {
|
|
7553
|
+
|
|
7554
|
+
samples.addPointIndex( i );
|
|
7555
|
+
|
|
7556
|
+
}
|
|
7557
|
+
|
|
7558
|
+
}
|
|
7559
|
+
|
|
7560
|
+
// 2. Remove minority point that is in densest cluster and place it in the largest void.
|
|
7561
|
+
while ( true ) {
|
|
7562
|
+
|
|
7563
|
+
const clusterIndex = samples.findCluster();
|
|
7564
|
+
samples.removePointIndex( clusterIndex );
|
|
7565
|
+
|
|
7566
|
+
const voidIndex = samples.findVoid();
|
|
7567
|
+
if ( clusterIndex === voidIndex ) {
|
|
7568
|
+
|
|
7569
|
+
samples.addPointIndex( clusterIndex );
|
|
7570
|
+
break;
|
|
7571
|
+
|
|
7572
|
+
}
|
|
7573
|
+
|
|
7574
|
+
samples.addPointIndex( voidIndex );
|
|
7575
|
+
|
|
7576
|
+
}
|
|
7577
|
+
|
|
7578
|
+
// 3. PHASE I: Assign a rank to each progressively less dense cluster point and put it
|
|
7579
|
+
// in the dither array.
|
|
7580
|
+
const ditherArray = new Uint32Array( size * size );
|
|
7581
|
+
savedSamples.copy( samples );
|
|
7582
|
+
|
|
7583
|
+
let rank;
|
|
7584
|
+
rank = samples.count - 1;
|
|
7585
|
+
while ( rank >= 0 ) {
|
|
7586
|
+
|
|
7587
|
+
const clusterIndex = samples.findCluster();
|
|
7588
|
+
samples.removePointIndex( clusterIndex );
|
|
7589
|
+
|
|
7590
|
+
ditherArray[ clusterIndex ] = rank;
|
|
7591
|
+
rank --;
|
|
7592
|
+
|
|
7593
|
+
}
|
|
7594
|
+
|
|
7595
|
+
// 4. PHASE II: Do the same thing for the largest voids up to half of the total pixels using
|
|
7596
|
+
// the initial binary pattern.
|
|
7597
|
+
const totalSize = size * size;
|
|
7598
|
+
rank = savedSamples.count;
|
|
7599
|
+
while ( rank < totalSize / 2 ) {
|
|
7600
|
+
|
|
7601
|
+
const voidIndex = savedSamples.findVoid();
|
|
7602
|
+
savedSamples.addPointIndex( voidIndex );
|
|
7603
|
+
ditherArray[ voidIndex ] = rank;
|
|
7604
|
+
rank ++;
|
|
7605
|
+
|
|
7606
|
+
}
|
|
7607
|
+
|
|
7608
|
+
// 5. PHASE III: Invert the pattern and finish out by assigning a rank to the remaining
|
|
7609
|
+
// and iteratively removing them.
|
|
7610
|
+
savedSamples.invert();
|
|
7611
|
+
|
|
7612
|
+
while ( rank < totalSize ) {
|
|
7613
|
+
|
|
7614
|
+
const clusterIndex = savedSamples.findCluster();
|
|
7615
|
+
savedSamples.removePointIndex( clusterIndex );
|
|
7616
|
+
ditherArray[ clusterIndex ] = rank;
|
|
7617
|
+
rank ++;
|
|
7618
|
+
|
|
7619
|
+
}
|
|
7620
|
+
|
|
7621
|
+
return { data: ditherArray, maxValue: totalSize };
|
|
7622
|
+
|
|
7623
|
+
}
|
|
7624
|
+
|
|
7625
|
+
}
|
|
7626
|
+
|
|
7627
|
+
function getStride( channels ) {
|
|
7628
|
+
|
|
7629
|
+
if ( channels >= 3 ) {
|
|
7630
|
+
|
|
7631
|
+
return 4;
|
|
7632
|
+
|
|
7633
|
+
} else {
|
|
7634
|
+
|
|
7635
|
+
return channels;
|
|
7636
|
+
|
|
7637
|
+
}
|
|
7638
|
+
|
|
7639
|
+
}
|
|
7640
|
+
|
|
7641
|
+
function getFormat( channels ) {
|
|
7642
|
+
|
|
7643
|
+
switch ( channels ) {
|
|
7644
|
+
|
|
7645
|
+
case 1:
|
|
7646
|
+
return RedFormat;
|
|
7647
|
+
case 2:
|
|
7648
|
+
return RGFormat;
|
|
7649
|
+
default:
|
|
7650
|
+
return RGBAFormat;
|
|
7651
|
+
|
|
7652
|
+
}
|
|
7653
|
+
|
|
7654
|
+
}
|
|
7655
|
+
|
|
7656
|
+
class BlueNoiseTexture extends DataTexture {
|
|
7657
|
+
|
|
7658
|
+
constructor( size = 64, channels = 1 ) {
|
|
7659
|
+
|
|
7660
|
+
super( new Float32Array( 4 ), 1, 1, RGBAFormat, FloatType );
|
|
7661
|
+
this.minFilter = NearestFilter;
|
|
7662
|
+
this.magFilter = NearestFilter;
|
|
7663
|
+
|
|
7664
|
+
this.size = size;
|
|
7665
|
+
this.channels = channels;
|
|
7666
|
+
this.update();
|
|
7667
|
+
|
|
7668
|
+
}
|
|
7669
|
+
|
|
7670
|
+
update() {
|
|
7671
|
+
|
|
7672
|
+
const channels = this.channels;
|
|
7673
|
+
const size = this.size;
|
|
7674
|
+
const generator = new BlueNoiseGenerator();
|
|
7675
|
+
generator.channels = channels;
|
|
7676
|
+
generator.size = size;
|
|
7677
|
+
|
|
7678
|
+
const stride = getStride( channels );
|
|
7679
|
+
const format = getFormat( stride );
|
|
7680
|
+
if ( this.image.width !== size || format !== this.format ) {
|
|
7681
|
+
|
|
7682
|
+
this.image.width = size;
|
|
7683
|
+
this.image.height = size;
|
|
7684
|
+
this.image.data = new Float32Array( ( size ** 2 ) * stride );
|
|
7685
|
+
this.format = format;
|
|
7686
|
+
this.dispose();
|
|
7687
|
+
|
|
7688
|
+
}
|
|
7689
|
+
|
|
7690
|
+
const data = this.image.data;
|
|
7691
|
+
for ( let i = 0, l = channels; i < l; i ++ ) {
|
|
7692
|
+
|
|
7693
|
+
const result = generator.generate();
|
|
7694
|
+
const bin = result.data;
|
|
7695
|
+
const maxValue = result.maxValue;
|
|
7696
|
+
|
|
7697
|
+
for ( let j = 0, l2 = bin.length; j < l2; j ++ ) {
|
|
7698
|
+
|
|
7699
|
+
const value = bin[ j ] / maxValue;
|
|
7700
|
+
data[ j * stride + i ] = value;
|
|
7701
|
+
|
|
7702
|
+
}
|
|
7703
|
+
|
|
7704
|
+
}
|
|
7705
|
+
|
|
7706
|
+
this.needsUpdate = true;
|
|
7707
|
+
|
|
7708
|
+
}
|
|
7709
|
+
|
|
7710
|
+
}
|
|
7711
|
+
|
|
7712
|
+
class PhysicalPathTracingMaterial extends MaterialBase {
|
|
7051
7713
|
|
|
7052
7714
|
onBeforeRender() {
|
|
7053
7715
|
|
|
@@ -7070,7 +7732,12 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
7070
7732
|
FEATURE_DOF: 1,
|
|
7071
7733
|
FEATURE_BACKGROUND_MAP: 0,
|
|
7072
7734
|
FEATURE_FOG: 1,
|
|
7073
|
-
|
|
7735
|
+
|
|
7736
|
+
// 0 = PCG
|
|
7737
|
+
// 1 = Sobol
|
|
7738
|
+
// 2 = Stratified List
|
|
7739
|
+
RANDOM_TYPE: 2,
|
|
7740
|
+
|
|
7074
7741
|
// 0 = Perspective
|
|
7075
7742
|
// 1 = Orthographic
|
|
7076
7743
|
// 2 = Equirectangular
|
|
@@ -7112,6 +7779,8 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
7112
7779
|
|
|
7113
7780
|
backgroundAlpha: { value: 1.0 },
|
|
7114
7781
|
sobolTexture: { value: null },
|
|
7782
|
+
stratifiedTexture: { value: new StratifiedSamplesTexture() },
|
|
7783
|
+
stratifiedOffsetTexture: { value: new BlueNoiseTexture( 64, 1 ) },
|
|
7115
7784
|
},
|
|
7116
7785
|
|
|
7117
7786
|
vertexShader: /* glsl */`
|
|
@@ -7151,23 +7820,35 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
7151
7820
|
${ materialStructGLSL }
|
|
7152
7821
|
|
|
7153
7822
|
// random
|
|
7154
|
-
|
|
7155
|
-
#if FEATURE_SOBOL
|
|
7823
|
+
#if RANDOM_TYPE == 2 // Stratified List
|
|
7156
7824
|
|
|
7825
|
+
${ stratifiedTextureGLSL }
|
|
7826
|
+
|
|
7827
|
+
#elif RANDOM_TYPE == 1 // Sobol
|
|
7828
|
+
|
|
7829
|
+
${ pcgGLSL }
|
|
7157
7830
|
${ sobolCommonGLSL }
|
|
7158
7831
|
${ sobolSamplingGLSL }
|
|
7159
7832
|
|
|
7160
|
-
|
|
7833
|
+
#define rand(v) sobol(v)
|
|
7834
|
+
#define rand2(v) sobol2(v)
|
|
7835
|
+
#define rand3(v) sobol3(v)
|
|
7836
|
+
#define rand4(v) sobol4(v)
|
|
7837
|
+
|
|
7838
|
+
#else // PCG
|
|
7839
|
+
|
|
7840
|
+
${ pcgGLSL }
|
|
7161
7841
|
|
|
7162
7842
|
// Using the sobol functions seems to break the the compiler on MacOS
|
|
7163
7843
|
// - specifically the "sobolReverseBits" function.
|
|
7164
7844
|
uint sobolPixelIndex = 0u;
|
|
7165
7845
|
uint sobolPathIndex = 0u;
|
|
7166
7846
|
uint sobolBounceIndex = 0u;
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7847
|
+
|
|
7848
|
+
#define rand(v) pcgRand()
|
|
7849
|
+
#define rand2(v) pcgRand2()
|
|
7850
|
+
#define rand3(v) pcgRand3()
|
|
7851
|
+
#define rand4(v) pcgRand4()
|
|
7171
7852
|
|
|
7172
7853
|
#endif
|
|
7173
7854
|
|
|
@@ -7354,7 +8035,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
7354
8035
|
|
|
7355
8036
|
if ( state.firstRay || state.transmissiveRay ) {
|
|
7356
8037
|
|
|
7357
|
-
gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction,
|
|
8038
|
+
gl_FragColor.rgb += sampleBackground( envRotation3x3 * ray.direction, rand2( 2 ) ) * state.throughputColor;
|
|
7358
8039
|
gl_FragColor.a = backgroundAlpha;
|
|
7359
8040
|
|
|
7360
8041
|
} else {
|
|
@@ -7445,7 +8126,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
7445
8126
|
}
|
|
7446
8127
|
|
|
7447
8128
|
scatterRec = bsdfSample( - ray.direction, surf );
|
|
7448
|
-
state.isShadowRay = scatterRec.specularPdf <
|
|
8129
|
+
state.isShadowRay = scatterRec.specularPdf < rand( 4 );
|
|
7449
8130
|
|
|
7450
8131
|
bool isBelowSurface = ! surf.volumeParticle && dot( scatterRec.direction, surf.faceNormal ) < 0.0;
|
|
7451
8132
|
vec3 hitPoint = stepRayOrigin( ray.origin, ray.direction, isBelowSurface ? - surf.faceNormal : surf.faceNormal, surfaceHit.dist );
|
|
@@ -7515,7 +8196,7 @@ class PhysicalPathTracingMaterial extends MaterialBase {
|
|
|
7515
8196
|
rrProb = sqrt( rrProb );
|
|
7516
8197
|
rrProb = max( rrProb, depthProb );
|
|
7517
8198
|
rrProb = min( rrProb, 1.0 );
|
|
7518
|
-
if (
|
|
8199
|
+
if ( rand( 8 ) > rrProb ) {
|
|
7519
8200
|
|
|
7520
8201
|
break;
|
|
7521
8202
|
|
|
@@ -7920,5 +8601,5 @@ class CompatibilityDetector {
|
|
|
7920
8601
|
|
|
7921
8602
|
// core
|
|
7922
8603
|
|
|
7923
|
-
export { BlurredEnvMapGenerator, CompatibilityDetector, DenoiseMaterial, DynamicPathTracingSceneGenerator, EquirectCamera, EquirectHdrInfoUniform, FogVolumeMaterial, GradientEquirectTexture, GradientMapMaterial, GraphMaterial, IESLoader, IESProfilesTexture, LightsInfoUniformStruct, MaterialBase, MaterialReducer, MaterialsTexture, PathTracingRenderer, PathTracingSceneGenerator, PhysicalCamera, PhysicalCameraUniform, PhysicalPathTracingMaterial, PhysicalSpotLight, ProceduralEquirectTexture, QuiltPathTracingRenderer, RenderTarget2DArray, ShapedAreaLight,
|
|
8604
|
+
export { BlurredEnvMapGenerator, CompatibilityDetector, DenoiseMaterial, DynamicPathTracingSceneGenerator, EquirectCamera, EquirectHdrInfoUniform, FogVolumeMaterial, GradientEquirectTexture, GradientMapMaterial, GraphMaterial, IESLoader, IESProfilesTexture, LightsInfoUniformStruct, MaterialBase, MaterialReducer, MaterialsTexture, PathTracingRenderer, PathTracingSceneGenerator, PhysicalCamera, PhysicalCameraUniform, PhysicalPathTracingMaterial, PhysicalSpotLight, ProceduralEquirectTexture, QuiltPathTracingRenderer, RenderTarget2DArray, ShapedAreaLight, getDummyMesh, getGroupMaterialIndicesAttribute, setCommonAttributes };
|
|
7924
8605
|
//# sourceMappingURL=index.module.js.map
|