rayzee 9.1.2 → 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 +4915 -4314
- 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/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 +7 -6
- 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 +142 -25
- package/src/Stages/PathTracerStage.js +6 -96
- package/src/TSL/DebugKernel.js +1 -1
- package/src/TSL/GenerateKernel.js +2 -5
- package/src/TSL/Random.js +3 -120
- package/src/TSL/ShadeKernel.js +3 -3
- package/src/index.js +2 -1
- package/src/managers/CameraManager.js +2 -2
- package/src/managers/DenoisingManager.js +76 -7
- 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
|
@@ -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
|
|
|
@@ -1544,13 +1461,8 @@ export class PathTracerStage extends RenderStage {
|
|
|
1544
1461
|
|
|
1545
1462
|
}
|
|
1546
1463
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
// Legacy API — sets the scalar STBN atlas texture
|
|
1550
|
-
this.stbnScalarTexture = tex;
|
|
1551
|
-
if ( tex ) stbnScalarTextureNode.value = tex;
|
|
1552
|
-
|
|
1553
|
-
}
|
|
1464
|
+
/** @deprecated No shader reads a blue-noise texture any more; kept so existing callers do not break. */
|
|
1465
|
+
setBlueNoiseTexture() {}
|
|
1554
1466
|
|
|
1555
1467
|
/**
|
|
1556
1468
|
* Rebuild the packed light buffer from cached lightBVH + emissive data.
|
|
@@ -1744,8 +1656,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
1744
1656
|
this.storageTextures?.dispose();
|
|
1745
1657
|
|
|
1746
1658
|
// Dispose textures
|
|
1747
|
-
this.stbnScalarTexture?.dispose();
|
|
1748
|
-
this.stbnVec2Texture?.dispose();
|
|
1749
1659
|
this.placeholderTexture?.dispose();
|
|
1750
1660
|
|
|
1751
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( () => {
|
|
@@ -59,11 +59,8 @@ export function buildGenerateKernel( params ) {
|
|
|
59
59
|
const baseSeed = getDecorrelatedSeed( { pixelCoord, rayIndex: int( 0 ), frame } ).toVar();
|
|
60
60
|
const seed = pcgHash( { state: baseSeed } ).toVar();
|
|
61
61
|
|
|
62
|
-
// Sample index 1
|
|
63
|
-
|
|
64
|
-
// samples at index 0, so index 1 is collision-free — this decorrelates the sub-pixel
|
|
65
|
-
// position from the first scatter direction (they were reading the identical cell).
|
|
66
|
-
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();
|
|
67
64
|
|
|
68
65
|
// Y is subtracted because uv.y grows downward while the NDC this used to build grew upward —
|
|
69
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
|
@@ -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 {
|
|
@@ -363,7 +363,7 @@ export class CameraManager extends EventDispatcher {
|
|
|
363
363
|
onReset?.();
|
|
364
364
|
|
|
365
365
|
this.dispatchEvent( {
|
|
366
|
-
type:
|
|
366
|
+
type: EngineEvents.CAMERA_SWITCHED, cameraIndex: index, effects: this._captureEffects(), fov: this.camera.fov,
|
|
367
367
|
cameraProjection: this._getSettings?.( 'cameraProjection' ),
|
|
368
368
|
} );
|
|
369
369
|
|
|
@@ -717,7 +717,7 @@ export class CameraManager extends EventDispatcher {
|
|
|
717
717
|
this.resetAutoFocus();
|
|
718
718
|
this._reportOrthoHeight();
|
|
719
719
|
this.dispatchEvent( {
|
|
720
|
-
type:
|
|
720
|
+
type: EngineEvents.CAMERA_SWITCHED, cameraIndex: this.currentCameraIndex, effects: this._captureEffects(), fov: this.camera.fov,
|
|
721
721
|
cameraProjection: this._getSettings?.( 'cameraProjection' ),
|
|
722
722
|
} );
|
|
723
723
|
|
|
@@ -3,6 +3,7 @@ import { OIDNDenoiser } from '../Passes/OIDNDenoiser.js';
|
|
|
3
3
|
import { OIDNTemporalHistory } from '../Passes/OIDNTemporalHistory.js';
|
|
4
4
|
import { AIUpscaler } from '../Passes/AIUpscaler.js';
|
|
5
5
|
import { EngineEvents } from '../EngineEvents.js';
|
|
6
|
+
import { ISSUE_CODES } from '../EngineIssues.js';
|
|
6
7
|
import { createLogger } from '../utils/Logger.js';
|
|
7
8
|
|
|
8
9
|
// The neural passes live in `../neural/` but report through the manager that drives them, so they
|
|
@@ -100,12 +101,14 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
100
101
|
* @param {import('../Pipeline/RenderPipeline.js').RenderPipeline} params.pipeline
|
|
101
102
|
* @param {Function} params.getExposure - () => current exposure value
|
|
102
103
|
* @param {Function} params.getSaturation - () => current saturation value
|
|
104
|
+
* @param {import('../EngineIssues.js').IssueLog} [params.issues]
|
|
103
105
|
*/
|
|
104
|
-
constructor( { renderer, mainCanvas, stages, pipeline, getExposure, getSaturation } ) {
|
|
106
|
+
constructor( { renderer, mainCanvas, stages, pipeline, getExposure, getSaturation, issues = null } ) {
|
|
105
107
|
|
|
106
108
|
super();
|
|
107
109
|
|
|
108
110
|
this.renderer = renderer;
|
|
111
|
+
this._issues = issues;
|
|
109
112
|
this.mainCanvas = mainCanvas;
|
|
110
113
|
this.upscalerCanvas = this._createUpscalerCanvas( mainCanvas );
|
|
111
114
|
this.pipeline = pipeline;
|
|
@@ -183,6 +186,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
183
186
|
this._denoiserStartHandler = null;
|
|
184
187
|
this._denoiserEndHandler = null;
|
|
185
188
|
this._denoiserTileHandler = null;
|
|
189
|
+
this._denoiserErrorHandler = null;
|
|
186
190
|
this._upscalerResChangedHandler = null;
|
|
187
191
|
this._upscalerStartHandler = null;
|
|
188
192
|
this._upscalerProgressHandler = null;
|
|
@@ -214,6 +218,23 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
214
218
|
|
|
215
219
|
}
|
|
216
220
|
|
|
221
|
+
/** GPU bytes of OIDN's inputs, output and motion history. */
|
|
222
|
+
gpuBytes() {
|
|
223
|
+
|
|
224
|
+
return ( this.denoiser?.gpuBytes?.() ?? 0 ) + ( this._history?.gpuBytes?.() ?? 0 );
|
|
225
|
+
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
_recordNoOverlayCanvas( pass ) {
|
|
229
|
+
|
|
230
|
+
this._issues?.record(
|
|
231
|
+
ISSUE_CODES.DENOISER_UNAVAILABLE,
|
|
232
|
+
`the ${pass} pass needs the render canvas to be in a document, and it is not — skipped`,
|
|
233
|
+
{ pass }
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
}
|
|
237
|
+
|
|
217
238
|
/**
|
|
218
239
|
* Updates the render resolution and propagates to denoiser/upscaler.
|
|
219
240
|
* @param {number} width
|
|
@@ -280,8 +301,6 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
280
301
|
*/
|
|
281
302
|
setupDenoiser() {
|
|
282
303
|
|
|
283
|
-
if ( ! this.upscalerCanvas ) return;
|
|
284
|
-
|
|
285
304
|
const pt = this._stages.pathTracer;
|
|
286
305
|
|
|
287
306
|
// No canvas: the denoiser hands its result to the pipeline as a picture, and the
|
|
@@ -335,9 +354,17 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
335
354
|
|
|
336
355
|
};
|
|
337
356
|
|
|
357
|
+
// Only the weight load names a URL; the init wrapper re-reports that same failure.
|
|
358
|
+
this._denoiserErrorHandler = e => e.url && this._issues?.record(
|
|
359
|
+
ISSUE_CODES.ASSET_UNREACHABLE,
|
|
360
|
+
`denoiser weights "${e.url}" could not be loaded — the render is not denoised`,
|
|
361
|
+
{ url: e.url, asset: 'oidn-weights', cause: String( e.error?.message ?? e.error ) }
|
|
362
|
+
);
|
|
363
|
+
|
|
338
364
|
this.denoiser.addEventListener( 'start', this._denoiserStartHandler );
|
|
339
365
|
this.denoiser.addEventListener( 'end', this._denoiserEndHandler );
|
|
340
366
|
this.denoiser.addEventListener( 'tileProgress', this._denoiserTileHandler );
|
|
367
|
+
this.denoiser.addEventListener( 'error', this._denoiserErrorHandler );
|
|
341
368
|
|
|
342
369
|
}
|
|
343
370
|
|
|
@@ -384,7 +411,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
384
411
|
|
|
385
412
|
// Forward lifecycle events (store refs for removal on re-setup / dispose)
|
|
386
413
|
this._upscalerResChangedHandler = ( e ) =>
|
|
387
|
-
this.dispatchEvent( { type:
|
|
414
|
+
this.dispatchEvent( { type: EngineEvents.RESOLUTION_CHANGED, width: e.width, height: e.height } );
|
|
388
415
|
this._upscalerStartHandler = () =>
|
|
389
416
|
this.dispatchEvent( { type: EngineEvents.UPSCALING_START } );
|
|
390
417
|
this._upscalerProgressHandler = ( e ) =>
|
|
@@ -1101,7 +1128,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1101
1128
|
// The nominal size, not the path tracer's: that one is smaller while the camera moves.
|
|
1102
1129
|
if ( wasShowing && this._lastRenderWidth && this._lastRenderHeight ) {
|
|
1103
1130
|
|
|
1104
|
-
this.dispatchEvent( { type:
|
|
1131
|
+
this.dispatchEvent( { type: EngineEvents.RESOLUTION_CHANGED, width: this._lastRenderWidth, height: this._lastRenderHeight } );
|
|
1105
1132
|
|
|
1106
1133
|
}
|
|
1107
1134
|
|
|
@@ -1166,7 +1193,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1166
1193
|
|
|
1167
1194
|
if ( ! this.upscalerCanvas ) {
|
|
1168
1195
|
|
|
1169
|
-
|
|
1196
|
+
this._recordNoOverlayCanvas( 'neural' );
|
|
1170
1197
|
return;
|
|
1171
1198
|
|
|
1172
1199
|
}
|
|
@@ -1282,7 +1309,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1282
1309
|
if ( this.mainCanvas ) this.mainCanvas.style.opacity = '0';
|
|
1283
1310
|
|
|
1284
1311
|
// The host shows the output dimensions; without this they keep reading the render size.
|
|
1285
|
-
this.dispatchEvent( { type:
|
|
1312
|
+
this.dispatchEvent( { type: EngineEvents.RESOLUTION_CHANGED, width: image.width, height: image.height } );
|
|
1286
1313
|
|
|
1287
1314
|
} catch ( error ) {
|
|
1288
1315
|
|
|
@@ -1297,6 +1324,44 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1297
1324
|
|
|
1298
1325
|
}
|
|
1299
1326
|
|
|
1327
|
+
/**
|
|
1328
|
+
* One denoise of the accumulation at the final tier, resolved once its picture is published.
|
|
1329
|
+
* Unlike onRenderComplete(), leaves the render loop and the upscaler alone.
|
|
1330
|
+
* @returns {Promise<boolean>} whether a denoised picture was published
|
|
1331
|
+
*/
|
|
1332
|
+
async denoiseOnce() {
|
|
1333
|
+
|
|
1334
|
+
const dn = this.denoiser;
|
|
1335
|
+
if ( ! dn?.enabled ) return false;
|
|
1336
|
+
|
|
1337
|
+
this._cleanupCompletionListener();
|
|
1338
|
+
this._resetCadence();
|
|
1339
|
+
this._useFinalQuality();
|
|
1340
|
+
|
|
1341
|
+
// start() refuses during a run and only defers during a weight load.
|
|
1342
|
+
while ( dn.state.isLoading || dn.state.isDenoising ) {
|
|
1343
|
+
|
|
1344
|
+
await ( dn.state.isLoading ? dn.whenLoaded() : new Promise( ( resolve ) => {
|
|
1345
|
+
|
|
1346
|
+
const onEnd = () => {
|
|
1347
|
+
|
|
1348
|
+
dn.removeEventListener( 'end', onEnd );
|
|
1349
|
+
resolve();
|
|
1350
|
+
|
|
1351
|
+
};
|
|
1352
|
+
|
|
1353
|
+
dn.addEventListener( 'end', onEnd );
|
|
1354
|
+
|
|
1355
|
+
} ) );
|
|
1356
|
+
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
const ok = await dn.start();
|
|
1360
|
+
if ( ok ) this._onDisplayRefresh?.();
|
|
1361
|
+
return ok && dn.hasOutput;
|
|
1362
|
+
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1300
1365
|
onRenderComplete( { isStillComplete } ) {
|
|
1301
1366
|
|
|
1302
1367
|
// Remove any stale completion-chain listener from a previous render cycle
|
|
@@ -1459,6 +1524,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1459
1524
|
if ( this._denoiserStartHandler ) this.denoiser.removeEventListener( 'start', this._denoiserStartHandler );
|
|
1460
1525
|
if ( this._denoiserEndHandler ) this.denoiser.removeEventListener( 'end', this._denoiserEndHandler );
|
|
1461
1526
|
if ( this._denoiserTileHandler ) this.denoiser.removeEventListener( 'tileProgress', this._denoiserTileHandler );
|
|
1527
|
+
if ( this._denoiserErrorHandler ) this.denoiser.removeEventListener( 'error', this._denoiserErrorHandler );
|
|
1462
1528
|
this.denoiser.dispose();
|
|
1463
1529
|
this.denoiser = null;
|
|
1464
1530
|
|
|
@@ -1482,6 +1548,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1482
1548
|
this._denoiserStartHandler = null;
|
|
1483
1549
|
this._denoiserEndHandler = null;
|
|
1484
1550
|
this._denoiserTileHandler = null;
|
|
1551
|
+
this._denoiserErrorHandler = null;
|
|
1485
1552
|
this._upscalerResChangedHandler = null;
|
|
1486
1553
|
this._upscalerStartHandler = null;
|
|
1487
1554
|
this._upscalerProgressHandler = null;
|
|
@@ -1490,6 +1557,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1490
1557
|
this._onReset = null;
|
|
1491
1558
|
this._onPostProcessRefresh = null;
|
|
1492
1559
|
this._onDisplayRefresh = null;
|
|
1560
|
+
this._issues = null;
|
|
1493
1561
|
|
|
1494
1562
|
if ( this.upscalerCanvas?.parentNode ) {
|
|
1495
1563
|
|
|
@@ -1669,6 +1737,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1669
1737
|
setUpscalerEnabled( enabled ) {
|
|
1670
1738
|
|
|
1671
1739
|
if ( this.upscaler ) this.upscaler.enabled = enabled;
|
|
1740
|
+
else if ( enabled ) this._recordNoOverlayCanvas( 'upscaler' );
|
|
1672
1741
|
// The enlarged picture describes a setting that is no longer on.
|
|
1673
1742
|
if ( ! enabled ) this._restoreRenderDisplay();
|
|
1674
1743
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DataArrayTexture, LinearFilter, RGBAFormat, UnsignedByteType } from 'three';
|
|
2
2
|
import { cachedObjectURL } from '../Storage/DownloadCache.js';
|
|
3
|
+
import { ISSUE_CODES } from '../EngineIssues.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Manages projection masks ("gobos" / "cookies") for spot lights.
|
|
@@ -33,6 +34,7 @@ export class GoboManager {
|
|
|
33
34
|
|
|
34
35
|
this.pathTracer = pathTracer;
|
|
35
36
|
this._onReset = options.onReset || null;
|
|
37
|
+
this._issues = options.issues ?? null;
|
|
36
38
|
|
|
37
39
|
/** @type {DataArrayTexture | null} */
|
|
38
40
|
this.texture = null;
|
|
@@ -140,7 +142,11 @@ export class GoboManager {
|
|
|
140
142
|
const entry = this.entries.find( e => e.name === name );
|
|
141
143
|
if ( ! entry ) {
|
|
142
144
|
|
|
143
|
-
|
|
145
|
+
this._issues?.record(
|
|
146
|
+
ISSUE_CODES.ASSET_UNREACHABLE,
|
|
147
|
+
`no gobo named "${name}" is loaded — the light projects without one`,
|
|
148
|
+
{ name, asset: 'gobo' }
|
|
149
|
+
);
|
|
144
150
|
return false;
|
|
145
151
|
|
|
146
152
|
}
|
|
@@ -237,6 +243,7 @@ export class GoboManager {
|
|
|
237
243
|
this.entries = [];
|
|
238
244
|
this.pathTracer = null;
|
|
239
245
|
this._onReset = null;
|
|
246
|
+
this._issues = null;
|
|
240
247
|
|
|
241
248
|
}
|
|
242
249
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DataArrayTexture, LinearFilter, RGBAFormat, UnsignedByteType } from 'three';
|
|
2
2
|
import { parseIES, resampleIESToGrid, deriveIESBeamAngle, deriveIESPenumbra } from '../Processor/IESParser.js';
|
|
3
3
|
import { sharedDownloads } from '../Storage/DownloadCache.js';
|
|
4
|
+
import { ISSUE_CODES } from '../EngineIssues.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Manages IES photometric profiles for spot lights.
|
|
@@ -31,6 +32,7 @@ export class IESManager {
|
|
|
31
32
|
|
|
32
33
|
this.pathTracer = pathTracer;
|
|
33
34
|
this._onReset = options.onReset || null;
|
|
35
|
+
this._issues = options.issues ?? null;
|
|
34
36
|
|
|
35
37
|
/** @type {DataArrayTexture | null} */
|
|
36
38
|
this.texture = null;
|
|
@@ -72,7 +74,11 @@ export class IESManager {
|
|
|
72
74
|
|
|
73
75
|
} catch ( err ) {
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
this._issues?.warn(
|
|
78
|
+
ISSUE_CODES.ASSET_UNREACHABLE,
|
|
79
|
+
`IES profile "${it.name}" could not be loaded: ${err.message}`,
|
|
80
|
+
{ url: it.url, asset: 'ies', cause: err.message }
|
|
81
|
+
);
|
|
76
82
|
return null;
|
|
77
83
|
|
|
78
84
|
}
|
|
@@ -188,7 +194,11 @@ export class IESManager {
|
|
|
188
194
|
const entry = this.entries.find( e => e.name === name );
|
|
189
195
|
if ( ! entry ) {
|
|
190
196
|
|
|
191
|
-
|
|
197
|
+
this._issues?.record(
|
|
198
|
+
ISSUE_CODES.ASSET_UNREACHABLE,
|
|
199
|
+
`no IES profile named "${name}" is loaded — the light keeps its plain distribution`,
|
|
200
|
+
{ name, asset: 'ies' }
|
|
201
|
+
);
|
|
192
202
|
return empty;
|
|
193
203
|
|
|
194
204
|
}
|
|
@@ -249,6 +259,7 @@ export class IESManager {
|
|
|
249
259
|
this.entries = [];
|
|
250
260
|
this.pathTracer = null;
|
|
251
261
|
this._onReset = null;
|
|
262
|
+
this._issues = null;
|
|
252
263
|
|
|
253
264
|
}
|
|
254
265
|
|
|
@@ -809,7 +809,7 @@ export class InteractionManager extends EventDispatcher {
|
|
|
809
809
|
this.addEventListener( 'focusChanged', ( event ) => {
|
|
810
810
|
|
|
811
811
|
app.settings.set( 'focusDistance', event.worldDistance );
|
|
812
|
-
app.dispatchEvent( { type:
|
|
812
|
+
app.dispatchEvent( { type: EngineEvents.FOCUS_CHANGED, distance: event.distance } );
|
|
813
813
|
|
|
814
814
|
} );
|
|
815
815
|
|