rayzee 9.1.1 → 9.2.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 +174 -15
- package/dist/{NeuralRetouch-BRquqfmZ.js → NeuralRetouch-DgnfldWO.js} +1 -1
- package/dist/{NeuralSuperRes-CbNvQjCw.js → NeuralSuperRes-Baa2w8VU.js} +72 -40
- package/dist/{ToneMapGPU-DpUZDMbQ.js → ToneMapGPU-CNgexVto.js} +75 -71
- package/dist/rayzee.es.js +4704 -4103
- package/dist/rayzee.umd.js +96 -96
- package/package.json +3 -2
- package/src/AssetConfig.js +10 -5
- package/src/EngineEvents.js +41 -1
- package/src/EngineIssues.js +3 -0
- package/src/Headless.js +23 -7
- package/src/HeadlessCanvas.js +104 -0
- package/src/Passes/OIDNDenoiser.js +29 -4
- package/src/Passes/OIDNTemporalHistory.js +7 -0
- package/src/PathTracerApp.js +432 -119
- package/src/Platform.js +76 -0
- package/src/Processor/AssetLoader.js +66 -4
- package/src/Processor/BVHBuilder.js +2 -1
- package/src/Processor/EquirectHDRInfo.js +2 -1
- package/src/Processor/HostMemory.js +14 -0
- package/src/Processor/IESParser.js +1 -2
- package/src/Processor/PBRT/PBRTSceneBuilder.js +26 -0
- package/src/Processor/ParallelBVHBuilder.js +7 -6
- package/src/Processor/PlatformImageLoader.js +118 -0
- package/src/Processor/ResampleRGBA8.js +102 -0
- package/src/Processor/SceneProcessor.js +8 -8
- package/src/Processor/TextureCreator.js +162 -13
- package/src/Processor/Workers/PackWorker.js +28 -0
- package/src/Stages/Compositor.js +25 -6
- package/src/Stages/PathTracer.js +143 -26
- package/src/Stages/PathTracerStage.js +7 -98
- package/src/TSL/DebugKernel.js +1 -1
- package/src/TSL/EmissiveSampling.js +1 -1
- package/src/TSL/GenerateKernel.js +3 -7
- package/src/TSL/Random.js +3 -120
- package/src/TSL/ShadeKernel.js +6 -6
- package/src/index.js +2 -1
- package/src/managers/CameraManager.js +2 -2
- package/src/managers/DenoisingManager.js +77 -8
- package/src/managers/GoboManager.js +8 -1
- package/src/managers/IESManager.js +13 -2
- package/src/managers/InteractionManager.js +1 -1
- package/src/managers/LightManager.js +31 -0
- package/src/managers/OverlayManager.js +11 -1
- package/src/managers/VideoRenderManager.js +8 -0
- package/src/node/NodeWorker.js +144 -0
- package/src/node/index.js +9 -0
- package/src/node/nodePlatform.js +86 -0
package/src/Stages/PathTracer.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from '../TSL/SortGlobalKernels.js';
|
|
26
26
|
import { ENGINE_DEFAULTS, MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
27
27
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
28
|
+
import { deviceMemoryGB } from '../Processor/HostMemory.js';
|
|
28
29
|
import {
|
|
29
30
|
Fn, uint, int, atomicStore, atomicLoad, atomicAdd, instanceIndex, If, Return,
|
|
30
31
|
} from 'three/tsl';
|
|
@@ -113,6 +114,11 @@ export class PathTracer extends PathTracerStage {
|
|
|
113
114
|
this._geometryPixelCount = 0;
|
|
114
115
|
this._convergedReadbackPending = false;
|
|
115
116
|
|
|
117
|
+
// Lockstep: readbacks issued on a fixed cadence and applied a fixed number of frames later,
|
|
118
|
+
// so when one lands cannot change the image. { due, read, apply } while one is in flight.
|
|
119
|
+
this._lockstep = false;
|
|
120
|
+
this._lockstepRead = null;
|
|
121
|
+
|
|
116
122
|
// Tier-2: last settled active-pixel count (maxRays − frozen), sizes next frame's bounce-0 grid.
|
|
117
123
|
// 0 until a settled readback lands (and on reset/camera-move/resize) → grid stays full-size until then.
|
|
118
124
|
this._lastActivePixelCount = 0;
|
|
@@ -131,6 +137,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
131
137
|
this._wfIsFirstChunk = uniform( 1, 'uint' ); // 1 → zero frame-scoped counters (CONVERGED/FROZEN)
|
|
132
138
|
this._pathBudget = 0; // B, paths-in-flight; computed once from device limits in _buildWavefrontKernels
|
|
133
139
|
this._pathBudgetOverride = 0; // test hook: force B (0 = derive from device)
|
|
140
|
+
this.hostMemoryGB = options.hostMemoryGB;
|
|
134
141
|
this._chunkRows = 0; // rows per chunk = floor(B / renderWidth), clamped ≥1
|
|
135
142
|
this._numChunks = 1; // ceil(renderHeight / chunkRows)
|
|
136
143
|
|
|
@@ -244,6 +251,15 @@ export class PathTracer extends PathTracerStage {
|
|
|
244
251
|
|
|
245
252
|
}
|
|
246
253
|
|
|
254
|
+
const lockstepRead = this._lockstepRead;
|
|
255
|
+
if ( lockstepRead && this.frameCount >= lockstepRead.due ) {
|
|
256
|
+
|
|
257
|
+
if ( ! lockstepRead.apply ) return; // see readbackWait()
|
|
258
|
+
this._lockstepRead = null;
|
|
259
|
+
lockstepRead.apply();
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
|
|
247
263
|
if ( this.isComplete || this.frameCount >= this.completionThreshold || this._isConvergedComplete() ) {
|
|
248
264
|
|
|
249
265
|
// Denoiser switched on after the render finished — spend one more frame to fill the aux MRT.
|
|
@@ -299,7 +315,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
299
315
|
|
|
300
316
|
}
|
|
301
317
|
|
|
302
|
-
this._updateAccumulationUniforms( frameValue
|
|
318
|
+
this._updateAccumulationUniforms( frameValue );
|
|
303
319
|
const auxMixes = this._updateAuxAccumulationUniforms( frameValue );
|
|
304
320
|
this.frame.value = frameValue;
|
|
305
321
|
this.seedFrame.value = this._seedTick ++;
|
|
@@ -657,6 +673,54 @@ export class PathTracer extends PathTracerStage {
|
|
|
657
673
|
this._auxSamples = 0;
|
|
658
674
|
this._auxSeedPending = false;
|
|
659
675
|
|
|
676
|
+
if ( this._lockstep ) {
|
|
677
|
+
|
|
678
|
+
// Nothing measured by the previous render may steer this one.
|
|
679
|
+
this._lockstepRead = null;
|
|
680
|
+
this._readbackGeneration ++;
|
|
681
|
+
this._lastBounceCounts = null;
|
|
682
|
+
this._lastBounceEnergy = null;
|
|
683
|
+
this._lastBounceCountsBudget = - 1;
|
|
684
|
+
this._lastBounceCountsLoopBound = - 1;
|
|
685
|
+
this._curveSizingValid = false;
|
|
686
|
+
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Lockstep readbacks: the survivor curve and the convergence counters are read every N frames
|
|
693
|
+
* and applied exactly N frames later, waiting when they have not landed, and every reset starts
|
|
694
|
+
* from nothing measured and from seed 0. The same input then renders the same image however fast
|
|
695
|
+
* frames are submitted. Off by default: waiting costs a stall whenever the GPU falls N frames behind.
|
|
696
|
+
* @param {boolean} enabled
|
|
697
|
+
*/
|
|
698
|
+
setLockstepReadbacks( enabled ) {
|
|
699
|
+
|
|
700
|
+
enabled = !! enabled;
|
|
701
|
+
if ( enabled === this._lockstep ) return;
|
|
702
|
+
this._lockstep = enabled;
|
|
703
|
+
this._lockstepRead = null;
|
|
704
|
+
this._readbackGeneration ++;
|
|
705
|
+
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
get lockstepReadbacks() {
|
|
709
|
+
|
|
710
|
+
return this._lockstep;
|
|
711
|
+
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* While a lockstep readback is due and has not landed, render() traces nothing: await this
|
|
716
|
+
* before the next frame. Null otherwise.
|
|
717
|
+
* @returns {?Promise<void>}
|
|
718
|
+
*/
|
|
719
|
+
readbackWait() {
|
|
720
|
+
|
|
721
|
+
const pending = this._lockstepRead;
|
|
722
|
+
return pending && ! pending.apply && this.frameCount >= pending.due ? pending.read : null;
|
|
723
|
+
|
|
660
724
|
}
|
|
661
725
|
|
|
662
726
|
/**
|
|
@@ -805,6 +869,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
805
869
|
_willReadCountersThisFrame() {
|
|
806
870
|
|
|
807
871
|
if ( this.cameraChanged || this.cameraOptimizer?.isInInteractionMode() ) return false;
|
|
872
|
+
if ( this._lockstep ) return ! this._lockstepRead && this.frameCount % this._readbackEveryNFrames === 0;
|
|
808
873
|
if ( this._readbackPending || this._convergedReadbackPending ) return false;
|
|
809
874
|
return this._readbackFrameCounter + 1 >= this._readbackEveryNFrames;
|
|
810
875
|
|
|
@@ -819,6 +884,13 @@ export class PathTracer extends PathTracerStage {
|
|
|
819
884
|
// Async readback of the per-bounce snapshot every N frames; never awaited, so the early-exit uses past-frame data.
|
|
820
885
|
_maybeReadbackCounters() {
|
|
821
886
|
|
|
887
|
+
if ( this._lockstep ) {
|
|
888
|
+
|
|
889
|
+
this._issueLockstepReadback();
|
|
890
|
+
return;
|
|
891
|
+
|
|
892
|
+
}
|
|
893
|
+
|
|
822
894
|
// Never sample the survivor curve mid-motion, nor while the CameraOptimizer is holding maxBounces
|
|
823
895
|
// down to its interaction value (1): mid-motion counts belong to a pose we're leaving, and a curve
|
|
824
896
|
// measured at the interaction budget stores _lastBounceCountsBudget=1 — when the real (high) budget
|
|
@@ -864,16 +936,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
864
936
|
// Drop counts measured at a now-stale generation (a resize or camera move happened mid-flight).
|
|
865
937
|
// A surviving readback was initiated while settled (init is skipped mid-motion) and no motion
|
|
866
938
|
// happened before it resolved, so its counts match the current view — safe to size from.
|
|
867
|
-
if ( gen === this._readbackGeneration )
|
|
868
|
-
|
|
869
|
-
const all = new Uint32Array( buf.slice( 0 ) );
|
|
870
|
-
this._lastBounceCounts = all.subarray( 0, n );
|
|
871
|
-
this._lastBounceEnergy = all.subarray( n, 2 * n );
|
|
872
|
-
this._lastBounceCountsBudget = budget;
|
|
873
|
-
this._lastBounceCountsLoopBound = measuredLoopBound;
|
|
874
|
-
this._curveSizingValid = true;
|
|
875
|
-
|
|
876
|
-
}
|
|
939
|
+
if ( gen === this._readbackGeneration ) this._applyBounceCounts( buf, budget, measuredLoopBound, n );
|
|
877
940
|
|
|
878
941
|
this._readbackPending = false;
|
|
879
942
|
|
|
@@ -899,19 +962,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
899
962
|
const total = this._wfRenderWidth.value * this._wfRenderHeight.value;
|
|
900
963
|
this.renderer.getArrayBufferAsync( cAttr ).then( ( buf ) => {
|
|
901
964
|
|
|
902
|
-
if ( cgen === this._readbackGeneration && total > 0 )
|
|
903
|
-
|
|
904
|
-
const c = new Uint32Array( buf );
|
|
905
|
-
this._convergedFraction = c[ COUNTER.CONVERGED_COUNT ] / total;
|
|
906
|
-
// No geometry at all (pure environment) leaves nothing to gate on — 1 lets the
|
|
907
|
-
// whole-frame fraction decide alone.
|
|
908
|
-
const geo = c[ COUNTER.GEOMETRY_COUNT ];
|
|
909
|
-
this._geometryPixelCount = geo;
|
|
910
|
-
this._convergedGeometryFraction = geo > 0 ? c[ COUNTER.CONVERGED_GEOMETRY_COUNT ] / geo : 1;
|
|
911
|
-
// Tier-2: bounce-0 active-pixel count measured this settled frame → sizes next frame's grid.
|
|
912
|
-
this._lastActivePixelCount = c[ COUNTER.ACTIVE_PIXEL_COUNT ];
|
|
913
|
-
|
|
914
|
-
}
|
|
965
|
+
if ( cgen === this._readbackGeneration && total > 0 ) this._applyCounters( buf, total );
|
|
915
966
|
|
|
916
967
|
this._convergedReadbackPending = false;
|
|
917
968
|
|
|
@@ -927,6 +978,72 @@ export class PathTracer extends PathTracerStage {
|
|
|
927
978
|
|
|
928
979
|
}
|
|
929
980
|
|
|
981
|
+
_issueLockstepReadback() {
|
|
982
|
+
|
|
983
|
+
// One in flight, and none mid-motion: frames there do not count, so frameCount stands still.
|
|
984
|
+
if ( this._lockstepRead || this.frameCount % this._readbackEveryNFrames !== 0 ) return;
|
|
985
|
+
if ( this.cameraChanged || this.cameraOptimizer?.isInInteractionMode() ) return;
|
|
986
|
+
|
|
987
|
+
const bounceAttr = this._queueManager?.getBounceCountsAttribute();
|
|
988
|
+
const counterAttr = this._queueManager?.getCountersAttribute();
|
|
989
|
+
if ( ! bounceAttr || ! counterAttr ) return;
|
|
990
|
+
|
|
991
|
+
const gen = this._readbackGeneration;
|
|
992
|
+
const budget = this.maxBounces.value;
|
|
993
|
+
const loopBound = this._bounceLoopBound();
|
|
994
|
+
const n = this._queueManager.MAX_BOUNCE_SNAPSHOTS;
|
|
995
|
+
const total = this._wfRenderWidth.value * this._wfRenderHeight.value;
|
|
996
|
+
|
|
997
|
+
const entry = { due: this.frameCount + this._readbackEveryNFrames, read: null, apply: null };
|
|
998
|
+
entry.read = Promise.all( [
|
|
999
|
+
this.renderer.getArrayBufferAsync( bounceAttr ),
|
|
1000
|
+
this.renderer.getArrayBufferAsync( counterAttr ),
|
|
1001
|
+
] ).then( ( [ bounceBuf, counterBuf ] ) => {
|
|
1002
|
+
|
|
1003
|
+
entry.apply = () => {
|
|
1004
|
+
|
|
1005
|
+
if ( gen !== this._readbackGeneration ) return;
|
|
1006
|
+
this._applyBounceCounts( bounceBuf, budget, loopBound, n );
|
|
1007
|
+
if ( total > 0 ) this._applyCounters( counterBuf, total );
|
|
1008
|
+
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
}, ( e ) => {
|
|
1012
|
+
|
|
1013
|
+
log.warn( 'lockstep readback failed:', e );
|
|
1014
|
+
entry.apply = () => {};
|
|
1015
|
+
|
|
1016
|
+
} );
|
|
1017
|
+
|
|
1018
|
+
this._lockstepRead = entry;
|
|
1019
|
+
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
_applyBounceCounts( buf, budget, loopBound, n ) {
|
|
1023
|
+
|
|
1024
|
+
const all = new Uint32Array( buf.slice( 0 ) );
|
|
1025
|
+
this._lastBounceCounts = all.subarray( 0, n );
|
|
1026
|
+
this._lastBounceEnergy = all.subarray( n, 2 * n );
|
|
1027
|
+
this._lastBounceCountsBudget = budget;
|
|
1028
|
+
this._lastBounceCountsLoopBound = loopBound;
|
|
1029
|
+
this._curveSizingValid = true;
|
|
1030
|
+
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
_applyCounters( buf, total ) {
|
|
1034
|
+
|
|
1035
|
+
const c = new Uint32Array( buf );
|
|
1036
|
+
this._convergedFraction = c[ COUNTER.CONVERGED_COUNT ] / total;
|
|
1037
|
+
// No geometry at all (pure environment) leaves nothing to gate on — 1 lets the
|
|
1038
|
+
// whole-frame fraction decide alone.
|
|
1039
|
+
const geo = c[ COUNTER.GEOMETRY_COUNT ];
|
|
1040
|
+
this._geometryPixelCount = geo;
|
|
1041
|
+
this._convergedGeometryFraction = geo > 0 ? c[ COUNTER.CONVERGED_GEOMETRY_COUNT ] / geo : 1;
|
|
1042
|
+
// Tier-2: bounce-0 active-pixel count measured this settled frame → sizes next frame's grid.
|
|
1043
|
+
this._lastActivePixelCount = c[ COUNTER.ACTIVE_PIXEL_COUNT ];
|
|
1044
|
+
|
|
1045
|
+
}
|
|
1046
|
+
|
|
930
1047
|
// Sync wavefront's texture nodes with current env/material textures; only a changed ref triggers GPU rebind.
|
|
931
1048
|
_refreshWfTextureNodes() {
|
|
932
1049
|
|
|
@@ -1031,7 +1148,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1031
1148
|
const maxBuffer = limits?.maxBufferSize || maxBinding;
|
|
1032
1149
|
|
|
1033
1150
|
const bByBinding = Math.floor( ( maxBinding * 0.9 ) / RAY_BYTES );
|
|
1034
|
-
const deviceMemBytes = (
|
|
1151
|
+
const deviceMemBytes = deviceMemoryGB( this.hostMemoryGB ).gb * 1024 * 1024 * 1024;
|
|
1035
1152
|
const poolVramBudget = Math.min( deviceMemBytes * 0.25, maxBuffer * 4 );
|
|
1036
1153
|
const bByVram = Math.floor( poolVramBudget / bytesPerPath );
|
|
1037
1154
|
|
|
@@ -1596,7 +1713,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1596
1713
|
cameraViewMatrix: this.cameraViewMatrix,
|
|
1597
1714
|
fireflyThreshold: this.fireflyThreshold,
|
|
1598
1715
|
shadowTerminatorOffset: this.shadowTerminatorOffset,
|
|
1599
|
-
// RNG axis only
|
|
1716
|
+
// RNG axis only.
|
|
1600
1717
|
frame: this.seedFrame,
|
|
1601
1718
|
accumFrame: this.frame,
|
|
1602
1719
|
resolution: this.resolution,
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { storage } from 'three/tsl';
|
|
2
2
|
import { gpuOnlyStorageAttribute, uploadStorageChunkRange, uploadStorageChunks } from '../TSL/patches.js';
|
|
3
3
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
4
|
-
import {
|
|
5
|
-
NearestFilter, Vector2, Matrix4,
|
|
6
|
-
TextureLoader, RepeatWrapping
|
|
7
|
-
} from 'three';
|
|
8
|
-
import { stbnScalarTextureNode, stbnVec2TextureNode } from '../TSL/Random.js';
|
|
4
|
+
import { Vector2, Matrix4 } from 'three';
|
|
9
5
|
|
|
10
6
|
// Pipeline system
|
|
11
7
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
@@ -26,8 +22,6 @@ import { LightSerializer } from '../Processor/LightSerializer';
|
|
|
26
22
|
// Constants
|
|
27
23
|
import { ENGINE_DEFAULTS as DEFAULT_STATE, TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
28
24
|
import { TRI_GEO_ROWS, TRI_SHADE_ROWS } from '../TSL/Common.js';
|
|
29
|
-
import { getAssetConfig } from '../AssetConfig.js';
|
|
30
|
-
import { cachedObjectURL, DOWNLOAD_POLICY } from '../Storage/DownloadCache.js';
|
|
31
25
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
32
26
|
|
|
33
27
|
// Triangles converted per staging pass: 256K is 12 + 8 MB of staging, kept for the stage's life.
|
|
@@ -144,9 +138,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
144
138
|
// Initialize rendering state
|
|
145
139
|
this._initRenderingState();
|
|
146
140
|
|
|
147
|
-
// Setup blue noise
|
|
148
|
-
this.setupBlueNoise();
|
|
149
|
-
|
|
150
141
|
// Cache frequently used objects
|
|
151
142
|
this.tempVector2 = new Vector2();
|
|
152
143
|
this.lastCameraMatrix = new Matrix4();
|
|
@@ -216,14 +207,8 @@ export class PathTracerStage extends RenderStage {
|
|
|
216
207
|
// and refreshes the bound TextureNode in-place when it changes.
|
|
217
208
|
this.iesProfiles = null;
|
|
218
209
|
|
|
219
|
-
// STBN noise textures
|
|
220
|
-
this.stbnScalarTexture = null;
|
|
221
|
-
this.stbnVec2Texture = null;
|
|
222
|
-
|
|
223
210
|
/**
|
|
224
|
-
*
|
|
225
|
-
* output must be reproducible — see PathTracerApp.setDeterministicMode().
|
|
226
|
-
* Replaced by the real promise in setupBlueNoise(), which runs after this.
|
|
211
|
+
* @deprecated Always resolved: the engine no longer loads blue-noise textures.
|
|
227
212
|
* @type {Promise<void>}
|
|
228
213
|
*/
|
|
229
214
|
this.blueNoiseReady = Promise.resolve();
|
|
@@ -369,74 +354,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
369
354
|
|
|
370
355
|
}
|
|
371
356
|
|
|
372
|
-
/**
|
|
373
|
-
* Load STBN (Spatiotemporal Blue Noise) atlas textures.
|
|
374
|
-
* Each atlas is 1024×1024: 8×8 grid of 128×128 tiles, 64 temporal slices.
|
|
375
|
-
*/
|
|
376
|
-
setupBlueNoise() {
|
|
377
|
-
|
|
378
|
-
const loader = new TextureLoader();
|
|
379
|
-
loader.setCrossOrigin( 'anonymous' );
|
|
380
|
-
|
|
381
|
-
const configure = ( tex ) => {
|
|
382
|
-
|
|
383
|
-
tex.minFilter = NearestFilter;
|
|
384
|
-
tex.magFilter = NearestFilter;
|
|
385
|
-
tex.wrapS = RepeatWrapping;
|
|
386
|
-
tex.wrapT = RepeatWrapping;
|
|
387
|
-
tex.generateMipmaps = false;
|
|
388
|
-
return tex;
|
|
389
|
-
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
const { stbnScalarAtlas, stbnVec2Atlas } = getAssetConfig();
|
|
393
|
-
|
|
394
|
-
const load = async ( url ) => {
|
|
395
|
-
|
|
396
|
-
const source = await cachedObjectURL( url, { policy: DOWNLOAD_POLICY.REVALIDATE } );
|
|
397
|
-
try {
|
|
398
|
-
|
|
399
|
-
return await loader.loadAsync( source.url );
|
|
400
|
-
|
|
401
|
-
} finally {
|
|
402
|
-
|
|
403
|
-
source.release();
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
const scalarLoad = load( stbnScalarAtlas ).then( ( tex ) => {
|
|
410
|
-
|
|
411
|
-
this.stbnScalarTexture = configure( tex );
|
|
412
|
-
stbnScalarTextureNode.value = tex;
|
|
413
|
-
log.debug( `STBN scalar atlas ${fmt.px( tex.image.width, tex.image.height )}` );
|
|
414
|
-
|
|
415
|
-
} );
|
|
416
|
-
|
|
417
|
-
const vec2Load = load( stbnVec2Atlas ).then( ( tex ) => {
|
|
418
|
-
|
|
419
|
-
this.stbnVec2Texture = configure( tex );
|
|
420
|
-
stbnVec2TextureNode.value = tex;
|
|
421
|
-
log.debug( `STBN vec2 atlas ${fmt.px( tex.image.width, tex.image.height )}` );
|
|
422
|
-
|
|
423
|
-
} );
|
|
424
|
-
|
|
425
|
-
// Until the atlases land the STBN sampler reads a constant-0.5 placeholder and bakes
|
|
426
|
-
// that degenerate "noise" permanently into the accumulation buffer. The cut-over frame
|
|
427
|
-
// is disk/network dependent, so reproducible renders must await this. Never rejects.
|
|
428
|
-
this.blueNoiseReady = Promise.allSettled( [ scalarLoad, vec2Load ] ).then( ( results ) => {
|
|
429
|
-
|
|
430
|
-
for ( const result of results ) {
|
|
431
|
-
|
|
432
|
-
if ( result.status === 'rejected' ) log.warn( 'STBN atlas failed to load', result.reason );
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
} );
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
|
|
440
357
|
/**
|
|
441
358
|
* Setup event listeners for pipeline events
|
|
442
359
|
*/
|
|
@@ -646,8 +563,8 @@ export class PathTracerStage extends RenderStage {
|
|
|
646
563
|
// ASVGF's history. A real mode change is still caught by the renderMode comparison.
|
|
647
564
|
this.lastInteractionModeState = false;
|
|
648
565
|
|
|
649
|
-
// Only deterministic mode
|
|
650
|
-
if ( this._pinSeedToFrame ) this._seedTick = 0;
|
|
566
|
+
// Only deterministic mode and lockstep readbacks rewind the seed axis; see _pinSeedToFrame.
|
|
567
|
+
if ( this._pinSeedToFrame || this._lockstep ) this._seedTick = 0;
|
|
651
568
|
|
|
652
569
|
}
|
|
653
570
|
|
|
@@ -1406,9 +1323,8 @@ export class PathTracerStage extends RenderStage {
|
|
|
1406
1323
|
/**
|
|
1407
1324
|
* Update accumulation uniforms
|
|
1408
1325
|
* @param {number} frameValue
|
|
1409
|
-
* @param {number} renderMode
|
|
1410
1326
|
*/
|
|
1411
|
-
_updateAccumulationUniforms( frameValue
|
|
1327
|
+
_updateAccumulationUniforms( frameValue ) {
|
|
1412
1328
|
|
|
1413
1329
|
const currentInteractionMode = this.cameraOptimizer?.isInInteractionMode() ?? false;
|
|
1414
1330
|
this.lastInteractionModeState = currentInteractionMode;
|
|
@@ -1545,13 +1461,8 @@ export class PathTracerStage extends RenderStage {
|
|
|
1545
1461
|
|
|
1546
1462
|
}
|
|
1547
1463
|
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
// Legacy API — sets the scalar STBN atlas texture
|
|
1551
|
-
this.stbnScalarTexture = tex;
|
|
1552
|
-
if ( tex ) stbnScalarTextureNode.value = tex;
|
|
1553
|
-
|
|
1554
|
-
}
|
|
1464
|
+
/** @deprecated No shader reads a blue-noise texture any more; kept so existing callers do not break. */
|
|
1465
|
+
setBlueNoiseTexture() {}
|
|
1555
1466
|
|
|
1556
1467
|
/**
|
|
1557
1468
|
* Rebuild the packed light buffer from cached lightBVH + emissive data.
|
|
@@ -1745,8 +1656,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
1745
1656
|
this.storageTextures?.dispose();
|
|
1746
1657
|
|
|
1747
1658
|
// Dispose textures
|
|
1748
|
-
this.stbnScalarTexture?.dispose();
|
|
1749
|
-
this.stbnVec2Texture?.dispose();
|
|
1750
1659
|
this.placeholderTexture?.dispose();
|
|
1751
1660
|
|
|
1752
1661
|
// Clear data references
|
package/src/TSL/DebugKernel.js
CHANGED
|
@@ -59,7 +59,7 @@ export function buildDebugKernel( params ) {
|
|
|
59
59
|
If( visMode.equal( int( 9 ) ), () => {
|
|
60
60
|
|
|
61
61
|
// One ray per pixel — plain per-frame jitter (totalRays = 1 → random, no stratified lattice).
|
|
62
|
-
const jitter = getStratifiedSample( pixelCoord, int( 0 ),
|
|
62
|
+
const jitter = getStratifiedSample( pixelCoord, int( 0 ), seed, resolution, frame );
|
|
63
63
|
color.assign( vec4( jitter, 1.0, 1.0 ) );
|
|
64
64
|
|
|
65
65
|
} ).Else( () => {
|
|
@@ -552,7 +552,7 @@ export const sampleEmissiveTriangle = Fn( ( [
|
|
|
552
552
|
|
|
553
553
|
export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
554
554
|
hitPoint, normal, geomNormal, viewDir, material,
|
|
555
|
-
|
|
555
|
+
_bounceIndex, rngState,
|
|
556
556
|
pixelCoord, resolution, frame, dimBase,
|
|
557
557
|
emissiveBoost,
|
|
558
558
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
14
|
Fn, float, vec2, vec3, vec4, int, uint,
|
|
15
|
-
If,
|
|
15
|
+
If, instanceIndex, atomicLoad,
|
|
16
16
|
localId, workgroupId,
|
|
17
17
|
} from 'three/tsl';
|
|
18
18
|
|
|
@@ -44,7 +44,6 @@ export function buildGenerateKernel( params ) {
|
|
|
44
44
|
renderWidth,
|
|
45
45
|
chunkRowBase, chunkRows, // row band offset (global first row) + row count for this chunk
|
|
46
46
|
transmissiveBounces, // per-ray refraction budget (megakernel parity: PathTracerCore.js:606)
|
|
47
|
-
transparentBackground, // alpha inits to 1 here (megakernel parity: PathTracerCore.js:554) — env-escape-without-opaque zeroes it in Shade
|
|
48
47
|
auxGBufferEnabled, // live uniform: 1 = init the per-pixel G-buffer (denoiser on), 0 = skip it
|
|
49
48
|
// listDriven: 1D dispatch over the active-pixel list (activeIndicesRO[tid] = LOCAL slot) instead of 2D.
|
|
50
49
|
listDriven = false, activeIndicesRO = null, counters = null,
|
|
@@ -60,11 +59,8 @@ export function buildGenerateKernel( params ) {
|
|
|
60
59
|
const baseSeed = getDecorrelatedSeed( { pixelCoord, rayIndex: int( 0 ), frame } ).toVar();
|
|
61
60
|
const seed = pcgHash( { state: baseSeed } ).toVar();
|
|
62
61
|
|
|
63
|
-
// Sample index 1
|
|
64
|
-
|
|
65
|
-
// samples at index 0, so index 1 is collision-free — this decorrelates the sub-pixel
|
|
66
|
-
// position from the first scatter direction (they were reading the identical cell).
|
|
67
|
-
const stratifiedJitter = getStratifiedSample( pixelCoord, int( 1 ), int( 1 ), seed, resolution, frame ).toVar();
|
|
62
|
+
// Sample index 1, not the bounces' 0, so the sub-pixel position is independent of the first scatter.
|
|
63
|
+
const stratifiedJitter = getStratifiedSample( pixelCoord, int( 1 ), seed, resolution, frame ).toVar();
|
|
68
64
|
|
|
69
65
|
// Y is subtracted because uv.y grows downward while the NDC this used to build grew upward —
|
|
70
66
|
// keeps the sub-pixel sample sequence bit-identical to the pre-panorama ray gen.
|
package/src/TSL/Random.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Three.js Transpiler r182
|
|
2
2
|
|
|
3
|
-
import { uniform,
|
|
4
|
-
import { DataTexture, FloatType } from 'three';
|
|
3
|
+
import { uniform, float, If, wgslFn, uint, TWO_PI, cos, sin, vec2, sqrt, select, int, vec4 } from 'three/tsl';
|
|
5
4
|
|
|
6
5
|
// -----------------------------------------------------------------------------
|
|
7
6
|
// Uniform declarations and constants
|
|
@@ -12,31 +11,6 @@ const samplingTechnique = samplingTechniqueUniform;
|
|
|
12
11
|
|
|
13
12
|
// 0: PCG, 1: Halton, 2: Owen-scrambled Sobol (default; anything higher falls back to it)
|
|
14
13
|
|
|
15
|
-
// 1x1 placeholder — real texture assigned later via .value = ...
|
|
16
|
-
const _placeholderData = new Float32Array( [ 0.5, 0.5, 0.5, 1.0 ] );
|
|
17
|
-
|
|
18
|
-
const _placeholderScalar = new DataTexture( _placeholderData, 1, 1 );
|
|
19
|
-
_placeholderScalar.type = FloatType;
|
|
20
|
-
_placeholderScalar.needsUpdate = true;
|
|
21
|
-
|
|
22
|
-
const _placeholderVec2 = new DataTexture( new Float32Array( [ 0.5, 0.5, 0.0, 1.0 ] ), 1, 1 );
|
|
23
|
-
_placeholderVec2.type = FloatType;
|
|
24
|
-
_placeholderVec2.needsUpdate = true;
|
|
25
|
-
|
|
26
|
-
// STBN (Spatiotemporal Blue Noise) atlas textures — Heitz 2019
|
|
27
|
-
// Each atlas: 1024×1024, 8×8 grid of 128×128 tiles, 64 temporal slices
|
|
28
|
-
// Scalar atlas: single-channel (R) — optimal for 1D decisions (RR, lobe selection)
|
|
29
|
-
// Vec2 atlas: two-channel (R,G) — decorrelated 2D pairs (direction sampling Xi)
|
|
30
|
-
export const stbnScalarTextureNode = texture( _placeholderScalar );
|
|
31
|
-
stbnScalarTextureNode.setUpdateMatrix( false );
|
|
32
|
-
|
|
33
|
-
export const stbnVec2TextureNode = texture( _placeholderVec2 );
|
|
34
|
-
stbnVec2TextureNode.setUpdateMatrix( false );
|
|
35
|
-
|
|
36
|
-
// R2 quasi-random sequence constants (Roberts 2018) — optimal 2D additive offsets
|
|
37
|
-
const R2_A1 = float( 0.7548776662466927 );
|
|
38
|
-
const R2_A2 = float( 0.5698402909980532 );
|
|
39
|
-
|
|
40
14
|
// -----------------------------------------------------------------------------
|
|
41
15
|
// Basic random number generation
|
|
42
16
|
// -----------------------------------------------------------------------------
|
|
@@ -160,53 +134,6 @@ export const RandomPointInCircle = ( rngState ) => {
|
|
|
160
134
|
export const SAMPLER_DIMS_PER_BOUNCE = 32;
|
|
161
135
|
export const SAMPLER_DIM_AUX_BASE = 4096;
|
|
162
136
|
|
|
163
|
-
// -----------------------------------------------------------------------------
|
|
164
|
-
// STBN atlas sampling — spatiotemporal blue noise
|
|
165
|
-
// -----------------------------------------------------------------------------
|
|
166
|
-
// Atlas layout: 8×8 grid of 128×128 tiles = 1024×1024 texture.
|
|
167
|
-
// Temporal axis: frame % 64 selects tile (true STBN temporal decorrelation).
|
|
168
|
-
// Spatial decorrelation: R2 quasi-random offset keyed on dimension + sample index.
|
|
169
|
-
|
|
170
|
-
const computeSTBNAtlasCoord = ( pixelCoords, sampleIndex, dimensionIndex, frame ) => {
|
|
171
|
-
|
|
172
|
-
// Temporal slice — true STBN temporal axis
|
|
173
|
-
const slice = uint( frame ).bitAnd( uint( 63 ) ); // frame % 64
|
|
174
|
-
|
|
175
|
-
// R2 quasi-random spatial offset for per-dimension/per-sample decorrelation
|
|
176
|
-
const n = float( dimensionIndex ).add( float( sampleIndex ).mul( 7.0 ) );
|
|
177
|
-
const offsetX = int( fract( n.mul( R2_A1 ).add( 0.5 ) ).mul( 128.0 ) );
|
|
178
|
-
const offsetY = int( fract( n.mul( R2_A2 ).add( 0.5 ) ).mul( 128.0 ) );
|
|
179
|
-
|
|
180
|
-
// Pixel within 128×128 tile (toroidal wrap via bitmask)
|
|
181
|
-
const px = int( pixelCoords.x ).add( offsetX ).bitAnd( int( 127 ) );
|
|
182
|
-
const py = int( pixelCoords.y ).add( offsetY ).bitAnd( int( 127 ) );
|
|
183
|
-
|
|
184
|
-
// Atlas tile position from slice index
|
|
185
|
-
const tileCol = int( slice ).bitAnd( int( 7 ) ); // slice % 8
|
|
186
|
-
const tileRow = int( slice ).shiftRight( int( 3 ) ); // slice / 8
|
|
187
|
-
|
|
188
|
-
return ivec2( tileCol.mul( int( 128 ) ).add( px ), tileRow.mul( int( 128 ) ).add( py ) );
|
|
189
|
-
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// Sample decorrelated 2D STBN pair in [0,1]²
|
|
193
|
-
export const sampleSTBN2D = ( pixelCoords, sampleIndex, dimensionPairIndex, frame ) => {
|
|
194
|
-
|
|
195
|
-
const coord = computeSTBNAtlasCoord( pixelCoords, sampleIndex, dimensionPairIndex, frame );
|
|
196
|
-
const raw = stbnVec2TextureNode.load( coord ).xy;
|
|
197
|
-
|
|
198
|
-
// The atlas has only 64 temporal slices, so frame N and N+64 read the same slice: the
|
|
199
|
-
// sample repeats and accumulation stops improving past 64 frames. Decorrelate across
|
|
200
|
-
// 64-frame cycles with a Cranley-Patterson rotation (toroidal shift) by an R2 offset
|
|
201
|
-
// keyed on the cycle index (frame >> 6). The offset is uniform per cycle, preserving
|
|
202
|
-
// spatial and within-window temporal blue noise; cycle 0's offset is 0, so frames
|
|
203
|
-
// 0-63 stay bit-identical. A toroidal shift of uniform samples stays uniform (unbiased).
|
|
204
|
-
const cycle = float( uint( frame ).shiftRight( uint( 6 ) ) );
|
|
205
|
-
const rotation = fract( vec2( R2_A1, R2_A2 ).mul( cycle ) );
|
|
206
|
-
return fract( raw.add( rotation ) );
|
|
207
|
-
|
|
208
|
-
};
|
|
209
|
-
|
|
210
137
|
// -----------------------------------------------------------------------------
|
|
211
138
|
// Low-discrepancy sequence generators
|
|
212
139
|
// -----------------------------------------------------------------------------
|
|
@@ -531,52 +458,8 @@ export const getRandomSample1D = ( pixelCoord, sampleIndex, dimensionIndex, rngS
|
|
|
531
458
|
|
|
532
459
|
};
|
|
533
460
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
export const getStratifiedSample = ( pixelCoord, rayIndex, totalRays, rngState, resolution, frame ) => {
|
|
538
|
-
|
|
539
|
-
// result variable avoids early-return ReturnNode escaping into outer Fn scope
|
|
540
|
-
const result = vec2( 0.0 ).toVar();
|
|
541
|
-
|
|
542
|
-
If( totalRays.lessThanEqual( int( 1 ) ), () => {
|
|
543
|
-
|
|
544
|
-
result.assign( getRandomSample( pixelCoord, rayIndex, int( 0 ), rngState, int( - 1 ), resolution, frame ) );
|
|
545
|
-
|
|
546
|
-
} ).Else( () => {
|
|
547
|
-
|
|
548
|
-
// Calculate strata dimensions
|
|
549
|
-
|
|
550
|
-
const strataX = int( sqrt( float( totalRays ) ) );
|
|
551
|
-
const strataY = totalRays.add( strataX ).sub( 1 ).div( strataX );
|
|
552
|
-
const strataIdx = mod( rayIndex, strataX.mul( strataY ) );
|
|
553
|
-
const sx = mod( strataIdx, strataX );
|
|
554
|
-
const sy = strataIdx.div( strataX );
|
|
555
|
-
|
|
556
|
-
// Base stratified position
|
|
557
|
-
|
|
558
|
-
const strataPos = vec2( float( sx ), float( sy ) ).div( vec2( float( strataX ), float( strataY ) ) );
|
|
559
|
-
|
|
560
|
-
const j1 = RandomValueFast( rngState ).toVar();
|
|
561
|
-
const j2 = RandomValueFast( rngState ).toVar();
|
|
562
|
-
const jitter = vec2( j1, j2 ).toVar();
|
|
563
|
-
|
|
564
|
-
If( totalRays.greaterThan( int( 4 ) ), () => {
|
|
565
|
-
|
|
566
|
-
const stbnInfluence = sampleSTBN2D( pixelCoord, rayIndex, int( 0 ), frame ).mul( 0.1 );
|
|
567
|
-
jitter.assign( mix( jitter, stbnInfluence, 0.2 ) );
|
|
568
|
-
|
|
569
|
-
} );
|
|
570
|
-
|
|
571
|
-
jitter.divAssign( vec2( float( strataX ), float( strataY ) ) );
|
|
572
|
-
|
|
573
|
-
result.assign( strataPos.add( jitter ) );
|
|
574
|
-
|
|
575
|
-
} );
|
|
576
|
-
|
|
577
|
-
return result;
|
|
578
|
-
|
|
579
|
-
};
|
|
461
|
+
export const getStratifiedSample = ( pixelCoord, rayIndex, rngState, resolution, frame ) =>
|
|
462
|
+
getRandomSample( pixelCoord, rayIndex, int( 0 ), rngState, int( - 1 ), resolution, frame );
|
|
580
463
|
|
|
581
464
|
// Get decorrelated seed with better mixing
|
|
582
465
|
|
package/src/TSL/ShadeKernel.js
CHANGED
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
If, Loop, normalize, max, min, exp, log, clamp, dot, length, select, smoothstep, acos,
|
|
11
11
|
instanceIndex,
|
|
12
12
|
sampler,
|
|
13
|
-
|
|
13
|
+
atomicLoad, atomicStore,
|
|
14
14
|
Return,
|
|
15
15
|
} from 'three/tsl';
|
|
16
16
|
|
|
17
|
-
import { sampleEnvironment,
|
|
17
|
+
import { sampleEnvironment, sampleEquirect, groundProjectedEnvDir } from './Environment.js';
|
|
18
18
|
import { getMaterial, powerHeuristic, balanceHeuristic, classifyMaterial, REC709_LUMINANCE_COEFFICIENTS, PI_INV, EPSILON, diffuseGroundMaterial, offsetRayOrigin, SHADOW_END } from './Common.js';
|
|
19
19
|
import { cosineWeightedSample } from './MaterialSampling.js';
|
|
20
20
|
import { sampleAllMaterialTextures, processAnisotropyMap, applyExtensionMaps, getTransformedUV, triangleUVTangent } from './TextureSampling.js';
|
|
@@ -107,7 +107,7 @@ export function buildShadeKernel( params ) {
|
|
|
107
107
|
accumFrame,
|
|
108
108
|
chunkRowBase, // chunked path pool: global pixel = chunkRowBase·W + localSlot (rayID). See spec.
|
|
109
109
|
emissiveTriangleCount, emissiveVec4Offset, emissiveTotalPower,
|
|
110
|
-
emissiveBoost,
|
|
110
|
+
emissiveBoost, enableEmissiveTriangleSampling,
|
|
111
111
|
lightBVHNodeCount, reverseMapVec4Offset,
|
|
112
112
|
maxRayCount,
|
|
113
113
|
// Aux G-buffer (normal/depth/albedo + surface ID) feeds only the denoiser/OIDN MRT. Gated by a
|
|
@@ -201,9 +201,9 @@ export function buildShadeKernel( params ) {
|
|
|
201
201
|
const pixelIndex = rayID;
|
|
202
202
|
const rngState = readRngState( hitBufferRW, rayID ).toVar();
|
|
203
203
|
|
|
204
|
-
//
|
|
205
|
-
// = chunkRowBase·W + localSlot, so
|
|
206
|
-
// (and
|
|
204
|
+
// Samples key on the GLOBAL pixel. pixelIndex is the LOCAL path slot; the global pixel
|
|
205
|
+
// = chunkRowBase·W + localSlot, so sequences stay aligned across row-band chunks
|
|
206
|
+
// (and match Generate's per-pixel RNG seed). chunkRowBase is 0 in the single-chunk case.
|
|
207
207
|
// Hoisted here rather than at the BSDF draw because the ground catcher runs NEE far earlier.
|
|
208
208
|
const _resX = int( resolution.x ).toVar();
|
|
209
209
|
const _globalPixel = int( pixelIndex ).add( chunkRowBase.mul( _resX ) );
|
package/src/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import './TSL/patches.js';
|
|
|
12
12
|
export { PathTracerApp, describeAdapter } from './PathTracerApp.js';
|
|
13
13
|
|
|
14
14
|
// Event types
|
|
15
|
-
export { EngineEvents } from './EngineEvents.js';
|
|
15
|
+
export { EngineEvents, LEGACY_EVENT_NAMES } from './EngineEvents.js';
|
|
16
16
|
|
|
17
17
|
// Headless rendering — the supported entry point for a caller with no screen
|
|
18
18
|
export { renderHeadless, openHeadless, captureHeadless } from './Headless.js';
|
|
@@ -25,6 +25,7 @@ export { SETTING_SOURCE } from './RenderSettings.js';
|
|
|
25
25
|
|
|
26
26
|
// Asset URL / cache namespace overrides (call before constructing PathTracerApp)
|
|
27
27
|
export { configureAssets, getAssetConfig } from './AssetConfig.js';
|
|
28
|
+
export { configurePlatform, getPlatform } from './Platform.js';
|
|
28
29
|
|
|
29
30
|
// On-disk storage (origin private file system): caches, and areas a host defines for its own data
|
|
30
31
|
export {
|