rayzee 7.31.1 → 8.0.0
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 +164 -0
- package/dist/rayzee.es.js +6366 -4361
- package/dist/rayzee.umd.js +122 -73
- package/package.json +1 -1
- package/src/EngineDefaults.js +168 -13
- package/src/EngineIssues.js +5 -0
- package/src/PathTracerApp.js +120 -19
- package/src/Processor/ArchiveReader.js +538 -0
- package/src/Processor/AssetLoader.js +265 -23
- package/src/Processor/BVHBuilder.js +48 -32
- package/src/Processor/BVHRefitter.js +244 -99
- package/src/Processor/ChunkedRecords.js +302 -0
- package/src/Processor/EmissiveTriangleBuilder.js +48 -21
- package/src/Processor/GeometryExtractor.js +577 -92
- package/src/Processor/HostMemory.js +222 -0
- package/src/Processor/InstanceTable.js +548 -97
- package/src/Processor/PBRT/EqualAreaOctahedral.js +32 -8
- package/src/Processor/PBRT/PBRTCurves.js +249 -0
- package/src/Processor/PBRT/PBRTMath.js +19 -0
- package/src/Processor/PBRT/PBRTParser.js +187 -41
- package/src/Processor/PBRT/PBRTSceneBuilder.js +586 -42
- package/src/Processor/PBRT/PBRTTokenizer.js +205 -65
- package/src/Processor/PBRT/index.js +257 -50
- package/src/Processor/PackedRayBuffer.js +29 -18
- package/src/Processor/ParallelBVHBuilder.js +9 -8
- package/src/Processor/QueueManager.js +5 -3
- package/src/Processor/SceneProcessor.js +1046 -365
- package/src/Processor/TLASBuilder.js +546 -201
- package/src/Processor/TextureCreator.js +11 -9
- package/src/Processor/VRAMTracker.js +12 -3
- package/src/Processor/Workers/BVHRefitWorker.js +16 -11
- package/src/Processor/Workers/BVHWorker.js +12 -12
- package/src/Processor/Workers/TLASWorker.js +37 -0
- package/src/Stages/NormalDepth.js +6 -2
- package/src/Stages/PathTracer.js +5 -5
- package/src/Stages/PathTracerStage.js +163 -41
- package/src/TSL/BVHTraversal.js +208 -90
- package/src/TSL/Common.js +135 -2
- package/src/TSL/Displacement.js +21 -11
- package/src/TSL/EmissiveSampling.js +49 -25
- package/src/TSL/ExtendKernel.js +3 -2
- package/src/TSL/LightBVHSampling.js +7 -4
- package/src/TSL/LightsDirect.js +30 -29
- package/src/TSL/ShadeKernel.js +8 -6
- package/src/TSL/Struct.js +3 -0
- package/src/TSL/TextureSampling.js +24 -9
- package/src/TSL/patches.js +128 -3
- package/src/index.js +3 -0
- package/src/managers/AnimationManager.js +86 -76
- package/src/managers/MaterialDataManager.js +29 -23
- package/src/managers/TransformManager.js +9 -192
package/src/TSL/Displacement.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { Fn, float, vec2, int, If, Loop, abs, normalize, dot, max } from 'three/tsl';
|
|
1
|
+
import { Fn, float, vec2, int, If, Loop, abs, normalize, dot, max, uintBitsToFloat } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
import { struct } from './patches.js';
|
|
4
|
-
import { getDatafromStorageBuffer } from './Common.js';
|
|
4
|
+
import { getDatafromStorageBuffer, TRI_STRIDE, instanceRows, instanceDirToWorld } from './Common.js';
|
|
5
5
|
import { sampleDisplacementMap, bucketTexelSize, getLinearBucketTextures } from './TextureSampling.js';
|
|
6
6
|
|
|
7
7
|
// Ray-displacement intersection configuration
|
|
8
8
|
const MAX_MARCH_STEPS = 32;
|
|
9
9
|
const MIN_MARCH_STEPS = 16;
|
|
10
10
|
const BINARY_STEPS = 5;
|
|
11
|
-
const TRI_STRIDE = 8;
|
|
12
11
|
|
|
13
12
|
export const DisplacementResult = struct( {
|
|
14
13
|
hitPoint: 'vec3',
|
|
@@ -32,7 +31,7 @@ export const DisplacementResult = struct( {
|
|
|
32
31
|
* We find dt where h_ray(dt) = h_surf(dt).
|
|
33
32
|
*/
|
|
34
33
|
export const refineDisplacedIntersection = Fn( ( [
|
|
35
|
-
ray, hitInfo, triangleBuffer, material, bounceIndex
|
|
34
|
+
ray, hitInfo, triangleBuffer, material, bounceIndex, bvhBuffer, instanceLeaf
|
|
36
35
|
] ) => {
|
|
37
36
|
|
|
38
37
|
const resultHitPoint = hitInfo.hitPoint.toVar();
|
|
@@ -43,21 +42,32 @@ export const refineDisplacedIntersection = Fn( ( [
|
|
|
43
42
|
// Fetch triangle vertex data
|
|
44
43
|
const triIdx = hitInfo.triangleIndex;
|
|
45
44
|
|
|
46
|
-
const pA = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 0 ), int( TRI_STRIDE ) ).xyz.toVar();
|
|
47
|
-
const pB = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 1 ), int( TRI_STRIDE ) ).xyz;
|
|
48
|
-
const pC = getDatafromStorageBuffer( triangleBuffer, triIdx, int( 2 ), int( TRI_STRIDE ) ).xyz;
|
|
45
|
+
const pA = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIdx, int( 0 ), int( TRI_STRIDE ) ).xyz ).toVar();
|
|
46
|
+
const pB = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIdx, int( 1 ), int( TRI_STRIDE ) ).xyz );
|
|
47
|
+
const pC = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIdx, int( 2 ), int( TRI_STRIDE ) ).xyz );
|
|
49
48
|
|
|
50
|
-
const uvData1 = getDatafromStorageBuffer( triangleBuffer, triIdx, int(
|
|
51
|
-
const uvData2 = getDatafromStorageBuffer( triangleBuffer, triIdx, int(
|
|
49
|
+
const uvData1 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIdx, int( 3 ), int( TRI_STRIDE ) ) ).toVar();
|
|
50
|
+
const uvData2 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIdx, int( 4 ), int( TRI_STRIDE ) ).xy );
|
|
52
51
|
|
|
53
52
|
const uvA = uvData1.xy.toVar();
|
|
54
53
|
const uvB = uvData1.zw;
|
|
55
|
-
const uvC = uvData2
|
|
54
|
+
const uvC = uvData2;
|
|
56
55
|
|
|
57
|
-
// Compute tangent vectors from triangle edges + UV differences
|
|
56
|
+
// Compute tangent vectors from triangle edges + UV differences. The ray, the hit point and
|
|
57
|
+
// the normal are all in world space; a triangle that belongs to a placement is not, so its
|
|
58
|
+
// edges are moved before they are used, or the marching frame is half in each space.
|
|
58
59
|
const edge1 = pB.sub( pA ).toVar();
|
|
59
60
|
const edge2 = pC.sub( pA ).toVar();
|
|
60
61
|
|
|
62
|
+
If( instanceLeaf.greaterThanEqual( int( 0 ) ), () => {
|
|
63
|
+
|
|
64
|
+
const rows = instanceRows( bvhBuffer, instanceLeaf );
|
|
65
|
+
const r = { r0: rows[ 0 ].xyz, r1: rows[ 1 ].xyz, r2: rows[ 2 ].xyz };
|
|
66
|
+
edge1.assign( instanceDirToWorld( { ...r, v: edge1 } ) );
|
|
67
|
+
edge2.assign( instanceDirToWorld( { ...r, v: edge2 } ) );
|
|
68
|
+
|
|
69
|
+
} );
|
|
70
|
+
|
|
61
71
|
const dUV1 = uvB.sub( uvA ).toVar();
|
|
62
72
|
const dUV2 = uvC.sub( uvA ).toVar();
|
|
63
73
|
const det = dUV1.x.mul( dUV2.y ).sub( dUV2.x.mul( dUV1.y ) ).toVar();
|
|
@@ -22,10 +22,17 @@ import {
|
|
|
22
22
|
cos,
|
|
23
23
|
acos,
|
|
24
24
|
atan,
|
|
25
|
+
uint,
|
|
26
|
+
uintBitsToFloat,
|
|
25
27
|
} from 'three/tsl';
|
|
26
28
|
|
|
27
29
|
import { struct } from './patches.js';
|
|
28
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
MIN_PDF, getDatafromStorageBuffer, powerHeuristic, MATERIAL_SLOTS, MATERIAL_SLOT,
|
|
32
|
+
computeDotProductsAniso, instanceRows, instanceNormalToWorld, instancePointToWorld,
|
|
33
|
+
unpackTriangleNormal, TRI_STRIDE
|
|
34
|
+
} from './Common.js';
|
|
35
|
+
import { TRI_MATERIAL_MASK } from '../EngineDefaults.js';
|
|
29
36
|
import { getRandomSample1D, getRandomSample2D } from './Random.js';
|
|
30
37
|
import { calculateMaterialPDFFromDots } from './LightsSampling.js';
|
|
31
38
|
import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
@@ -272,7 +279,6 @@ export const isEmissive = Fn( ( [ material ] ) => {
|
|
|
272
279
|
// TRIANGLE DATA ACCESS
|
|
273
280
|
// ================================================================================
|
|
274
281
|
|
|
275
|
-
const TRI_STRIDE = 8;
|
|
276
282
|
const EMISSIVE_STRIDE = 2; // 2 vec4s per emissive entry
|
|
277
283
|
|
|
278
284
|
export const TriangleData = struct( {
|
|
@@ -283,26 +289,40 @@ export const TriangleData = struct( {
|
|
|
283
289
|
|
|
284
290
|
// Fetch triangle vertices from storage buffer
|
|
285
291
|
// Returns data packed in a struct-like way
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
+
/**
|
|
293
|
+
* A triangle's vertices and normals in WORLD space.
|
|
294
|
+
*
|
|
295
|
+
* Storage is per instance in object space, so `instanceLeaf` names the TLAS leaf whose
|
|
296
|
+
* transform brings it back out; pass -1 for geometry that is already in world space.
|
|
297
|
+
*/
|
|
298
|
+
export const fetchTriangleData = Fn( ( [ triangleIndex, triangleBuffer, bvhBuffer, instanceLeaf ] ) => {
|
|
299
|
+
|
|
300
|
+
// Positions carry their packed normal in .w
|
|
301
|
+
const pos0 = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 0 ), int( TRI_STRIDE ) ).toVar();
|
|
302
|
+
const pos1 = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 1 ), int( TRI_STRIDE ) ).toVar();
|
|
303
|
+
const pos2 = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 2 ), int( TRI_STRIDE ) ).toVar();
|
|
304
|
+
|
|
305
|
+
const uvMat = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 4 ), int( TRI_STRIDE ) );
|
|
306
|
+
|
|
307
|
+
const v0 = uintBitsToFloat( pos0.xyz ).toVar(), v1 = uintBitsToFloat( pos1.xyz ).toVar(), v2 = uintBitsToFloat( pos2.xyz ).toVar();
|
|
308
|
+
const n0 = unpackTriangleNormal( pos0.w ).toVar(), n1 = unpackTriangleNormal( pos1.w ).toVar(), n2 = unpackTriangleNormal( pos2.w ).toVar();
|
|
309
|
+
|
|
310
|
+
If( instanceLeaf.greaterThanEqual( int( 0 ) ), () => {
|
|
311
|
+
|
|
312
|
+
const rows = instanceRows( bvhBuffer, instanceLeaf );
|
|
313
|
+
v0.assign( instancePointToWorld( rows, v0 ) );
|
|
314
|
+
v1.assign( instancePointToWorld( rows, v1 ) );
|
|
315
|
+
v2.assign( instancePointToWorld( rows, v2 ) );
|
|
316
|
+
n0.assign( normalize( instanceNormalToWorld( rows, n0 ) ) );
|
|
317
|
+
n1.assign( normalize( instanceNormalToWorld( rows, n1 ) ) );
|
|
318
|
+
n2.assign( normalize( instanceNormalToWorld( rows, n2 ) ) );
|
|
292
319
|
|
|
293
|
-
|
|
294
|
-
const norm0 = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 3 ), int( TRI_STRIDE ) );
|
|
295
|
-
const norm1 = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 4 ), int( TRI_STRIDE ) );
|
|
296
|
-
const norm2 = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 5 ), int( TRI_STRIDE ) );
|
|
297
|
-
|
|
298
|
-
// Material index (stored in last vec4)
|
|
299
|
-
const uvMat = getDatafromStorageBuffer( triangleBuffer, triangleIndex, int( 7 ), int( TRI_STRIDE ) );
|
|
320
|
+
} );
|
|
300
321
|
|
|
301
322
|
// Return all data as a struct
|
|
302
323
|
return TriangleData( {
|
|
303
|
-
v0
|
|
304
|
-
|
|
305
|
-
materialIndex: int( uvMat.z ),
|
|
324
|
+
v0, v1, v2, n0, n1, n2,
|
|
325
|
+
materialIndex: int( uvMat.z.bitAnd( uint( TRI_MATERIAL_MASK ) ) ),
|
|
306
326
|
} );
|
|
307
327
|
|
|
308
328
|
} );
|
|
@@ -312,9 +332,10 @@ export const fetchTriangleData = Fn( ( [ triangleIndex, triangleBuffer ] ) => {
|
|
|
312
332
|
// Uses same heuristic as sampleEmissiveTriangle for MIS consistency
|
|
313
333
|
export const calculateEmissiveLightPdf = Fn( ( [
|
|
314
334
|
triangleIndex, hitDistance, rayDir, shadingPoint, triangleBuffer, materialBuffer, emissiveTotalPower,
|
|
335
|
+
bvhBuffer, instanceLeaf,
|
|
315
336
|
] ) => {
|
|
316
337
|
|
|
317
|
-
const triData = TriangleData.wrap( fetchTriangleData( triangleIndex, triangleBuffer ) );
|
|
338
|
+
const triData = TriangleData.wrap( fetchTriangleData( triangleIndex, triangleBuffer, bvhBuffer, instanceLeaf ) );
|
|
318
339
|
const area = triangleArea( triData.v0, triData.v1, triData.v2 );
|
|
319
340
|
|
|
320
341
|
// Targeted material read: only fetch emissive data (2 vec4s instead of full 27)
|
|
@@ -391,7 +412,7 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
391
412
|
rngState,
|
|
392
413
|
pixelCoord, resolution, frame, dimBase,
|
|
393
414
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
394
|
-
triangleBuffer,
|
|
415
|
+
triangleBuffer, bvhBuffer,
|
|
395
416
|
] ) => {
|
|
396
417
|
|
|
397
418
|
const result = EmissiveSample( {
|
|
@@ -423,9 +444,12 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
423
444
|
const samplePower = max( emissiveData0.g, float( 1e-10 ) );
|
|
424
445
|
const emission = emissiveData1.xyz;
|
|
425
446
|
const area = emissiveData1.w;
|
|
447
|
+
// Slot 3 carries the emitter's TLAS leaf; the selection pdf it used to hold is
|
|
448
|
+
// recomputed from power and the total, so the word was free.
|
|
449
|
+
const emissiveInstance = int( emissiveData0.a );
|
|
426
450
|
|
|
427
451
|
// Fetch triangle geometry
|
|
428
|
-
const triData = TriangleData.wrap( fetchTriangleData( triangleIndex, triangleBuffer ) );
|
|
452
|
+
const triData = TriangleData.wrap( fetchTriangleData( triangleIndex, triangleBuffer, bvhBuffer, emissiveInstance ) );
|
|
429
453
|
|
|
430
454
|
const xi = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 3 ) ), rngState, resolution, frame ).toVar();
|
|
431
455
|
|
|
@@ -532,7 +556,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
532
556
|
pixelCoord, resolution, frame, dimBase,
|
|
533
557
|
emissiveBoost,
|
|
534
558
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
535
|
-
triangleBuffer,
|
|
559
|
+
triangleBuffer, bvhBuffer,
|
|
536
560
|
// Callback functions to avoid circular deps
|
|
537
561
|
traceShadowRayFn,
|
|
538
562
|
calculateRayOffsetFn,
|
|
@@ -553,7 +577,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
553
577
|
hitPoint, normal, rngState,
|
|
554
578
|
pixelCoord, resolution, frame, dimBase,
|
|
555
579
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
556
|
-
triangleBuffer,
|
|
580
|
+
triangleBuffer, bvhBuffer,
|
|
557
581
|
) );
|
|
558
582
|
|
|
559
583
|
If( emissiveSample.valid.and( emissiveSample.pdf.greaterThan( 0.0 ) ), () => {
|
|
@@ -614,7 +638,7 @@ export const calculateEmissiveTriangleContribution = Fn( ( [
|
|
|
614
638
|
pixelCoord, resolution, frame, dimBase,
|
|
615
639
|
emissiveBoost,
|
|
616
640
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
617
|
-
triangleBuffer,
|
|
641
|
+
triangleBuffer, bvhBuffer,
|
|
618
642
|
traceShadowRayFn,
|
|
619
643
|
calculateRayOffsetFn,
|
|
620
644
|
] ) => {
|
|
@@ -625,7 +649,7 @@ export const calculateEmissiveTriangleContribution = Fn( ( [
|
|
|
625
649
|
pixelCoord, resolution, frame, dimBase,
|
|
626
650
|
emissiveBoost,
|
|
627
651
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
628
|
-
triangleBuffer,
|
|
652
|
+
triangleBuffer, bvhBuffer,
|
|
629
653
|
traceShadowRayFn,
|
|
630
654
|
calculateRayOffsetFn,
|
|
631
655
|
) );
|
package/src/TSL/ExtendKernel.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
Fn, uint,
|
|
6
|
+
Fn, uint, int,
|
|
7
7
|
If,
|
|
8
8
|
instanceIndex,
|
|
9
9
|
atomicLoad,
|
|
@@ -71,7 +71,8 @@ export function buildExtendKernel( params ) {
|
|
|
71
71
|
hitInfo.uv.x, hitInfo.uv.y,
|
|
72
72
|
hitInfo.normal,
|
|
73
73
|
uint( hitInfo.materialIndex ),
|
|
74
|
-
|
|
74
|
+
// Biased by one: leaf 0 is real, so 0 has to mean "no instance".
|
|
75
|
+
uint( hitInfo.instanceLeaf.add( int( 1 ) ) ),
|
|
75
76
|
);
|
|
76
77
|
|
|
77
78
|
} );
|
|
@@ -110,7 +110,7 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
110
110
|
lbvhBuffer,
|
|
111
111
|
emissiveTriangleBuffer,
|
|
112
112
|
emissiveVec4Offset,
|
|
113
|
-
triangleBuffer,
|
|
113
|
+
triangleBuffer, bvhBuffer,
|
|
114
114
|
] ) => {
|
|
115
115
|
|
|
116
116
|
const result = EmissiveSample( {
|
|
@@ -256,9 +256,10 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
256
256
|
const triangleIndex = int( emissiveData0.r );
|
|
257
257
|
const emission = emissiveData1.xyz;
|
|
258
258
|
const area = emissiveData1.w;
|
|
259
|
+
const emissiveInstance = int( emissiveData0.a );
|
|
259
260
|
|
|
260
261
|
// Fetch triangle geometry
|
|
261
|
-
const triData = TriangleData.wrap( fetchTriangleData( triangleIndex, triangleBuffer ) );
|
|
262
|
+
const triData = TriangleData.wrap( fetchTriangleData( triangleIndex, triangleBuffer, bvhBuffer, emissiveInstance ) );
|
|
262
263
|
|
|
263
264
|
const xi = getRandomSample2D( pixelCoord, int( 0 ), dimBase.add( int( 3 ) ), rngState, resolution, frame ).toVar();
|
|
264
265
|
|
|
@@ -361,7 +362,7 @@ export const sampleLightBVHTriangle = Fn( ( [
|
|
|
361
362
|
// float per absolute triangleIndex (4 packed per vec4): the root→leaf left(0)/right(1) choices.
|
|
362
363
|
export const calculateLightBVHPdf = Fn( ( [
|
|
363
364
|
triangleIndex, hitDistance, rayDir, shadingPoint,
|
|
364
|
-
lightBuffer, emissiveVec4Offset, reverseMapVec4Offset, triangleBuffer,
|
|
365
|
+
lightBuffer, emissiveVec4Offset, reverseMapVec4Offset, triangleBuffer, bvhBuffer,
|
|
365
366
|
] ) => {
|
|
366
367
|
|
|
367
368
|
const result = float( 0.0 ).toVar();
|
|
@@ -462,6 +463,7 @@ export const calculateLightBVHPdf = Fn( ( [
|
|
|
462
463
|
const leafTotalPower = max( d0.w, float( 1e-10 ) );
|
|
463
464
|
|
|
464
465
|
const targetPower = float( 0.0 ).toVar();
|
|
466
|
+
const targetInstance = int( - 1 ).toVar();
|
|
465
467
|
Loop( { start: int( 0 ), end: emissiveCount }, ( { i } ) => {
|
|
466
468
|
|
|
467
469
|
const entryIdx = emissiveStart.add( i );
|
|
@@ -469,6 +471,7 @@ export const calculateLightBVHPdf = Fn( ( [
|
|
|
469
471
|
If( int( emData0.r ).equal( triIdx ), () => {
|
|
470
472
|
|
|
471
473
|
targetPower.assign( max( emData0.g, float( 0.0 ) ) );
|
|
474
|
+
targetInstance.assign( int( emData0.a ) );
|
|
472
475
|
Break();
|
|
473
476
|
|
|
474
477
|
} );
|
|
@@ -478,7 +481,7 @@ export const calculateLightBVHPdf = Fn( ( [
|
|
|
478
481
|
selectionPdf.mulAssign( targetPower.div( leafTotalPower ) );
|
|
479
482
|
|
|
480
483
|
// Convert selection pdf → solid-angle measure using the SAME heuristic as the sampler.
|
|
481
|
-
const triData = TriangleData.wrap( fetchTriangleData( triIdx, triangleBuffer ) );
|
|
484
|
+
const triData = TriangleData.wrap( fetchTriangleData( triIdx, triangleBuffer, bvhBuffer, targetInstance ) );
|
|
482
485
|
If( useSphericalSampling( triData.v0, triData.v1, triData.v2, shadingPoint ), () => {
|
|
483
486
|
|
|
484
487
|
const solidAngle = sphericalTriangleSolidAngle( triData.v0, triData.v1, triData.v2, shadingPoint );
|
package/src/TSL/LightsDirect.js
CHANGED
|
@@ -22,10 +22,14 @@ import {
|
|
|
22
22
|
clamp,
|
|
23
23
|
smoothstep,
|
|
24
24
|
select,
|
|
25
|
+
uintBitsToFloat,
|
|
25
26
|
} from 'three/tsl';
|
|
26
27
|
|
|
27
28
|
import { Ray, ShadowMaterial, HitInfo } from './Struct.js';
|
|
28
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
REC709_LUMINANCE_COEFFICIENTS, getShadowMaterial, getDatafromStorageBuffer, instanceRows,
|
|
31
|
+
instanceFaceNormalToWorld, TRI_STRIDE, getAlphaShadowsUniform, shadowFlagsSettle
|
|
32
|
+
} from './Common.js';
|
|
29
33
|
import { fresnelSchlickFloat, iorToFresnel0 } from './Fresnel.js';
|
|
30
34
|
import { calculateBeerLawAbsorption } from './MaterialTransmission.js';
|
|
31
35
|
import { getTransformedUV, sampleBucket } from './TextureSampling.js';
|
|
@@ -33,7 +37,8 @@ import { getTransformedUV, sampleBucket } from './TextureSampling.js';
|
|
|
33
37
|
// Module-level state for alpha-cutout shadow testing.
|
|
34
38
|
// Set by PathTracer before shade-graph construction.
|
|
35
39
|
let _shadowAlbedoMaps = null;
|
|
36
|
-
|
|
40
|
+
|
|
41
|
+
export { setAlphaShadowsUniform } from './Common.js';
|
|
37
42
|
|
|
38
43
|
/**
|
|
39
44
|
* Set the sRGB bucket texture node array for alpha-aware shadow rays (albedo alpha).
|
|
@@ -46,16 +51,6 @@ export function setShadowAlbedoMaps( buckets ) {
|
|
|
46
51
|
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
/**
|
|
50
|
-
* Set the runtime uniform node that toggles alpha-cutout shadows.
|
|
51
|
-
* @param {UniformNode} node - TSL int uniform (0 = disabled, 1 = enabled)
|
|
52
|
-
*/
|
|
53
|
-
export function setAlphaShadowsUniform( node ) {
|
|
54
|
-
|
|
55
|
-
_enableAlphaShadows = node;
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
54
|
// ================================================================================
|
|
60
55
|
// SHADOW RAY TRACING
|
|
61
56
|
// ================================================================================
|
|
@@ -94,13 +89,11 @@ export const traceShadowRay = Fn( ( [
|
|
|
94
89
|
|
|
95
90
|
} );
|
|
96
91
|
|
|
97
|
-
// Opaque fast-path: check the per-triangle blocker
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const blocker = getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 3 ), TRI_STRIDE_SR ).w;
|
|
103
|
-
If( blocker.greaterThan( 0.5 ), () => {
|
|
92
|
+
// Opaque fast-path: check the per-triangle blocker bit, set at extraction time when
|
|
93
|
+
// alphaMode/transparent/transmission/opacity all indicate a fully opaque surface.
|
|
94
|
+
// Short-circuits the 7-slot getShadowMaterial fetch and the whole alpha decision tree.
|
|
95
|
+
const flags = getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 4 ), int( TRI_STRIDE ) ).z;
|
|
96
|
+
If( shadowFlagsSettle( flags ), () => {
|
|
104
97
|
|
|
105
98
|
transmittance.assign( 0.0 );
|
|
106
99
|
Break();
|
|
@@ -118,7 +111,8 @@ export const traceShadowRay = Fn( ( [
|
|
|
118
111
|
// ---------------------------------------------------------------
|
|
119
112
|
const alphaCutout = tslBool( false ).toVar();
|
|
120
113
|
|
|
121
|
-
|
|
114
|
+
const alphaShadows = getAlphaShadowsUniform();
|
|
115
|
+
if ( alphaShadows ) If( alphaShadows.equal( int( 1 ) ), () => {
|
|
122
116
|
|
|
123
117
|
// Sample texture alpha once (shared by MASK and BLEND paths).
|
|
124
118
|
// Deferred UV: barycentrics in shadowHit.uv, triangle index in shadowHit.triangleIndex.
|
|
@@ -131,10 +125,9 @@ export const traceShadowRay = Fn( ( [
|
|
|
131
125
|
const baryU = shadowHit.uv.x;
|
|
132
126
|
const baryV = shadowHit.uv.y;
|
|
133
127
|
const baryW = float( 1.0 ).sub( baryU ).sub( baryV );
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const hitUV = uvData1.xy.mul( baryW ).add( uvData1.zw.mul( baryU ) ).add( uvData2.xy.mul( baryV ) );
|
|
128
|
+
const uvData1 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 3 ), int( TRI_STRIDE ) ) );
|
|
129
|
+
const uvData2 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 4 ), int( TRI_STRIDE ) ).xy );
|
|
130
|
+
const hitUV = uvData1.xy.mul( baryW ).add( uvData1.zw.mul( baryU ) ).add( uvData2.mul( baryV ) );
|
|
138
131
|
const albedoUV = getTransformedUV( { uv: hitUV, transform: shadowMaterial.albedoTransform } );
|
|
139
132
|
texAlpha.assign( sampleBucket( _shadowAlbedoMaps, shadowMaterial.albedoMapIndex, albedoUV ).a );
|
|
140
133
|
|
|
@@ -188,11 +181,19 @@ export const traceShadowRay = Fn( ( [
|
|
|
188
181
|
// Deferred geometric-normal compute — refetch triangle positions and
|
|
189
182
|
// derive the normal here so opaque/alpha-cutout shadow hits don't pay
|
|
190
183
|
// the cross+normalize cost in BVH traversal.
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
184
|
+
const pA = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 0 ), int( TRI_STRIDE ) ).xyz );
|
|
185
|
+
const pB = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 1 ), int( TRI_STRIDE ) ).xyz );
|
|
186
|
+
const pC = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, shadowHit.triangleIndex, int( 2 ), int( TRI_STRIDE ) ).xyz );
|
|
187
|
+
// Positions are in the blocker's object space; the cross product is a normal, so
|
|
188
|
+
// it rides the inverse-transpose back out rather than the forward matrix.
|
|
189
|
+
const objNormal = normalize( cross( pB.sub( pA ), pC.sub( pA ) ) ).toVar();
|
|
190
|
+
If( shadowHit.instanceLeaf.greaterThanEqual( int( 0 ) ), () => {
|
|
191
|
+
|
|
192
|
+
objNormal.assign( normalize( instanceFaceNormalToWorld( instanceRows( bvhBuffer, shadowHit.instanceLeaf ), objNormal ) ) );
|
|
193
|
+
|
|
194
|
+
} );
|
|
195
|
+
|
|
196
|
+
const geomNormal = objNormal;
|
|
196
197
|
shadowHit.normal.assign( geomNormal );
|
|
197
198
|
|
|
198
199
|
const entering = dot( dir, geomNormal ).lessThan( 0.0 );
|
package/src/TSL/ShadeKernel.js
CHANGED
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
readTransparentCount,
|
|
57
57
|
readMisRayT,
|
|
58
58
|
readHitDistance, readHitBarycentrics, readHitNormal,
|
|
59
|
-
readHitMaterialIndex, readHitTriangleIndex,
|
|
59
|
+
readHitMaterialIndex, readHitTriangleIndex, readHitInstanceLeaf,
|
|
60
60
|
writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf, writeRayRadiance,
|
|
61
61
|
writeGBuffer, writeGBufferHitDist, readGBuffer, gbDecodeNormalDepth,
|
|
62
62
|
readRayRadiance,
|
|
@@ -219,6 +219,7 @@ export function buildShadeKernel( params ) {
|
|
|
219
219
|
const hitUV = readHitBarycentrics( hitBufferRO, rayID ).toVar();
|
|
220
220
|
const hitMatIdx = readHitMaterialIndex( hitBufferRO, rayID ).toVar();
|
|
221
221
|
const hitTriIdx = readHitTriangleIndex( hitBufferRO, rayID ).toVar();
|
|
222
|
+
const hitInstance = readHitInstanceLeaf( hitBufferRO, rayID ).toVar();
|
|
222
223
|
|
|
223
224
|
// per-ray camera-bounce depth — advances ONLY on opaque scatter (free bounces don't); drives termination (maxBounces). Megakernel: effectiveBounces.
|
|
224
225
|
const cameraDepth = readPathBounces( rayBufferRW, rayID ).toVar();
|
|
@@ -620,7 +621,7 @@ export function buildShadeKernel( params ) {
|
|
|
620
621
|
boxTests: int( 0 ), triTests: int( 0 ),
|
|
621
622
|
} );
|
|
622
623
|
const dispResult = DisplacementResult.wrap( refineDisplacedIntersection(
|
|
623
|
-
dispRay, dispHit, triangleBuffer, material, bounceIndex,
|
|
624
|
+
dispRay, dispHit, triangleBuffer, material, bounceIndex, bvhBuffer, hitInstance,
|
|
624
625
|
) ).toVar();
|
|
625
626
|
samplingUV.assign( dispResult.uv );
|
|
626
627
|
displacedNormal.assign( dispResult.normal );
|
|
@@ -634,7 +635,7 @@ export function buildShadeKernel( params ) {
|
|
|
634
635
|
const uvTangent = vec4( 0.0 ).toVar();
|
|
635
636
|
If( material.normalMapIndex.greaterThanEqual( int( 0 ) ), () => {
|
|
636
637
|
|
|
637
|
-
uvTangent.assign( triangleUVTangent( triangleBuffer, int( hitTriIdx ), N, material.normalTransform ) );
|
|
638
|
+
uvTangent.assign( triangleUVTangent( triangleBuffer, int( hitTriIdx ), N, material.normalTransform, bvhBuffer, hitInstance ) );
|
|
638
639
|
|
|
639
640
|
} );
|
|
640
641
|
|
|
@@ -1024,7 +1025,7 @@ export function buildShadeKernel( params ) {
|
|
|
1024
1025
|
|
|
1025
1026
|
lightPdf.assign( calculateLightBVHPdf(
|
|
1026
1027
|
int( hitTriIdx ), misDist, direction, misOrigin,
|
|
1027
|
-
lightBuffer, emissiveVec4Offset, reverseMapVec4Offset, triangleBuffer,
|
|
1028
|
+
lightBuffer, emissiveVec4Offset, reverseMapVec4Offset, triangleBuffer, bvhBuffer,
|
|
1028
1029
|
) );
|
|
1029
1030
|
|
|
1030
1031
|
} ).Else( () => {
|
|
@@ -1032,6 +1033,7 @@ export function buildShadeKernel( params ) {
|
|
|
1032
1033
|
lightPdf.assign( calculateEmissiveLightPdf(
|
|
1033
1034
|
int( hitTriIdx ), misDist, direction, misOrigin,
|
|
1034
1035
|
triangleBuffer, materialBuffer, emissiveTotalPower,
|
|
1036
|
+
bvhBuffer, hitInstance,
|
|
1035
1037
|
) );
|
|
1036
1038
|
|
|
1037
1039
|
} );
|
|
@@ -1217,7 +1219,7 @@ export function buildShadeKernel( params ) {
|
|
|
1217
1219
|
lightBuffer,
|
|
1218
1220
|
lightBuffer,
|
|
1219
1221
|
emissiveVec4Offset,
|
|
1220
|
-
triangleBuffer,
|
|
1222
|
+
triangleBuffer, bvhBuffer,
|
|
1221
1223
|
) );
|
|
1222
1224
|
|
|
1223
1225
|
// No rough-diffuse secondary-bounce skip here: dropping NEE while the emissive-hit
|
|
@@ -1274,7 +1276,7 @@ export function buildShadeKernel( params ) {
|
|
|
1274
1276
|
_pixelCoord, resolution, frame, dimBase,
|
|
1275
1277
|
emissiveBoost,
|
|
1276
1278
|
lightBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
1277
|
-
triangleBuffer,
|
|
1279
|
+
triangleBuffer, bvhBuffer,
|
|
1278
1280
|
traceShadowRayWrapped,
|
|
1279
1281
|
calculateRayOffset,
|
|
1280
1282
|
);
|
package/src/TSL/Struct.js
CHANGED
|
@@ -122,6 +122,9 @@ export const HitInfo = struct( {
|
|
|
122
122
|
uv: 'vec2',
|
|
123
123
|
materialIndex: 'int',
|
|
124
124
|
meshIndex: 'int',
|
|
125
|
+
// TLAS leaf that owns the hit: triangles are stored per instance in object space, so
|
|
126
|
+
// anything that needs them in world space reads the transform back off this node.
|
|
127
|
+
instanceLeaf: 'int',
|
|
125
128
|
triangleIndex: 'int',
|
|
126
129
|
boxTests: 'int',
|
|
127
130
|
triTests: 'int',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, wgslFn, float, vec2, vec3, vec4, int, If, normalize, cross, dot, length, sign, abs, atan, mix, clamp, texture, textureSize } from 'three/tsl';
|
|
1
|
+
import { Fn, wgslFn, float, vec2, vec3, vec4, int, If, normalize, cross, dot, length, sign, abs, atan, mix, clamp, texture, textureSize, uintBitsToFloat } from 'three/tsl';
|
|
2
2
|
import { DataArrayTexture, LinearFilter } from 'three';
|
|
3
3
|
|
|
4
4
|
import {
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
MaterialSamples,
|
|
7
7
|
ExtMapResult,
|
|
8
8
|
} from './Struct.js';
|
|
9
|
-
import { getDatafromStorageBuffer } from './Common.js';
|
|
9
|
+
import { getDatafromStorageBuffer, instanceRows, instanceDirToWorld, TRI_STRIDE } from './Common.js';
|
|
10
10
|
import { TEXTURE_CONSTANTS } from '../EngineDefaults.js';
|
|
11
11
|
|
|
12
12
|
// ================================================================================
|
|
@@ -405,15 +405,22 @@ const uvTransformJacobian = /*@__PURE__*/ wgslFn( `
|
|
|
405
405
|
* @param {Node} geometryNormal shading-space normal, for the handedness test
|
|
406
406
|
* @param {Node} transform the normal map's mat3 UV transform
|
|
407
407
|
*/
|
|
408
|
-
|
|
408
|
+
/**
|
|
409
|
+
* Tangent frame for a triangle, in world space.
|
|
410
|
+
*
|
|
411
|
+
* Positions are stored per instance in object space, so T and B come out in that space
|
|
412
|
+
* and are carried across before they meet the world-space geometric normal — otherwise
|
|
413
|
+
* the handedness test mixes two frames and normal maps light from the wrong side.
|
|
414
|
+
*/
|
|
415
|
+
export const triangleUVTangent = Fn( ( [ triangleBuffer, triIndex, geometryNormal, transform, bvhBuffer, instanceLeaf ] ) => {
|
|
409
416
|
|
|
410
|
-
const S = int(
|
|
411
|
-
const pA = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 0 ), S ).xyz;
|
|
412
|
-
const e1 = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 1 ), S ).xyz.sub( pA );
|
|
413
|
-
const e2 = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 2 ), S ).xyz.sub( pA );
|
|
417
|
+
const S = int( TRI_STRIDE );
|
|
418
|
+
const pA = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 0 ), S ).xyz );
|
|
419
|
+
const e1 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 1 ), S ).xyz ).sub( pA );
|
|
420
|
+
const e2 = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 2 ), S ).xyz ).sub( pA );
|
|
414
421
|
|
|
415
|
-
const uvAB = getDatafromStorageBuffer( triangleBuffer, triIndex, int(
|
|
416
|
-
const uvC = getDatafromStorageBuffer( triangleBuffer, triIndex, int(
|
|
422
|
+
const uvAB = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 3 ), S ) );
|
|
423
|
+
const uvC = uintBitsToFloat( getDatafromStorageBuffer( triangleBuffer, triIndex, int( 4 ), S ).xy );
|
|
417
424
|
const d1 = uvAB.zw.sub( uvAB.xy );
|
|
418
425
|
const d2 = uvC.sub( uvAB.xy );
|
|
419
426
|
|
|
@@ -436,6 +443,14 @@ export const triangleUVTangent = Fn( ( [ triangleBuffer, triIndex, geometryNorma
|
|
|
436
443
|
|
|
437
444
|
} );
|
|
438
445
|
|
|
446
|
+
If( instanceLeaf.greaterThanEqual( int( 0 ) ), () => {
|
|
447
|
+
|
|
448
|
+
const rows = instanceRows( bvhBuffer, instanceLeaf );
|
|
449
|
+
T.assign( instanceDirToWorld( { r0: rows[ 0 ].xyz, r1: rows[ 1 ].xyz, r2: rows[ 2 ].xyz, v: T } ) );
|
|
450
|
+
B.assign( instanceDirToWorld( { r0: rows[ 0 ].xyz, r1: rows[ 1 ].xyz, r2: rows[ 2 ].xyz, v: B } ) );
|
|
451
|
+
|
|
452
|
+
} );
|
|
453
|
+
|
|
439
454
|
If( length( T ).greaterThan( 1e-12 ), () => {
|
|
440
455
|
|
|
441
456
|
result.assign( vec4( normalize( T ), sign( dot( cross( geometryNormal, T ), B ) ) ) );
|