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