rayzee 7.21.0 → 7.22.1
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 +75 -4
- package/dist/rayzee.es.js +634 -604
- package/dist/rayzee.umd.js +52 -52
- package/package.json +1 -1
- package/src/EngineDefaults.js +6 -0
- package/src/PathTracerApp.js +76 -0
- package/src/Processor/BVHBuilder.js +4 -6
- package/src/Processor/KernelManager.js +17 -70
- package/src/Processor/QueueManager.js +4 -3
- package/src/Processor/SceneProcessor.js +12 -4
- package/src/Processor/TreeletOptimizer.js +0 -7
- package/src/RenderSettings.js +41 -6
- package/src/Stages/NormalDepth.js +16 -13
- package/src/Stages/PathTracer.js +29 -31
- package/src/TSL/BVHTraversal.js +1 -67
- package/src/TSL/CameraRay.js +141 -0
- package/src/TSL/DebugKernel.js +4 -5
- package/src/TSL/ExtendKernel.js +11 -3
- package/src/TSL/FinalWriteKernel.js +1 -14
- package/src/TSL/GenerateKernel.js +11 -8
- package/src/TSL/SortGlobalKernels.js +52 -7
- package/src/managers/DenoisingManager.js +13 -0
- package/src/managers/EnvironmentManager.js +6 -3
- package/src/managers/UniformManager.js +8 -1
package/src/Stages/PathTracer.js
CHANGED
|
@@ -31,6 +31,13 @@ import {
|
|
|
31
31
|
|
|
32
32
|
const log = createLogger( 'wavefront' );
|
|
33
33
|
|
|
34
|
+
// Shared by the 1D index-list kernels built inline below.
|
|
35
|
+
const LIST_WG_SIZE = 256;
|
|
36
|
+
|
|
37
|
+
// Resized per bounce iteration, each from its own registered workgroup size. Unregistered entries
|
|
38
|
+
// (the sort passes when _sortMaterials is off) are skipped by setDispatchForCount.
|
|
39
|
+
const BOUNCE_KERNELS = [ 'extend', 'shade', 'globalHist', 'globalScatter', 'compact', 'compactCopyback' ];
|
|
40
|
+
|
|
34
41
|
export class PathTracer extends PathTracerStage {
|
|
35
42
|
|
|
36
43
|
constructor( renderer, scene, camera, options = {} ) {
|
|
@@ -350,7 +357,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
350
357
|
|
|
351
358
|
// reset counters → compact non-frozen pixel IDs → publish count → list-driven 1D generate.
|
|
352
359
|
const genSized = Math.min( maxRays, Math.ceil( activeBounce0 * 1.5 ) + 1024 );
|
|
353
|
-
km.
|
|
360
|
+
km.setDispatchForCount( 'generateList', genSized );
|
|
354
361
|
km.dispatch( 'resetFrameCounters' );
|
|
355
362
|
km.dispatch( 'buildActivePixels' );
|
|
356
363
|
km.dispatch( 'seedEnter' );
|
|
@@ -402,23 +409,12 @@ export class PathTracer extends PathTracerStage {
|
|
|
402
409
|
}
|
|
403
410
|
|
|
404
411
|
const sized = Math.min( maxRays, Math.ceil( entering * 1.5 ) + 1024 );
|
|
405
|
-
const
|
|
406
|
-
km.setDispatchCount( 'extend', wg );
|
|
407
|
-
km.setDispatchCount( 'shade', wg );
|
|
408
|
-
km.setDispatchCount( 'globalHist', wg );
|
|
409
|
-
km.setDispatchCount( 'globalScatter', wg );
|
|
410
|
-
km.setDispatchCount( 'compact', wg );
|
|
411
|
-
km.setDispatchCount( 'compactCopyback', wg );
|
|
412
|
+
for ( const k of BOUNCE_KERNELS ) km.setDispatchForCount( k, sized );
|
|
412
413
|
|
|
413
414
|
} else {
|
|
414
415
|
|
|
415
416
|
km.dispatch( 'enterFull' );
|
|
416
|
-
const
|
|
417
|
-
km.setDispatchCount( 'extend', full );
|
|
418
|
-
km.setDispatchCount( 'shade', full );
|
|
419
|
-
km.setDispatchCount( 'compact', full );
|
|
420
|
-
km.setDispatchCount( 'globalHist', full );
|
|
421
|
-
km.setDispatchCount( 'globalScatter', full );
|
|
417
|
+
for ( const k of BOUNCE_KERNELS ) km.setDispatchForCount( k, maxRays );
|
|
422
418
|
|
|
423
419
|
}
|
|
424
420
|
|
|
@@ -935,7 +931,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
935
931
|
|
|
936
932
|
} );
|
|
937
933
|
this._kernelManager.register( 'initActiveIndices',
|
|
938
|
-
initFn().compute( [ Math.ceil( ( this._chunkRows * w ) /
|
|
934
|
+
initFn().compute( [ Math.ceil( ( this._chunkRows * w ) / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
|
|
939
935
|
);
|
|
940
936
|
|
|
941
937
|
const genParams = {
|
|
@@ -948,6 +944,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
948
944
|
frame: this.seedFrame,
|
|
949
945
|
cameraWorldMatrix: this.cameraWorldMatrix,
|
|
950
946
|
cameraProjectionMatrixInverse: this.cameraProjectionMatrixInverse,
|
|
947
|
+
cameraProjection: this.cameraProjection,
|
|
948
|
+
panoLonRange: this.panoLonRange,
|
|
949
|
+
panoLatRange: this.panoLatRange,
|
|
950
|
+
panoLevelHorizon: this.panoLevelHorizon,
|
|
951
951
|
enableDOF: this.enableDOF,
|
|
952
952
|
focalLength: this.focalLength,
|
|
953
953
|
aperture: this.aperture,
|
|
@@ -977,13 +977,12 @@ export class PathTracer extends PathTracerStage {
|
|
|
977
977
|
const freezeK = this.pixelFreezeStability;
|
|
978
978
|
const resetFrameFn = Fn( () => {
|
|
979
979
|
|
|
980
|
-
// ACTIVE is per-chunk (zeroed before every chunk's scatter). CONVERGED
|
|
981
|
-
// summed across chunks in FinalWrite → zero
|
|
980
|
+
// ACTIVE is per-chunk (zeroed before every chunk's scatter). CONVERGED is a per-FRAME counter
|
|
981
|
+
// summed across chunks in FinalWrite → zero it only on the first chunk of the frame.
|
|
982
982
|
atomicStore( counters.element( uint( COUNTER.ACTIVE_RAY_COUNT ) ), uint( 0 ) );
|
|
983
983
|
If( this._wfIsFirstChunk.greaterThan( uint( 0 ) ), () => {
|
|
984
984
|
|
|
985
985
|
atomicStore( counters.element( uint( COUNTER.CONVERGED_COUNT ) ), uint( 0 ) );
|
|
986
|
-
atomicStore( counters.element( uint( COUNTER.FROZEN_COUNT ) ), uint( 0 ) );
|
|
987
986
|
|
|
988
987
|
} );
|
|
989
988
|
|
|
@@ -1050,7 +1049,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1050
1049
|
|
|
1051
1050
|
} );
|
|
1052
1051
|
this._kernelManager.register( 'buildActivePixels',
|
|
1053
|
-
buildActiveFn().compute( [ Math.ceil( ( this._chunkRows * w ) /
|
|
1052
|
+
buildActiveFn().compute( [ Math.ceil( ( this._chunkRows * w ) / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
|
|
1054
1053
|
);
|
|
1055
1054
|
|
|
1056
1055
|
const seedEnterFn = Fn( () => {
|
|
@@ -1073,7 +1072,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1073
1072
|
counters,
|
|
1074
1073
|
} );
|
|
1075
1074
|
this._kernelManager.register( 'generateList',
|
|
1076
|
-
genListFn().compute( [ Math.ceil( maxRays /
|
|
1075
|
+
genListFn().compute( [ Math.ceil( maxRays / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
|
|
1077
1076
|
);
|
|
1078
1077
|
|
|
1079
1078
|
const freshBvh = this.bvhStorageNode;
|
|
@@ -1108,7 +1107,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
1108
1107
|
const extFn = buildExtendKernel( {
|
|
1109
1108
|
bvhBuffer: freshBvh,
|
|
1110
1109
|
triangleBuffer: freshTri,
|
|
1111
|
-
materialBuffer: freshMat,
|
|
1112
1110
|
rayBufferRO: pb.rayBuffer.ro,
|
|
1113
1111
|
hitBufferRW: pb.hitBuffer.rw,
|
|
1114
1112
|
activeIndicesRO: qm.getActiveReadRO(),
|
|
@@ -1292,7 +1290,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1292
1290
|
|
|
1293
1291
|
} );
|
|
1294
1292
|
this._kernelManager.register( 'compactCopyback',
|
|
1295
|
-
copyFn().compute( [ Math.ceil( maxRays /
|
|
1293
|
+
copyFn().compute( [ Math.ceil( maxRays / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
|
|
1296
1294
|
);
|
|
1297
1295
|
|
|
1298
1296
|
const fwFn = buildFinalWriteKernel( {
|
|
@@ -1324,7 +1322,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
1324
1322
|
// Tier-2 freeze (stamp + pass-through)
|
|
1325
1323
|
usePixelFreeze: this.usePixelFreeze,
|
|
1326
1324
|
pixelFreezeThreshold: this.pixelFreezeThreshold,
|
|
1327
|
-
pixelFreezeStability: this.pixelFreezeStability,
|
|
1328
1325
|
streakBufferRW: streakRW,
|
|
1329
1326
|
frozenMaskRO, // dilated frozen mask (read-only; matches the active-list decision)
|
|
1330
1327
|
chunkRowBase: this._wfChunkRowBase,
|
|
@@ -1351,6 +1348,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
1351
1348
|
cameraProjectionMatrixInverse: this.cameraProjectionMatrixInverse,
|
|
1352
1349
|
cameraProjectionMatrix: this.cameraProjectionMatrix,
|
|
1353
1350
|
cameraViewMatrix: this.cameraViewMatrix,
|
|
1351
|
+
cameraProjection: this.cameraProjection,
|
|
1352
|
+
panoLonRange: this.panoLonRange,
|
|
1353
|
+
panoLatRange: this.panoLatRange,
|
|
1354
|
+
panoLevelHorizon: this.panoLevelHorizon,
|
|
1354
1355
|
enableDOF: this.enableDOF,
|
|
1355
1356
|
focalLength: this.focalLength,
|
|
1356
1357
|
aperture: this.aperture,
|
|
@@ -1396,10 +1397,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1396
1397
|
const w = this._wfRenderWidth.value;
|
|
1397
1398
|
const h = this._wfRenderHeight.value;
|
|
1398
1399
|
|
|
1399
|
-
this._kernelManager.
|
|
1400
|
-
Math.ceil( w / DEBUG_WG_SIZE ),
|
|
1401
|
-
Math.ceil( h / DEBUG_WG_SIZE ), 1
|
|
1402
|
-
] );
|
|
1400
|
+
this._kernelManager.setDispatchForGrid( 'debug', w, h );
|
|
1403
1401
|
|
|
1404
1402
|
}
|
|
1405
1403
|
|
|
@@ -1416,10 +1414,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
1416
1414
|
this._wfMaxRayCount.value = chunkPixels;
|
|
1417
1415
|
|
|
1418
1416
|
const km = this._kernelManager;
|
|
1419
|
-
km.
|
|
1420
|
-
km.
|
|
1421
|
-
km.
|
|
1422
|
-
km.
|
|
1417
|
+
km.setDispatchForGrid( 'generate', w, rows );
|
|
1418
|
+
km.setDispatchForGrid( 'finalWrite', w, rows );
|
|
1419
|
+
km.setDispatchForCount( 'initActiveIndices', chunkPixels );
|
|
1420
|
+
km.setDispatchForCount( 'buildActivePixels', chunkPixels );
|
|
1423
1421
|
|
|
1424
1422
|
return chunkPixels;
|
|
1425
1423
|
|
package/src/TSL/BVHTraversal.js
CHANGED
|
@@ -14,17 +14,14 @@ import {
|
|
|
14
14
|
min,
|
|
15
15
|
normalize,
|
|
16
16
|
mix,
|
|
17
|
-
vec4,
|
|
18
17
|
notEqual,
|
|
19
18
|
lessThan,
|
|
20
|
-
mat3,
|
|
21
19
|
array,
|
|
22
20
|
bool as tslBool,
|
|
23
21
|
} from 'three/tsl';
|
|
24
22
|
|
|
25
|
-
import {
|
|
23
|
+
import { HitInfo } from './Struct.js';
|
|
26
24
|
import { getDatafromStorageBuffer } from './Common.js';
|
|
27
|
-
import { RandomPointInCircle } from './Random.js';
|
|
28
25
|
|
|
29
26
|
const MAX_STACK_DEPTH = 32;
|
|
30
27
|
const MAX_BVH_ITERATIONS = 512;
|
|
@@ -559,66 +556,3 @@ export const traverseBVHShadow = Fn( ( [
|
|
|
559
556
|
return closestHit;
|
|
560
557
|
|
|
561
558
|
} );
|
|
562
|
-
|
|
563
|
-
// ================================================================================
|
|
564
|
-
// CAMERA RAY GENERATION
|
|
565
|
-
// ================================================================================
|
|
566
|
-
|
|
567
|
-
export const generateRayFromCamera = Fn( ( [
|
|
568
|
-
screenPosition, rngState,
|
|
569
|
-
cameraWorldMatrix, cameraProjectionMatrixInverse,
|
|
570
|
-
enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio
|
|
571
|
-
] ) => {
|
|
572
|
-
|
|
573
|
-
// Convert screen position to NDC
|
|
574
|
-
const ndcPos = vec3( screenPosition.xy, 1.0 );
|
|
575
|
-
|
|
576
|
-
// Convert NDC to camera space
|
|
577
|
-
const rayDirCS = cameraProjectionMatrixInverse.mul( vec4( ndcPos, 1.0 ) );
|
|
578
|
-
|
|
579
|
-
// Convert to world space
|
|
580
|
-
const rayDirectionWorld = normalize( mat3(
|
|
581
|
-
cameraWorldMatrix[ 0 ].xyz,
|
|
582
|
-
cameraWorldMatrix[ 1 ].xyz,
|
|
583
|
-
cameraWorldMatrix[ 2 ].xyz
|
|
584
|
-
).mul( rayDirCS.xyz.div( rayDirCS.w ) ) ).toVar();
|
|
585
|
-
|
|
586
|
-
const rayOriginWorld = vec3( cameraWorldMatrix[ 3 ] ).toVar();
|
|
587
|
-
|
|
588
|
-
const resultOrigin = rayOriginWorld.toVar();
|
|
589
|
-
const resultDirection = rayDirectionWorld.toVar();
|
|
590
|
-
|
|
591
|
-
// Check if DOF is disabled or conditions make it ineffective
|
|
592
|
-
If( enableDOF.and( focalLength.greaterThan( 0.0 ) ).and( aperture.lessThan( 64.0 ) ).and( focusDistance.greaterThan( 0.001 ) ), () => {
|
|
593
|
-
|
|
594
|
-
// Physical aperture calculation
|
|
595
|
-
const effectiveAperture = focalLength.div( aperture );
|
|
596
|
-
// Apply scene scale to maintain correct physical aperture size
|
|
597
|
-
const apertureRadius = effectiveAperture.mul( 0.001 ).mul( sceneScale ).mul( apertureScale );
|
|
598
|
-
|
|
599
|
-
// Generate random point on aperture disk
|
|
600
|
-
const randomPoint = RandomPointInCircle( rngState );
|
|
601
|
-
|
|
602
|
-
// Apply anamorphic squeeze — stretch horizontally for oval bokeh
|
|
603
|
-
const lensX = randomPoint.x.mul( anamorphicRatio.max( 0.01 ) );
|
|
604
|
-
const lensY = randomPoint.y;
|
|
605
|
-
|
|
606
|
-
// Extract camera coordinate system directly from camera matrix
|
|
607
|
-
const cameraRight = normalize( vec3( cameraWorldMatrix[ 0 ] ) );
|
|
608
|
-
const cameraUp = normalize( vec3( cameraWorldMatrix[ 1 ] ) );
|
|
609
|
-
|
|
610
|
-
// Apply aperture offset using camera's actual coordinate system
|
|
611
|
-
const offset = cameraRight.mul( lensX ).add( cameraUp.mul( lensY ) ).mul( apertureRadius );
|
|
612
|
-
|
|
613
|
-
// Calculate new ray from offset origin to focal point
|
|
614
|
-
resultOrigin.assign( rayOriginWorld.add( offset ) );
|
|
615
|
-
resultDirection.assign( normalize( rayOriginWorld.add( rayDirectionWorld.mul( focusDistance ) ).sub( resultOrigin ) ) );
|
|
616
|
-
|
|
617
|
-
} );
|
|
618
|
-
|
|
619
|
-
return Ray( {
|
|
620
|
-
origin: resultOrigin,
|
|
621
|
-
direction: resultDirection,
|
|
622
|
-
} );
|
|
623
|
-
|
|
624
|
-
} );
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CameraRay.js — primary camera ray generation.
|
|
3
|
+
*
|
|
4
|
+
* Two projections behind one `cameraProjection` uniform: 0 = pinhole (NDC through
|
|
5
|
+
* cameraProjectionMatrixInverse), 1 = equirectangular 360 panorama. Both branches live in the same
|
|
6
|
+
* kernel, so switching modes writes a uniform and resets accumulation — it never recompiles WGSL.
|
|
7
|
+
*
|
|
8
|
+
* Equirect mapping, for uv ∈ [0,1] with v = 0 at the top row:
|
|
9
|
+
* lon = mix( lonMin, lonMax, u ), lat = mix( latMax, latMin, v )
|
|
10
|
+
* dirCam = ( sin(lon)·cos(lat), sin(lat), -cos(lon)·cos(lat) )
|
|
11
|
+
* so lon = lat = 0 is camera-forward (image centre), u = 0.75 is camera-right and v = 0 is the zenith.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
Fn, vec3, vec4, float, int,
|
|
16
|
+
If, normalize, mat3, mix, sin, cos, cross, dot,
|
|
17
|
+
} from 'three/tsl';
|
|
18
|
+
|
|
19
|
+
import { Ray } from './Struct.js';
|
|
20
|
+
import { constructTBN } from './Common.js';
|
|
21
|
+
import { RandomPointInCircle } from './Random.js';
|
|
22
|
+
|
|
23
|
+
/** World-space primary ray direction for pixel uv. Origin is always cameraWorldMatrix[3]. */
|
|
24
|
+
export const cameraRayDirection = Fn( ( [
|
|
25
|
+
uv01, cameraWorldMatrix, cameraProjectionMatrixInverse,
|
|
26
|
+
cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon
|
|
27
|
+
] ) => {
|
|
28
|
+
|
|
29
|
+
const direction = vec3( 0.0 ).toVar();
|
|
30
|
+
|
|
31
|
+
If( cameraProjection.equal( int( 1 ) ), () => {
|
|
32
|
+
|
|
33
|
+
const lon = mix( panoLonRange.x, panoLonRange.y, uv01.x ).toVar();
|
|
34
|
+
const lat = mix( panoLatRange.y, panoLatRange.x, uv01.y ).toVar();
|
|
35
|
+
const cosLat = cos( lat ).toVar();
|
|
36
|
+
|
|
37
|
+
const right = vec3( 0.0 ).toVar();
|
|
38
|
+
const up = vec3( 0.0 ).toVar();
|
|
39
|
+
const back = vec3( 0.0 ).toVar();
|
|
40
|
+
|
|
41
|
+
If( panoLevelHorizon.equal( int( 1 ) ), () => {
|
|
42
|
+
|
|
43
|
+
// Yaw-only frame — orbit pitch/roll must not tilt the panorama. The flattened column
|
|
44
|
+
// is normalized below, so it needs no pre-normalize.
|
|
45
|
+
const flat = vec3( cameraWorldMatrix[ 2 ].x, 0.0, cameraWorldMatrix[ 2 ].z ).toVar();
|
|
46
|
+
// Straight up/down: the camera's own up vector is the horizontal one.
|
|
47
|
+
If( dot( flat, flat ).lessThan( float( 1e-8 ) ), () => {
|
|
48
|
+
|
|
49
|
+
flat.assign( vec3( cameraWorldMatrix[ 1 ].x, 0.0, cameraWorldMatrix[ 1 ].z ) );
|
|
50
|
+
|
|
51
|
+
} );
|
|
52
|
+
|
|
53
|
+
back.assign( normalize( flat ) );
|
|
54
|
+
up.assign( vec3( 0.0, 1.0, 0.0 ) );
|
|
55
|
+
right.assign( cross( up, back ) );
|
|
56
|
+
|
|
57
|
+
} ).Else( () => {
|
|
58
|
+
|
|
59
|
+
right.assign( normalize( vec3( cameraWorldMatrix[ 0 ] ) ) );
|
|
60
|
+
up.assign( normalize( vec3( cameraWorldMatrix[ 1 ] ) ) );
|
|
61
|
+
back.assign( normalize( vec3( cameraWorldMatrix[ 2 ] ) ) );
|
|
62
|
+
|
|
63
|
+
} );
|
|
64
|
+
|
|
65
|
+
direction.assign( normalize(
|
|
66
|
+
right.mul( sin( lon ).mul( cosLat ) )
|
|
67
|
+
.add( up.mul( sin( lat ) ) )
|
|
68
|
+
.sub( back.mul( cos( lon ).mul( cosLat ) ) )
|
|
69
|
+
) );
|
|
70
|
+
|
|
71
|
+
} ).Else( () => {
|
|
72
|
+
|
|
73
|
+
const ndcPos = vec3( uv01.x.mul( 2.0 ).sub( 1.0 ), float( 1.0 ).sub( uv01.y.mul( 2.0 ) ), 1.0 );
|
|
74
|
+
const rayDirCS = cameraProjectionMatrixInverse.mul( vec4( ndcPos, 1.0 ) );
|
|
75
|
+
|
|
76
|
+
direction.assign( normalize( mat3(
|
|
77
|
+
cameraWorldMatrix[ 0 ].xyz,
|
|
78
|
+
cameraWorldMatrix[ 1 ].xyz,
|
|
79
|
+
cameraWorldMatrix[ 2 ].xyz
|
|
80
|
+
).mul( rayDirCS.xyz.div( rayDirCS.w ) ) ) );
|
|
81
|
+
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
return direction;
|
|
85
|
+
|
|
86
|
+
} );
|
|
87
|
+
|
|
88
|
+
export const generateRayFromCamera = Fn( ( [
|
|
89
|
+
uv01, rngState,
|
|
90
|
+
cameraWorldMatrix, cameraProjectionMatrixInverse,
|
|
91
|
+
cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
|
|
92
|
+
enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio
|
|
93
|
+
] ) => {
|
|
94
|
+
|
|
95
|
+
const rayOriginWorld = vec3( cameraWorldMatrix[ 3 ] ).toVar();
|
|
96
|
+
// .toVar() so the two reads below don't inline the whole projection graph twice.
|
|
97
|
+
const rayDirectionWorld = cameraRayDirection(
|
|
98
|
+
uv01, cameraWorldMatrix, cameraProjectionMatrixInverse,
|
|
99
|
+
cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon
|
|
100
|
+
).toVar();
|
|
101
|
+
|
|
102
|
+
const resultOrigin = rayOriginWorld.toVar();
|
|
103
|
+
const resultDirection = rayDirectionWorld.toVar();
|
|
104
|
+
|
|
105
|
+
If( enableDOF.and( focalLength.greaterThan( 0.0 ) ).and( aperture.lessThan( 64.0 ) ).and( focusDistance.greaterThan( 0.001 ) ), () => {
|
|
106
|
+
|
|
107
|
+
const effectiveAperture = focalLength.div( aperture );
|
|
108
|
+
const apertureRadius = effectiveAperture.mul( 0.001 ).mul( sceneScale ).mul( apertureScale );
|
|
109
|
+
|
|
110
|
+
const randomPoint = RandomPointInCircle( rngState );
|
|
111
|
+
// Anamorphic squeeze — stretch horizontally for oval bokeh
|
|
112
|
+
const lensX = randomPoint.x.mul( anamorphicRatio.max( 0.01 ) );
|
|
113
|
+
const lensY = randomPoint.y;
|
|
114
|
+
|
|
115
|
+
const lensOffset = vec3( 0.0 ).toVar();
|
|
116
|
+
|
|
117
|
+
If( cameraProjection.equal( int( 1 ) ), () => {
|
|
118
|
+
|
|
119
|
+
// Every pixel points a different way, so the lens plane is built around the ray. The
|
|
120
|
+
// camera's right/up would skew bokeh into a slit away from the image centre.
|
|
121
|
+
lensOffset.assign( constructTBN( { N: rayDirectionWorld } ).mul( vec3( lensX, lensY, 0.0 ) ) );
|
|
122
|
+
|
|
123
|
+
} ).Else( () => {
|
|
124
|
+
|
|
125
|
+
const camRight = normalize( vec3( cameraWorldMatrix[ 0 ] ) );
|
|
126
|
+
const camUp = normalize( vec3( cameraWorldMatrix[ 1 ] ) );
|
|
127
|
+
lensOffset.assign( camRight.mul( lensX ).add( camUp.mul( lensY ) ) );
|
|
128
|
+
|
|
129
|
+
} );
|
|
130
|
+
|
|
131
|
+
resultOrigin.assign( rayOriginWorld.add( lensOffset.mul( apertureRadius ) ) );
|
|
132
|
+
resultDirection.assign( normalize( rayOriginWorld.add( rayDirectionWorld.mul( focusDistance ) ).sub( resultOrigin ) ) );
|
|
133
|
+
|
|
134
|
+
} );
|
|
135
|
+
|
|
136
|
+
return Ray( {
|
|
137
|
+
origin: resultOrigin,
|
|
138
|
+
direction: resultDirection,
|
|
139
|
+
} );
|
|
140
|
+
|
|
141
|
+
} );
|
package/src/TSL/DebugKernel.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
localId, workgroupId,
|
|
14
14
|
} from 'three/tsl';
|
|
15
15
|
|
|
16
|
-
import { generateRayFromCamera } from './
|
|
16
|
+
import { generateRayFromCamera } from './CameraRay.js';
|
|
17
17
|
import { Ray } from './Struct.js';
|
|
18
18
|
import { TraceDebugMode } from './Debugger.js';
|
|
19
19
|
import { pcgHash, getStratifiedSample } from './Random.js';
|
|
@@ -26,6 +26,7 @@ export function buildDebugKernel( params ) {
|
|
|
26
26
|
writeColorTex, writeNDTex, writeAlbedoTex,
|
|
27
27
|
resolution, renderWidth, renderHeight,
|
|
28
28
|
cameraWorldMatrix, cameraProjectionMatrixInverse, cameraProjectionMatrix, cameraViewMatrix,
|
|
29
|
+
cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
|
|
29
30
|
enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
|
|
30
31
|
bvhBuffer, triangleBuffer, materialBuffer,
|
|
31
32
|
envTexture, environmentMatrix, environmentIntensity, enableEnvironmentLight,
|
|
@@ -45,12 +46,10 @@ export function buildDebugKernel( params ) {
|
|
|
45
46
|
const seed = pcgHash( { state: uint( pixelIndex ).add( uint( 1 ) ) } ).toVar();
|
|
46
47
|
|
|
47
48
|
// Center-pixel primary ray (no AA jitter — debug viz wants a stable, sharp image).
|
|
48
|
-
const screenPosition = pixelCoord.div( resolution ).mul( 2.0 ).sub( 1.0 ).toVar();
|
|
49
|
-
screenPosition.y.assign( screenPosition.y.negate() );
|
|
50
|
-
|
|
51
49
|
const ray = Ray.wrap( generateRayFromCamera(
|
|
52
|
-
|
|
50
|
+
pixelCoord.div( resolution ), seed,
|
|
53
51
|
cameraWorldMatrix, cameraProjectionMatrixInverse,
|
|
52
|
+
cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
|
|
54
53
|
enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
|
|
55
54
|
) );
|
|
56
55
|
|
package/src/TSL/ExtendKernel.js
CHANGED
|
@@ -13,17 +13,17 @@ import {
|
|
|
13
13
|
import { traverseBVH } from './BVHTraversal.js';
|
|
14
14
|
import { Ray, HitInfo } from './Struct.js';
|
|
15
15
|
import {
|
|
16
|
-
readRayOrigin, readRayDirection, readMediumStack,
|
|
16
|
+
readRayOrigin, readRayDirection, readRayBounceFlags, readMediumStack,
|
|
17
17
|
writeHitPacked,
|
|
18
18
|
} from '../Processor/PackedRayBuffer.js';
|
|
19
|
-
import { COUNTER } from '../Processor/QueueManager.js';
|
|
19
|
+
import { COUNTER, RAY_FLAG } from '../Processor/QueueManager.js';
|
|
20
20
|
|
|
21
21
|
const WG_SIZE = 256;
|
|
22
22
|
|
|
23
23
|
export function buildExtendKernel( params ) {
|
|
24
24
|
|
|
25
25
|
const {
|
|
26
|
-
bvhBuffer, triangleBuffer,
|
|
26
|
+
bvhBuffer, triangleBuffer,
|
|
27
27
|
rayBufferRO,
|
|
28
28
|
hitBufferRW,
|
|
29
29
|
activeIndicesRO,
|
|
@@ -45,6 +45,14 @@ export function buildExtendKernel( params ) {
|
|
|
45
45
|
|
|
46
46
|
const rayID = activeIndicesRO.element( threadIdx );
|
|
47
47
|
|
|
48
|
+
// Parity with Shade's guard. Free — the flags share DIR_FLAGS.w with the direction read below.
|
|
49
|
+
// Only culls work on the pinned-dispatch path, whose identity list still holds dead rays.
|
|
50
|
+
If( readRayBounceFlags( rayBufferRO, rayID ).bitAnd( uint( RAY_FLAG.ACTIVE ) ).equal( uint( 0 ) ), () => {
|
|
51
|
+
|
|
52
|
+
Return();
|
|
53
|
+
|
|
54
|
+
} );
|
|
55
|
+
|
|
48
56
|
const origin = readRayOrigin( rayBufferRO, rayID ).toVar();
|
|
49
57
|
const direction = readRayDirection( rayBufferRO, rayID ).toVar();
|
|
50
58
|
|
|
@@ -54,7 +54,7 @@ export function buildFinalWriteKernel( params ) {
|
|
|
54
54
|
|
|
55
55
|
// Tier-2 per-pixel freeze: streakBufferRW = per-pixel freeze-candidate streak (stamped here);
|
|
56
56
|
// frozenMaskRO = dilated frozen mask from buildActivePixels (pass-through gates on it, not streak).
|
|
57
|
-
usePixelFreeze, pixelFreezeThreshold,
|
|
57
|
+
usePixelFreeze, pixelFreezeThreshold, streakBufferRW, frozenMaskRO,
|
|
58
58
|
} = params;
|
|
59
59
|
|
|
60
60
|
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
@@ -184,25 +184,12 @@ export function buildFinalWriteKernel( params ) {
|
|
|
184
184
|
|
|
185
185
|
} );
|
|
186
186
|
|
|
187
|
-
If( wasFrozen, () => {
|
|
188
|
-
|
|
189
|
-
// Already frozen (passed through above) — count it.
|
|
190
|
-
atomicAdd( counters.element( uint( COUNTER.FROZEN_COUNT ) ), uint( 1 ) );
|
|
191
|
-
|
|
192
|
-
} );
|
|
193
|
-
|
|
194
187
|
If( wasFrozen.not().and( frame.greaterThan( uint( 0 ) ) ), () => {
|
|
195
188
|
|
|
196
189
|
const freezeCandidate = frame.greaterThanEqual( uint( adaptiveMinSamples ) )
|
|
197
190
|
.and( relErr.lessThan( pixelFreezeThreshold ) );
|
|
198
191
|
const newStreak = select( freezeCandidate, streakBufferRW.element( pixelId ).add( uint( 1 ) ), uint( 0 ) ).toVar();
|
|
199
192
|
streakBufferRW.element( pixelId ).assign( newStreak );
|
|
200
|
-
// Just reached K → frozen next frame; count it now so the frozen population is current.
|
|
201
|
-
If( newStreak.greaterThanEqual( uint( pixelFreezeStability ) ), () => {
|
|
202
|
-
|
|
203
|
-
atomicAdd( counters.element( uint( COUNTER.FROZEN_COUNT ) ), uint( 1 ) );
|
|
204
|
-
|
|
205
|
-
} );
|
|
206
193
|
|
|
207
194
|
} );
|
|
208
195
|
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
getStratifiedSample,
|
|
23
23
|
} from './Random.js';
|
|
24
24
|
|
|
25
|
-
import { generateRayFromCamera } from './
|
|
25
|
+
import { generateRayFromCamera } from './CameraRay.js';
|
|
26
26
|
import { Ray } from './Struct.js';
|
|
27
27
|
import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
|
|
28
28
|
import {
|
|
@@ -39,6 +39,7 @@ export function buildGenerateKernel( params ) {
|
|
|
39
39
|
rayBufferRW, rngBufferRW, gBufferRW,
|
|
40
40
|
resolution, frame,
|
|
41
41
|
cameraWorldMatrix, cameraProjectionMatrixInverse,
|
|
42
|
+
cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
|
|
42
43
|
enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
|
|
43
44
|
renderWidth,
|
|
44
45
|
chunkRowBase, chunkRows, // row band offset (global first row) + row count for this chunk
|
|
@@ -56,9 +57,6 @@ export function buildGenerateKernel( params ) {
|
|
|
56
57
|
|
|
57
58
|
const pixelCoord = vec2( float( gx ).add( 0.5 ), float( gy ).add( 0.5 ) );
|
|
58
59
|
|
|
59
|
-
const screenPosition = pixelCoord.div( resolution ).mul( 2.0 ).sub( 1.0 ).toVar();
|
|
60
|
-
screenPosition.y.assign( screenPosition.y.negate() );
|
|
61
|
-
|
|
62
60
|
const baseSeed = getDecorrelatedSeed( { pixelCoord, rayIndex: int( 0 ), frame } ).toVar();
|
|
63
61
|
const seed = pcgHash( { state: baseSeed } ).toVar();
|
|
64
62
|
|
|
@@ -68,13 +66,18 @@ export function buildGenerateKernel( params ) {
|
|
|
68
66
|
// position from the first scatter direction (they were reading the identical cell).
|
|
69
67
|
const stratifiedJitter = getStratifiedSample( pixelCoord, int( 1 ), int( 1 ), seed, resolution, frame ).toVar();
|
|
70
68
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
69
|
+
// Y is subtracted because uv.y grows downward while the NDC this used to build grew upward —
|
|
70
|
+
// keeps the sub-pixel sample sequence bit-identical to the pre-panorama ray gen.
|
|
71
|
+
const jitterOffset = stratifiedJitter.sub( 0.5 );
|
|
72
|
+
const jitteredUV = vec2(
|
|
73
|
+
pixelCoord.x.add( jitterOffset.x ),
|
|
74
|
+
pixelCoord.y.sub( jitterOffset.y )
|
|
75
|
+
).div( resolution );
|
|
74
76
|
|
|
75
77
|
const ray = Ray.wrap( generateRayFromCamera(
|
|
76
|
-
|
|
78
|
+
jitteredUV, seed,
|
|
77
79
|
cameraWorldMatrix, cameraProjectionMatrixInverse,
|
|
80
|
+
cameraProjection, panoLonRange, panoLatRange, panoLevelHorizon,
|
|
78
81
|
enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
|
|
79
82
|
) );
|
|
80
83
|
|
|
@@ -111,20 +111,65 @@ export function buildGlobalPrefixKernel( { sortGlobalHistogram, bins = SORT_GLOB
|
|
|
111
111
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
// Pass 4 — scatter:
|
|
115
|
-
//
|
|
114
|
+
// Pass 4 — scatter: each ray lands in a unique slot inside its material's contiguous region.
|
|
115
|
+
//
|
|
116
|
+
// Global atomics are reduced from O(rays) to O(workgroups × bins touched) by reserving one block
|
|
117
|
+
// per bin per workgroup, the same aggregation Pass 2 already applies to the histogram. `local`
|
|
118
|
+
// changes meaning across the barriers: first a per-workgroup count, then the workgroup's global
|
|
119
|
+
// base cursor for that bin, which the lanes then bump to claim their own slot.
|
|
120
|
+
//
|
|
121
|
+
// Slot order WITHIN a bin changes versus the per-ray version, but it was never deterministic —
|
|
122
|
+
// the old path raced on the same global atomicAdd. The output is still an exact permutation of the
|
|
123
|
+
// entering set, and Shade reads each ray independently.
|
|
116
124
|
export function buildGlobalScatterKernel( { hitBufferRO, activeIndicesReadRO, sortedIndicesRW, sortGlobalHistogram, counters, bins = SORT_GLOBAL_MAX_BINS } ) {
|
|
117
125
|
|
|
118
126
|
return Fn( () => {
|
|
119
127
|
|
|
120
128
|
const tid = instanceIndex;
|
|
129
|
+
const lid = localId.x;
|
|
130
|
+
const local = workgroupAtomicArray( 'uint', bins );
|
|
131
|
+
|
|
132
|
+
If( lid.lessThan( uint( bins ) ), () => {
|
|
133
|
+
|
|
134
|
+
atomicStore( local.element( lid ), uint( 0 ) );
|
|
135
|
+
|
|
136
|
+
} );
|
|
137
|
+
workgroupBarrier();
|
|
138
|
+
|
|
139
|
+
// Read once and reuse: recomputing the bin after the barriers would re-fetch the hit buffer.
|
|
121
140
|
const entering = atomicLoad( counters.element( uint( COUNTER.ENTERING_COUNT ) ) );
|
|
122
|
-
|
|
141
|
+
const inRange = tid.lessThan( entering );
|
|
142
|
+
const rayID = uint( 0 ).toVar();
|
|
143
|
+
const bin = uint( 0 ).toVar();
|
|
123
144
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
145
|
+
If( inRange, () => {
|
|
146
|
+
|
|
147
|
+
rayID.assign( activeIndicesReadRO.element( tid ) );
|
|
148
|
+
bin.assign( uint( readHitMaterialIndex( hitBufferRO, rayID ) ).clamp( uint( 0 ), uint( bins - 1 ) ) );
|
|
149
|
+
atomicAdd( local.element( bin ), uint( 1 ) );
|
|
150
|
+
|
|
151
|
+
} );
|
|
152
|
+
workgroupBarrier();
|
|
153
|
+
|
|
154
|
+
// One global atomic per bin per workgroup reserves that block; the returned base replaces the
|
|
155
|
+
// count in place. Bins this workgroup never touched are skipped — no lane will read them.
|
|
156
|
+
If( lid.lessThan( uint( bins ) ), () => {
|
|
157
|
+
|
|
158
|
+
const slot = local.element( lid );
|
|
159
|
+
const c = atomicLoad( slot );
|
|
160
|
+
If( c.greaterThan( uint( 0 ) ), () => {
|
|
161
|
+
|
|
162
|
+
atomicStore( slot, atomicAdd( sortGlobalHistogram.element( lid ), c ) );
|
|
163
|
+
|
|
164
|
+
} );
|
|
165
|
+
|
|
166
|
+
} );
|
|
167
|
+
workgroupBarrier();
|
|
168
|
+
|
|
169
|
+
If( inRange, () => {
|
|
170
|
+
|
|
171
|
+
// Workgroup-local atomic: gives this lane its rank inside the block reserved above.
|
|
172
|
+
sortedIndicesRW.element( atomicAdd( local.element( bin ), uint( 1 ) ) ).assign( rayID );
|
|
128
173
|
|
|
129
174
|
} );
|
|
130
175
|
|
|
@@ -225,6 +225,19 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
225
225
|
* @param {string} strategy - 'none' | 'asvgf' | 'edgeaware'
|
|
226
226
|
* @param {string} [asvgfPreset] - ASVGF quality preset when strategy is 'asvgf'
|
|
227
227
|
*/
|
|
228
|
+
/**
|
|
229
|
+
* Active real-time denoiser, derived from stage state so it can't drift from
|
|
230
|
+
* setDenoiserStrategy / setASVGFEnabled.
|
|
231
|
+
* @returns {'asvgf'|'edgeaware'|'none'}
|
|
232
|
+
*/
|
|
233
|
+
get denoiserStrategy() {
|
|
234
|
+
|
|
235
|
+
if ( this._stages.asvgf?.enabled ) return 'asvgf';
|
|
236
|
+
if ( this._stages.edgeFilter?.enabled ) return 'edgeaware';
|
|
237
|
+
return 'none';
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
|
|
228
241
|
setDenoiserStrategy( strategy, asvgfPreset ) {
|
|
229
242
|
|
|
230
243
|
const s = this._stages;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
RGBAFormat, RedFormat, FloatType, Vector2, Vector3, Color, Matrix4, DataTexture,
|
|
11
|
+
RGBAFormat, RedFormat, FloatType, LinearFilter, Vector2, Vector3, Color, Matrix4, DataTexture,
|
|
12
12
|
} from 'three';
|
|
13
13
|
import { EquirectHDRInfo } from '../Processor/EquirectHDRInfo.js';
|
|
14
14
|
import { ProceduralSky } from '../Processor/ProceduralSky.js';
|
|
@@ -36,9 +36,12 @@ export class EnvironmentManager {
|
|
|
36
36
|
this.proceduralSkyRenderer = null;
|
|
37
37
|
this.simpleSkyRenderer = null;
|
|
38
38
|
|
|
39
|
-
// Environment texture — 1×1 black placeholder for shader compilation
|
|
39
|
+
// Environment texture — 1×1 black placeholder for shader compilation. Filters must be
|
|
40
|
+
// Linear: DataTexture defaults to Nearest, which is unfilterable, and WGSLNodeBuilder then
|
|
41
|
+
// omits the `_sampler` binding that sampleEnvironment() takes as an explicit wgslFn arg.
|
|
40
42
|
this._envPlaceholder = new DataTexture(
|
|
41
|
-
new Float32Array( [ 0, 0, 0, 1 ] ), 1, 1, RGBAFormat, FloatType
|
|
43
|
+
new Float32Array( [ 0, 0, 0, 1 ] ), 1, 1, RGBAFormat, FloatType,
|
|
44
|
+
undefined, undefined, undefined, LinearFilter, LinearFilter
|
|
42
45
|
);
|
|
43
46
|
this._envPlaceholder.needsUpdate = true;
|
|
44
47
|
this.environmentTexture = this._envPlaceholder;
|