rayzee 7.20.3 → 7.22.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 +75 -4
- package/dist/rayzee.es.js +2821 -2854
- package/dist/rayzee.umd.js +60 -58
- package/package.json +1 -1
- package/src/EngineDefaults.js +9 -2
- package/src/PathTracerApp.js +83 -0
- package/src/Pipeline/BindingAudit.js +178 -0
- package/src/Pipeline/RenderPipeline.js +5 -2
- package/src/Processor/BVHBuilder.js +1 -4
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/KernelManager.js +17 -70
- package/src/Processor/PackedRayBuffer.js +33 -8
- package/src/Processor/QueueManager.js +4 -3
- package/src/Processor/SceneProcessor.js +10 -4
- package/src/Processor/TreeletOptimizer.js +0 -7
- package/src/RenderSettings.js +41 -6
- package/src/Stages/ASVGF.js +10 -10
- package/src/Stages/BilateralFilter.js +70 -11
- package/src/Stages/EdgeFilter.js +44 -6
- package/src/Stages/NormalDepth.js +26 -15
- package/src/Stages/PathTracer.js +36 -34
- package/src/Stages/PathTracerStage.js +15 -46
- package/src/TSL/BVHTraversal.js +1 -67
- package/src/TSL/CameraRay.js +141 -0
- package/src/TSL/Common.js +1 -47
- package/src/TSL/DebugKernel.js +4 -5
- package/src/TSL/EmissiveSampling.js +40 -44
- package/src/TSL/ExtendKernel.js +11 -3
- package/src/TSL/FinalWriteKernel.js +1 -14
- package/src/TSL/GenerateKernel.js +11 -8
- package/src/TSL/LightsCore.js +3 -0
- package/src/TSL/LightsDirect.js +4 -4
- package/src/TSL/LightsSampling.js +138 -327
- package/src/TSL/PathTracerCore.js +2 -2
- package/src/TSL/Random.js +2 -2
- package/src/TSL/ShadeKernel.js +39 -17
- package/src/TSL/SortGlobalKernels.js +52 -7
- package/src/TSL/Struct.js +0 -10
- package/src/index.js +5 -0
- package/src/managers/DenoisingManager.js +13 -0
- package/src/managers/EnvironmentManager.js +6 -8
- package/src/managers/UniformManager.js +12 -2
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 = {} ) {
|
|
@@ -272,6 +279,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
272
279
|
|
|
273
280
|
this._updateAccumulationUniforms( frameValue, renderMode );
|
|
274
281
|
this.frame.value = frameValue;
|
|
282
|
+
this.seedFrame.value = this._seedTick ++;
|
|
275
283
|
|
|
276
284
|
this._setWfDispatch();
|
|
277
285
|
|
|
@@ -349,7 +357,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
349
357
|
|
|
350
358
|
// reset counters → compact non-frozen pixel IDs → publish count → list-driven 1D generate.
|
|
351
359
|
const genSized = Math.min( maxRays, Math.ceil( activeBounce0 * 1.5 ) + 1024 );
|
|
352
|
-
km.
|
|
360
|
+
km.setDispatchForCount( 'generateList', genSized );
|
|
353
361
|
km.dispatch( 'resetFrameCounters' );
|
|
354
362
|
km.dispatch( 'buildActivePixels' );
|
|
355
363
|
km.dispatch( 'seedEnter' );
|
|
@@ -401,23 +409,12 @@ export class PathTracer extends PathTracerStage {
|
|
|
401
409
|
}
|
|
402
410
|
|
|
403
411
|
const sized = Math.min( maxRays, Math.ceil( entering * 1.5 ) + 1024 );
|
|
404
|
-
const
|
|
405
|
-
km.setDispatchCount( 'extend', wg );
|
|
406
|
-
km.setDispatchCount( 'shade', wg );
|
|
407
|
-
km.setDispatchCount( 'globalHist', wg );
|
|
408
|
-
km.setDispatchCount( 'globalScatter', wg );
|
|
409
|
-
km.setDispatchCount( 'compact', wg );
|
|
410
|
-
km.setDispatchCount( 'compactCopyback', wg );
|
|
412
|
+
for ( const k of BOUNCE_KERNELS ) km.setDispatchForCount( k, sized );
|
|
411
413
|
|
|
412
414
|
} else {
|
|
413
415
|
|
|
414
416
|
km.dispatch( 'enterFull' );
|
|
415
|
-
const
|
|
416
|
-
km.setDispatchCount( 'extend', full );
|
|
417
|
-
km.setDispatchCount( 'shade', full );
|
|
418
|
-
km.setDispatchCount( 'compact', full );
|
|
419
|
-
km.setDispatchCount( 'globalHist', full );
|
|
420
|
-
km.setDispatchCount( 'globalScatter', full );
|
|
417
|
+
for ( const k of BOUNCE_KERNELS ) km.setDispatchForCount( k, maxRays );
|
|
421
418
|
|
|
422
419
|
}
|
|
423
420
|
|
|
@@ -934,7 +931,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
934
931
|
|
|
935
932
|
} );
|
|
936
933
|
this._kernelManager.register( 'initActiveIndices',
|
|
937
|
-
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 ] )
|
|
938
935
|
);
|
|
939
936
|
|
|
940
937
|
const genParams = {
|
|
@@ -942,9 +939,15 @@ export class PathTracer extends PathTracerStage {
|
|
|
942
939
|
rngBufferRW: pb.rngBuffer.rw,
|
|
943
940
|
gBufferRW,
|
|
944
941
|
resolution: this.resolution,
|
|
945
|
-
|
|
942
|
+
// RNG axis only (baseSeed + stratified jitter) — takes the seed counter, not the
|
|
943
|
+
// accumulation index. FinalWrite keeps `frame`.
|
|
944
|
+
frame: this.seedFrame,
|
|
946
945
|
cameraWorldMatrix: this.cameraWorldMatrix,
|
|
947
946
|
cameraProjectionMatrixInverse: this.cameraProjectionMatrixInverse,
|
|
947
|
+
cameraProjection: this.cameraProjection,
|
|
948
|
+
panoLonRange: this.panoLonRange,
|
|
949
|
+
panoLatRange: this.panoLatRange,
|
|
950
|
+
panoLevelHorizon: this.panoLevelHorizon,
|
|
948
951
|
enableDOF: this.enableDOF,
|
|
949
952
|
focalLength: this.focalLength,
|
|
950
953
|
aperture: this.aperture,
|
|
@@ -974,13 +977,12 @@ export class PathTracer extends PathTracerStage {
|
|
|
974
977
|
const freezeK = this.pixelFreezeStability;
|
|
975
978
|
const resetFrameFn = Fn( () => {
|
|
976
979
|
|
|
977
|
-
// ACTIVE is per-chunk (zeroed before every chunk's scatter). CONVERGED
|
|
978
|
-
// 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.
|
|
979
982
|
atomicStore( counters.element( uint( COUNTER.ACTIVE_RAY_COUNT ) ), uint( 0 ) );
|
|
980
983
|
If( this._wfIsFirstChunk.greaterThan( uint( 0 ) ), () => {
|
|
981
984
|
|
|
982
985
|
atomicStore( counters.element( uint( COUNTER.CONVERGED_COUNT ) ), uint( 0 ) );
|
|
983
|
-
atomicStore( counters.element( uint( COUNTER.FROZEN_COUNT ) ), uint( 0 ) );
|
|
984
986
|
|
|
985
987
|
} );
|
|
986
988
|
|
|
@@ -1047,7 +1049,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1047
1049
|
|
|
1048
1050
|
} );
|
|
1049
1051
|
this._kernelManager.register( 'buildActivePixels',
|
|
1050
|
-
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 ] )
|
|
1051
1053
|
);
|
|
1052
1054
|
|
|
1053
1055
|
const seedEnterFn = Fn( () => {
|
|
@@ -1070,7 +1072,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1070
1072
|
counters,
|
|
1071
1073
|
} );
|
|
1072
1074
|
this._kernelManager.register( 'generateList',
|
|
1073
|
-
genListFn().compute( [ Math.ceil( maxRays /
|
|
1075
|
+
genListFn().compute( [ Math.ceil( maxRays / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
|
|
1074
1076
|
);
|
|
1075
1077
|
|
|
1076
1078
|
const freshBvh = this.bvhStorageNode;
|
|
@@ -1105,7 +1107,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
1105
1107
|
const extFn = buildExtendKernel( {
|
|
1106
1108
|
bvhBuffer: freshBvh,
|
|
1107
1109
|
triangleBuffer: freshTri,
|
|
1108
|
-
materialBuffer: freshMat,
|
|
1109
1110
|
rayBufferRO: pb.rayBuffer.ro,
|
|
1110
1111
|
hitBufferRW: pb.hitBuffer.rw,
|
|
1111
1112
|
activeIndicesRO: qm.getActiveReadRO(),
|
|
@@ -1180,7 +1181,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
1180
1181
|
environmentIntensity: this.environmentIntensity,
|
|
1181
1182
|
envMatrix: this.environmentMatrix,
|
|
1182
1183
|
enableEnvironmentLight: this.enableEnvironment,
|
|
1183
|
-
useEnvMapIS: this.useEnvMapIS,
|
|
1184
1184
|
groundProjectionEnabled: this.groundProjectionEnabled,
|
|
1185
1185
|
groundProjectionRadius: this.groundProjectionRadius,
|
|
1186
1186
|
groundProjectionHeight: this.groundProjectionHeight,
|
|
@@ -1210,7 +1210,9 @@ export class PathTracer extends PathTracerStage {
|
|
|
1210
1210
|
cameraProjectionMatrix: this.cameraProjectionMatrix,
|
|
1211
1211
|
cameraViewMatrix: this.cameraViewMatrix,
|
|
1212
1212
|
fireflyThreshold: this.fireflyThreshold,
|
|
1213
|
-
frame
|
|
1213
|
+
// RNG axis only (keys STBN via frame & 63).
|
|
1214
|
+
frame: this.seedFrame,
|
|
1215
|
+
accumFrame: this.frame,
|
|
1214
1216
|
resolution: this.resolution,
|
|
1215
1217
|
emissiveTriangleCount: this.emissiveTriangleCount,
|
|
1216
1218
|
emissiveVec4Offset: this.emissiveVec4Offset,
|
|
@@ -1288,7 +1290,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1288
1290
|
|
|
1289
1291
|
} );
|
|
1290
1292
|
this._kernelManager.register( 'compactCopyback',
|
|
1291
|
-
copyFn().compute( [ Math.ceil( maxRays /
|
|
1293
|
+
copyFn().compute( [ Math.ceil( maxRays / LIST_WG_SIZE ), 1, 1 ], [ LIST_WG_SIZE, 1, 1 ] )
|
|
1292
1294
|
);
|
|
1293
1295
|
|
|
1294
1296
|
const fwFn = buildFinalWriteKernel( {
|
|
@@ -1320,7 +1322,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
1320
1322
|
// Tier-2 freeze (stamp + pass-through)
|
|
1321
1323
|
usePixelFreeze: this.usePixelFreeze,
|
|
1322
1324
|
pixelFreezeThreshold: this.pixelFreezeThreshold,
|
|
1323
|
-
pixelFreezeStability: this.pixelFreezeStability,
|
|
1324
1325
|
streakBufferRW: streakRW,
|
|
1325
1326
|
frozenMaskRO, // dilated frozen mask (read-only; matches the active-list decision)
|
|
1326
1327
|
chunkRowBase: this._wfChunkRowBase,
|
|
@@ -1347,6 +1348,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
1347
1348
|
cameraProjectionMatrixInverse: this.cameraProjectionMatrixInverse,
|
|
1348
1349
|
cameraProjectionMatrix: this.cameraProjectionMatrix,
|
|
1349
1350
|
cameraViewMatrix: this.cameraViewMatrix,
|
|
1351
|
+
cameraProjection: this.cameraProjection,
|
|
1352
|
+
panoLonRange: this.panoLonRange,
|
|
1353
|
+
panoLatRange: this.panoLatRange,
|
|
1354
|
+
panoLevelHorizon: this.panoLevelHorizon,
|
|
1350
1355
|
enableDOF: this.enableDOF,
|
|
1351
1356
|
focalLength: this.focalLength,
|
|
1352
1357
|
aperture: this.aperture,
|
|
@@ -1392,10 +1397,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1392
1397
|
const w = this._wfRenderWidth.value;
|
|
1393
1398
|
const h = this._wfRenderHeight.value;
|
|
1394
1399
|
|
|
1395
|
-
this._kernelManager.
|
|
1396
|
-
Math.ceil( w / DEBUG_WG_SIZE ),
|
|
1397
|
-
Math.ceil( h / DEBUG_WG_SIZE ), 1
|
|
1398
|
-
] );
|
|
1400
|
+
this._kernelManager.setDispatchForGrid( 'debug', w, h );
|
|
1399
1401
|
|
|
1400
1402
|
}
|
|
1401
1403
|
|
|
@@ -1412,10 +1414,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
1412
1414
|
this._wfMaxRayCount.value = chunkPixels;
|
|
1413
1415
|
|
|
1414
1416
|
const km = this._kernelManager;
|
|
1415
|
-
km.
|
|
1416
|
-
km.
|
|
1417
|
-
km.
|
|
1418
|
-
km.
|
|
1417
|
+
km.setDispatchForGrid( 'generate', w, rows );
|
|
1418
|
+
km.setDispatchForGrid( 'finalWrite', w, rows );
|
|
1419
|
+
km.setDispatchForCount( 'initActiveIndices', chunkPixels );
|
|
1420
|
+
km.setDispatchForCount( 'buildActivePixels', chunkPixels );
|
|
1419
1421
|
|
|
1420
1422
|
return chunkPixels;
|
|
1421
1423
|
|
|
@@ -51,7 +51,6 @@ const BVH_VEC4_PER_NODE = 4;
|
|
|
51
51
|
* - camera:moved - When camera position/orientation changes
|
|
52
52
|
* - asvgf:reset - Request ASVGF to reset temporal data
|
|
53
53
|
* - asvgf:updateParameters - Update ASVGF parameters
|
|
54
|
-
* - asvgf:setTemporal - Enable/disable ASVGF temporal accumulation
|
|
55
54
|
*
|
|
56
55
|
* Textures published to context:
|
|
57
56
|
* - pathtracer:color - Main color output
|
|
@@ -143,6 +142,11 @@ export class PathTracerStage extends RenderStage {
|
|
|
143
142
|
this.lastCameraMatrix = new Matrix4();
|
|
144
143
|
this.lastProjectionMatrix = new Matrix4();
|
|
145
144
|
|
|
145
|
+
// Sampler seed axis — advances per rendered frame, survives accumulation resets.
|
|
146
|
+
// Pinned back to the accumulation index in deterministic mode.
|
|
147
|
+
this._seedTick = 0;
|
|
148
|
+
this._pinSeedToFrame = false;
|
|
149
|
+
|
|
146
150
|
// Denoising management state
|
|
147
151
|
this.lastRenderMode = - 1;
|
|
148
152
|
this.renderModeChangeTimeout = null;
|
|
@@ -293,18 +297,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
293
297
|
|
|
294
298
|
}
|
|
295
299
|
},
|
|
296
|
-
useEnvMapIS: {
|
|
297
|
-
get value() {
|
|
298
|
-
|
|
299
|
-
return self.useEnvMapIS.value;
|
|
300
|
-
|
|
301
|
-
},
|
|
302
|
-
set value( v ) {
|
|
303
|
-
|
|
304
|
-
self.useEnvMapIS.value = v;
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
},
|
|
308
300
|
enableAccumulation: {
|
|
309
301
|
get value() {
|
|
310
302
|
|
|
@@ -348,7 +340,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
348
340
|
enabled: DEFAULT_STATE.interactionModeEnabled,
|
|
349
341
|
qualitySettings: {
|
|
350
342
|
maxBounceCount: 1,
|
|
351
|
-
useEnvMapIS: false,
|
|
352
343
|
enableAccumulation: false,
|
|
353
344
|
enableEmissiveTriangleSampling: false,
|
|
354
345
|
},
|
|
@@ -651,10 +642,14 @@ export class PathTracerStage extends RenderStage {
|
|
|
651
642
|
this.isComplete = false;
|
|
652
643
|
this.performanceMonitor?.reset();
|
|
653
644
|
|
|
654
|
-
|
|
655
|
-
|
|
645
|
+
// lastRenderMode is deliberately NOT invalidated here — clearing it made
|
|
646
|
+
// manageASVGFForRenderMode see a phantom mode change 50 ms after every reset, wiping
|
|
647
|
+
// ASVGF's history. A real mode change is still caught by the renderMode comparison.
|
|
656
648
|
this.lastInteractionModeState = false;
|
|
657
649
|
|
|
650
|
+
// Only deterministic mode rewinds the seed axis; see _pinSeedToFrame.
|
|
651
|
+
if ( this._pinSeedToFrame ) this._seedTick = 0;
|
|
652
|
+
|
|
658
653
|
}
|
|
659
654
|
|
|
660
655
|
/**
|
|
@@ -1238,7 +1233,10 @@ export class PathTracerStage extends RenderStage {
|
|
|
1238
1233
|
if ( this.pendingRenderMode !== null && this.pendingRenderMode !== this.lastRenderMode ) {
|
|
1239
1234
|
|
|
1240
1235
|
this.lastRenderMode = this.pendingRenderMode;
|
|
1241
|
-
|
|
1236
|
+
// History from the previous mode is not comparable. temporalAlpha is NOT
|
|
1237
|
+
// touched here — it is owned by ASVGF_QUALITY_PRESETS, and overwriting it
|
|
1238
|
+
// with a hardcoded value meant `medium` and `high` never took effect.
|
|
1239
|
+
this.emit( 'asvgf:reset' );
|
|
1242
1240
|
|
|
1243
1241
|
}
|
|
1244
1242
|
|
|
@@ -1249,35 +1247,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
1249
1247
|
|
|
1250
1248
|
}
|
|
1251
1249
|
|
|
1252
|
-
this._handleFullQuadASVGF();
|
|
1253
|
-
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
_onRenderModeChanged( newMode ) {
|
|
1257
|
-
|
|
1258
|
-
if ( newMode === 1 ) {
|
|
1259
|
-
|
|
1260
|
-
this.emit( 'asvgf:updateParameters', {
|
|
1261
|
-
enableDebug: false,
|
|
1262
|
-
temporalAlpha: 0.15
|
|
1263
|
-
} );
|
|
1264
|
-
|
|
1265
|
-
} else {
|
|
1266
|
-
|
|
1267
|
-
this.emit( 'asvgf:updateParameters', {
|
|
1268
|
-
temporalAlpha: 0.1,
|
|
1269
|
-
} );
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
this.emit( 'asvgf:reset' );
|
|
1274
|
-
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
_handleFullQuadASVGF() {
|
|
1278
|
-
|
|
1279
|
-
this.emit( 'asvgf:setTemporal', { enabled: true } );
|
|
1280
|
-
|
|
1281
1250
|
}
|
|
1282
1251
|
|
|
1283
1252
|
// ===== UNIFORM & DATA SETTERS =====
|
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/Common.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { Fn, wgslFn, float, vec2, vec3, vec4, int, mat3, If, max, dot, clamp
|
|
1
|
+
import { Fn, wgslFn, float, vec2, vec3, vec4, int, mat3, If, max, dot, clamp } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
AnisoFrame,
|
|
5
5
|
DotProducts,
|
|
6
6
|
MaterialClassification,
|
|
7
|
-
MISStrategy,
|
|
8
7
|
RayTracingMaterial,
|
|
9
8
|
ShadowMaterial,
|
|
10
9
|
} from './Struct.js';
|
|
@@ -299,51 +298,6 @@ export const classifyMaterial = Fn( ( [ metalness, roughness, transmission, clea
|
|
|
299
298
|
|
|
300
299
|
} );
|
|
301
300
|
|
|
302
|
-
// Dynamic MIS strategy based on material properties
|
|
303
|
-
export const selectOptimalMISStrategy = Fn( ( [ roughness, metalness, bounceIndex, throughput ] ) => {
|
|
304
|
-
|
|
305
|
-
const throughputStrength = maxComponent( { v: throughput } ).toVar();
|
|
306
|
-
|
|
307
|
-
// Environment is now handled via deterministic NEE — not part of stochastic selection.
|
|
308
|
-
// Only lights and BRDF are stochastically selected.
|
|
309
|
-
const brdfWeight = float( 0.5 ).toVar();
|
|
310
|
-
const lightWeight = float( 0.5 ).toVar();
|
|
311
|
-
const useBRDFSampling = tslBool( true );
|
|
312
|
-
const useLightSampling = throughputStrength.greaterThan( 0.01 );
|
|
313
|
-
|
|
314
|
-
If( roughness.lessThan( 0.1 ).and( metalness.greaterThan( 0.8 ) ), () => {
|
|
315
|
-
|
|
316
|
-
// Highly specular materials — favor BRDF
|
|
317
|
-
brdfWeight.assign( 0.7 );
|
|
318
|
-
lightWeight.assign( 0.3 );
|
|
319
|
-
|
|
320
|
-
} ).ElseIf( roughness.greaterThan( 0.7 ), () => {
|
|
321
|
-
|
|
322
|
-
// Diffuse materials — favor light sampling
|
|
323
|
-
brdfWeight.assign( 0.4 );
|
|
324
|
-
lightWeight.assign( 0.6 );
|
|
325
|
-
|
|
326
|
-
} );
|
|
327
|
-
|
|
328
|
-
// Gentle adjustment for very deep bounces — shift toward BRDF
|
|
329
|
-
If( bounceIndex.greaterThan( int( 5 ) ), () => {
|
|
330
|
-
|
|
331
|
-
brdfWeight.assign( 0.6 );
|
|
332
|
-
lightWeight.assign( 0.4 );
|
|
333
|
-
|
|
334
|
-
} );
|
|
335
|
-
|
|
336
|
-
return MISStrategy( {
|
|
337
|
-
brdfWeight,
|
|
338
|
-
lightWeight,
|
|
339
|
-
envWeight: float( 0.0 ),
|
|
340
|
-
useBRDFSampling,
|
|
341
|
-
useLightSampling,
|
|
342
|
-
useEnvSampling: tslBool( false ),
|
|
343
|
-
} );
|
|
344
|
-
|
|
345
|
-
} );
|
|
346
|
-
|
|
347
301
|
// Storage buffer access — flat 1D indexing (WebGPU native)
|
|
348
302
|
// No 2D coordinate math needed: directly indexes into the buffer
|
|
349
303
|
export const getDatafromStorageBuffer = Fn( ( [ buffer, stride, sampleIndex, dataOffset ] ) => {
|
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
|
|