rayzee 11.6.0 → 11.7.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.
Files changed (38) hide show
  1. package/README.md +13 -14
  2. package/dist/{ArchiveImporter-iofnC8zB.js → ArchiveImporter-DYcRXeKE.js} +2 -2
  3. package/dist/{BidirectionalIntegrator-CqAilt2I.js → BidirectionalIntegrator-BvjRpbTP.js} +2 -2
  4. package/dist/{NeuralRetouch-DFI4pOjO.js → NeuralRetouch-ClaMiKYg.js} +1 -1
  5. package/dist/{NeuralSuperRes-ChCiiilS.js → NeuralSuperRes-WNEXun9s.js} +1 -1
  6. package/dist/{PathTracer-BX4sJLx7.js → PathTracer-B9wZPPVL.js} +6 -8
  7. package/dist/{PhysicalSky-Biv39eTA.js → PhysicalSky-DcjItqKi.js} +1 -1
  8. package/dist/{RayzeeRenderer-D8XRKU3A.js → RayzeeRenderer-p-NeE6E2.js} +2 -3
  9. package/dist/addons/archives.es.js +1 -1
  10. package/dist/addons/bidirectional.es.js +1 -1
  11. package/dist/addons/physical-sky.es.js +1 -1
  12. package/dist/rayzee-core.es.js +3 -3
  13. package/dist/rayzee.es.js +865 -1591
  14. package/dist/rayzee.umd.js +87 -134
  15. package/dist/{sceneCachePolicy-W5PLbzuR.js → sceneCachePolicy-B9w3BnaP.js} +1 -1
  16. package/package.json +1 -1
  17. package/src/Passes/OIDNDenoiser.js +9 -11
  18. package/src/PathTracerApp.js +6 -17
  19. package/src/Pipeline/BindingAudit.js +2 -3
  20. package/src/Pipeline/PipelineContext.js +0 -1
  21. package/src/Pipeline/RenderPipeline.js +1 -2
  22. package/src/Pipeline/RenderStage.js +1 -1
  23. package/src/Processor/PackedRayBuffer.js +2 -2
  24. package/src/Processor/StorageTexturePool.js +1 -1
  25. package/src/RayzeeRenderer.js +1 -1
  26. package/src/Stages/DenoiserSettings.js +5 -81
  27. package/src/Stages/MotionVector.js +21 -215
  28. package/src/Stages/NormalDepth.js +5 -5
  29. package/src/Stages/PathTracer.js +1 -1
  30. package/src/Stages/PathTracerStage.js +4 -17
  31. package/src/TSL/FinalWriteKernel.js +2 -2
  32. package/src/TSL/ShadeKernel.js +3 -3
  33. package/src/index.js +1 -1
  34. package/src/managers/DenoisingManager.js +78 -187
  35. package/src/Stages/ASVGF.js +0 -932
  36. package/src/Stages/BilateralFilter.js +0 -486
  37. package/src/Stages/EdgeFilter.js +0 -545
  38. package/src/Stages/Variance.js +0 -443
@@ -1,443 +0,0 @@
1
- import { Fn, wgslFn, float, int, uint, ivec2, uvec2, uniform, If, max, select,
2
- textureLoad, textureStore, workgroupArray, workgroupBarrier, localId, workgroupId } from 'three/tsl';
3
- import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
4
- import { FloatType, RGBAFormat, LinearFilter, Box2, Vector2 } from 'three';
5
- import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
6
- import { luminance } from '../TSL/Common.js';
7
- import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
8
-
9
- // NaN/±Inf guard: a poisoned luminance would otherwise corrupt the moment EMA forever
10
- // (mean/meanSq → variance → BilateralFilter sigmaL). NaN (x!=x) → 0, ±Inf → [0,1e7].
11
- const sanitizeLum = ( x ) => select( x.equal( x ), x, float( 0.0 ) ).clamp( 0.0, 1e7 );
12
-
13
- // ── wgslFn helpers ──────────────────────────────────────────
14
-
15
- /**
16
- * Temporal moment accumulation via exponential moving average.
17
- *
18
- * Returns vec4f(newMean, newMeanSq, temporalVariance * boost, spatialVariance * boost).
19
- */
20
- const temporalAccumulate = /*@__PURE__*/ wgslFn( `
21
- fn temporalAccumulate(
22
- lum: f32,
23
- prevMean: f32,
24
- prevMeanSq: f32,
25
- alpha: f32,
26
- spatialVariance: f32,
27
- varianceBoost: f32
28
- ) -> vec4f {
29
-
30
- let newMean = prevMean + ( lum - prevMean ) * alpha;
31
- let newMeanSq = prevMeanSq + ( lum * lum - prevMeanSq ) * alpha;
32
- let temporalVariance = max( newMeanSq - newMean * newMean, 0.0 );
33
-
34
- return vec4f(
35
- newMean,
36
- newMeanSq,
37
- temporalVariance * varianceBoost,
38
- spatialVariance * varianceBoost
39
- );
40
-
41
- }
42
- ` );
43
-
44
- /**
45
- * WebGPU Variance Estimation Stage (Compute Shader)
46
- *
47
- * Computes temporal and spatial variance from the path tracer output.
48
- * Used by BilateralFilter for variance-guided filtering.
49
- *
50
- * Uses compute shader with workgroup shared memory for the 3×3
51
- * spatial variance computation. Each 8×8 workgroup loads a 10×10
52
- * luminance tile into shared memory.
53
- *
54
- * Ping-pong between two StorageTextures for temporal moment
55
- * accumulation — two compute nodes, one for each write direction.
56
- *
57
- * Algorithm:
58
- * 1. Cooperative tile loading → shared memory (luminance from color)
59
- * 2. Barrier
60
- * 3. Spatial variance from 3×3 shared memory neighbourhood
61
- * 4. Temporal accumulation via textureLoad on previous moments
62
- * 5. Write (mean, meanSq, temporalVar, spatialVar) to StorageTexture
63
- *
64
- * Output format (RGBA HalfFloat):
65
- * R — mean luminance
66
- * G — second moment (mean of squared luminance)
67
- * B — temporal variance
68
- * A — spatial variance
69
- *
70
- * Execution: ALWAYS
71
- *
72
- * Textures published: variance:output
73
- * Textures read: configurable (default pathtracer:color)
74
- */
75
- export class Variance extends RenderStage {
76
-
77
- constructor( renderer, options = {} ) {
78
-
79
- super( 'VarianceEstimation', {
80
- ...options,
81
- executionMode: StageExecutionMode.ALWAYS
82
- } );
83
-
84
- this.renderer = renderer;
85
- this.inputTextureName = options.inputTextureName || 'pathtracer:color';
86
-
87
- // Parameters
88
- this.varianceBoost = uniform( options.varianceBoost ?? 1.0 );
89
- this.temporalAlpha = uniform( options.temporalAlpha ?? 0.1 );
90
- this.resW = uniform( options.width || 1 );
91
- this.resH = uniform( options.height || 1 );
92
-
93
- // Input texture node
94
- this._colorTexNode = new TextureNode();
95
-
96
- // Ping-pong StorageTextures for temporal moments
97
- const w = options.width || 1;
98
- const h = options.height || 1;
99
-
100
- // LinearFilter so downstream fragment shaders can sample these without hitting
101
- // Three.js WGSL codegen bug (textureLoad without level for StorageTextures)
102
- // FloatType (f32) required — HalfFloat's ~3.3 decimal digits cause catastrophic
103
- // cancellation in (meanSq - mean²) for converged pixels, producing a variance
104
- // floor of ~0.0001 that the (frame+1)² scaling amplifies to enormous values.
105
- // StorageTextures over-allocated at max — defensive against three.js #33061
106
- // (TSL compute pipeline keeps stale GPUTextureView after StorageTexture.setSize).
107
- this._storageTexA = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
108
- this._storageTexA.type = FloatType;
109
- this._storageTexA.format = RGBAFormat;
110
- this._storageTexA.minFilter = LinearFilter;
111
- this._storageTexA.magFilter = LinearFilter;
112
-
113
- this._storageTexB = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
114
- this._storageTexB.type = FloatType;
115
- this._storageTexB.format = RGBAFormat;
116
- this._storageTexB.minFilter = LinearFilter;
117
- this._storageTexB.magFilter = LinearFilter;
118
-
119
- this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
120
-
121
- // Right-sized RenderTarget published to downstream (UV-sampled). The
122
- // over-allocated StorageTexture itself must not be published — UV
123
- // sampling a 2048 texture would read the wrong region.
124
- this._outputTarget = new RenderTarget( w, h, {
125
- type: FloatType,
126
- format: RGBAFormat,
127
- minFilter: LinearFilter,
128
- magFilter: LinearFilter,
129
- depthBuffer: false,
130
- stencilBuffer: false
131
- } );
132
-
133
- this.currentMoments = 0; // 0 = write A, read B; 1 = write B, read A
134
- this._compiled = false;
135
- this._needsWarmReset = false;
136
-
137
- // Dispatch dimensions
138
- this._dispatchX = Math.ceil( w / 8 );
139
- this._dispatchY = Math.ceil( h / 8 );
140
-
141
- this._buildCompute();
142
-
143
- }
144
-
145
- setupEventListeners() {
146
-
147
- this.on( 'pathtracer:viewpointChanged', () => this.reset() );
148
-
149
- }
150
-
151
- /**
152
- * Build two compute nodes — one for each ping-pong direction.
153
- *
154
- * _computeNodeA: writes to StorageTexA, reads previous moments from StorageTexB
155
- * _computeNodeB: writes to StorageTexB, reads previous moments from StorageTexA
156
- *
157
- * Read-side textures wrapped in TextureNode so compile-time type is regular Texture
158
- * (avoids Three.js WGSL codegen bug: textureLoad without level for StorageTexture).
159
- * Values are set to actual StorageTextures in render().
160
- */
161
- _buildCompute() {
162
-
163
- // TextureNode wrappers for reading ping-pong textures
164
- // Default to EmptyTexture at compile time → codegen includes level parameter
165
- this._readTexNodeA = new TextureNode();
166
- this._readTexNodeB = new TextureNode();
167
-
168
- // A writes to StorageTexA, reads previous moments from B
169
- this._computeNodeA = this._buildComputeForDirection( this._storageTexA, this._readTexNodeB );
170
- // B writes to StorageTexB, reads previous moments from A
171
- this._computeNodeB = this._buildComputeForDirection( this._storageTexB, this._readTexNodeA );
172
-
173
- }
174
-
175
- _buildComputeForDirection( writeStorageTex, readTexNode ) {
176
-
177
- const colorTex = this._colorTexNode;
178
- const alpha = this.temporalAlpha;
179
- const varianceBoost = this.varianceBoost;
180
- const resW = this.resW;
181
- const resH = this.resH;
182
-
183
- const TILE_W = 10;
184
- const TILE_TOTAL = TILE_W * TILE_W; // 100
185
- const WG_SIZE = 8;
186
- const WG_THREADS = WG_SIZE * WG_SIZE; // 64
187
- const EXTRA_LOAD = TILE_TOTAL - WG_THREADS; // 36
188
-
189
- const sharedLum = workgroupArray( 'float', TILE_TOTAL );
190
-
191
- const computeFn = Fn( () => {
192
-
193
- const lx = localId.x;
194
- const ly = localId.y;
195
- const linearIdx = ly.mul( WG_SIZE ).add( lx );
196
-
197
- // Tile origin in global image coords (1px border before the core)
198
- const tileOriginX = int( workgroupId.x ).mul( WG_SIZE ).sub( 1 );
199
- const tileOriginY = int( workgroupId.y ).mul( WG_SIZE ).sub( 1 );
200
-
201
- // ── Cooperative tile loading ─────────────────────
202
-
203
- // Load #1: all 64 threads load positions 0-63
204
- const sx1 = linearIdx.mod( TILE_W );
205
- const sy1 = linearIdx.div( TILE_W );
206
- const gx1 = tileOriginX.add( int( sx1 ) ).clamp( int( 0 ), int( resW ).sub( 1 ) );
207
- const gy1 = tileOriginY.add( int( sy1 ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
208
-
209
- const sColor1 = textureLoad( colorTex, ivec2( gx1, gy1 ) ).xyz;
210
- sharedLum.element( linearIdx ).assign( sanitizeLum( luminance( sColor1 ) ) );
211
-
212
- // Load #2: threads 0-35 load positions 64-99
213
- If( linearIdx.lessThan( uint( EXTRA_LOAD ) ), () => {
214
-
215
- const idx2 = linearIdx.add( uint( WG_THREADS ) );
216
- const sx2 = idx2.mod( TILE_W );
217
- const sy2 = idx2.div( TILE_W );
218
- const gx2 = tileOriginX.add( int( sx2 ) ).clamp( int( 0 ), int( resW ).sub( 1 ) );
219
- const gy2 = tileOriginY.add( int( sy2 ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
220
-
221
- const sColor2 = textureLoad( colorTex, ivec2( gx2, gy2 ) ).xyz;
222
- sharedLum.element( idx2 ).assign( sanitizeLum( luminance( sColor2 ) ) );
223
-
224
- } );
225
-
226
- workgroupBarrier();
227
-
228
- // ── Per-thread computation ───────────────────────
229
-
230
- const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( lx ) );
231
- const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( ly ) );
232
-
233
- If( gx.lessThan( int( resW ) ).and( gy.lessThan( int( resH ) ) ), () => {
234
-
235
- // ── Spatial variance from 3×3 shared memory ──
236
-
237
- const spatMean = float( 0.0 ).toVar();
238
- const spatMeanSq = float( 0.0 ).toVar();
239
-
240
- for ( let dy = - 1; dy <= 1; dy ++ ) {
241
-
242
- for ( let dx = - 1; dx <= 1; dx ++ ) {
243
-
244
- const val = sharedLum.element(
245
- ly.add( 1 + dy ).mul( TILE_W ).add( lx.add( 1 + dx ) )
246
- );
247
- spatMean.addAssign( val );
248
- spatMeanSq.addAssign( val.mul( val ) );
249
-
250
- }
251
-
252
- }
253
-
254
- spatMean.divAssign( 9.0 );
255
- spatMeanSq.divAssign( 9.0 );
256
- const spatialVariance = max( spatMeanSq.sub( spatMean.mul( spatMean ) ), float( 0.0 ) );
257
-
258
- // ── Temporal accumulation ─────────────────────
259
- // Current luminance from center of shared memory tile
260
- const lum = sharedLum.element( ly.add( 1 ).mul( TILE_W ).add( lx.add( 1 ) ) );
261
-
262
- // Previous moments via textureLoad (per-pixel, not tiled)
263
- const prevMoments = textureLoad( readTexNode, ivec2( gx, gy ) );
264
-
265
- // ── Write output ──────────────────────────────
266
-
267
- textureStore(
268
- writeStorageTex,
269
- uvec2( uint( gx ), uint( gy ) ),
270
- temporalAccumulate(
271
- lum, prevMoments.x, prevMoments.y,
272
- alpha, spatialVariance, varianceBoost
273
- )
274
- ).toWriteOnly();
275
-
276
- } );
277
-
278
- } );
279
-
280
- return computeFn().compute(
281
- [ this._dispatchX, this._dispatchY, 1 ],
282
- [ WG_SIZE, WG_SIZE, 1 ]
283
- );
284
-
285
- }
286
-
287
- render( context ) {
288
-
289
- if ( ! this.enabled ) return;
290
-
291
- const colorTex = context.getTexture( this.inputTextureName );
292
- if ( ! colorTex ) return;
293
-
294
- // Auto-size
295
- const img = colorTex.image;
296
- if ( img && img.width > 0 && img.height > 0 ) {
297
-
298
- if ( img.width !== this._outputTarget.width ||
299
- img.height !== this._outputTarget.height ) {
300
-
301
- this.setSize( img.width, img.height );
302
-
303
- }
304
-
305
- }
306
-
307
- this._colorTexNode.value = colorTex;
308
-
309
- // Force-compile both compute nodes on first frame while _readTexNodeA/B
310
- // still hold EmptyTexture. This ensures WGSLNodeBuilder.generateTextureLoad()
311
- // sees isStorageTexture=false and emits the required level parameter.
312
- // The throwaway dispatches initialise both StorageTextures to near-zero,
313
- // which is correct for temporal moment accumulation on frame 0.
314
- if ( ! this._compiled ) {
315
-
316
- this.renderer.compute( this._computeNodeA );
317
- this.renderer.compute( this._computeNodeB );
318
- this._compiled = true;
319
-
320
- }
321
-
322
- // Set read-side TextureNode values to actual StorageTextures.
323
- // Shaders are already compiled — this only updates the uniform binding.
324
- this._readTexNodeA.value = this._storageTexA;
325
- this._readTexNodeB.value = this._storageTexB;
326
-
327
- // Warm reset: initialize BOTH ping-pong textures to current scene data
328
- // using alpha=1.0 so mean=currentLum, meanSq=currentLum², variance=0.
329
- // This avoids ~30 frames of EMA convergence from stale pre-reset data.
330
- if ( this._needsWarmReset ) {
331
-
332
- const savedAlpha = this.temporalAlpha.value;
333
- this.temporalAlpha.value = 1.0;
334
- this.renderer.compute( this._computeNodeA );
335
- this.renderer.compute( this._computeNodeB );
336
- this.temporalAlpha.value = savedAlpha;
337
- this._needsWarmReset = false;
338
-
339
- }
340
-
341
- // Dispatch correct ping-pong direction
342
- const computeNode = this.currentMoments === 0
343
- ? this._computeNodeA // write A, read B
344
- : this._computeNodeB; // write B, read A
345
-
346
- this.renderer.compute( computeNode );
347
-
348
- // The write target this frame
349
- const writeTarget = this.currentMoments === 0
350
- ? this._storageTexA
351
- : this._storageTexB;
352
-
353
- // Swap for next frame
354
- this.currentMoments = 1 - this.currentMoments;
355
-
356
- // Copy the active region out of the over-allocated StorageTexture into the
357
- // right-sized RenderTarget; downstream stages UV-sample the latter.
358
- this._srcRegion.max.set( this._outputTarget.width, this._outputTarget.height );
359
- this.renderer.copyTextureToTexture( writeTarget, this._outputTarget.texture, this._srcRegion );
360
-
361
- // Publish the RenderTarget (not the over-allocated StorageTexture)
362
- context.setTexture( 'variance:output', this._outputTarget.texture );
363
-
364
- }
365
-
366
- // Free the 2048² StorageTextures when disabled; three.js re-creates them on the next dispatch
367
- // after re-enable, and reset() re-anchors the EMA. See ASVGF.releaseGPUMemory.
368
- releaseGPUMemory() {
369
-
370
- this._storageTexA?.dispose();
371
- this._storageTexB?.dispose();
372
- // Render-res RT texture (dispose .texture, not the RT — RT.dispose() doesn't free it here).
373
- this.context?.removeTexture( 'variance:output' );
374
- this._outputTarget?.texture?.dispose();
375
- this.reset();
376
-
377
- }
378
-
379
- reset() {
380
-
381
- this.currentMoments = 0;
382
- // Defer initialization to next render() when fresh scene data is available.
383
- // Using alpha=1.0 on the first post-reset frame immediately anchors the EMA
384
- // to the new scene, avoiding ~30 frames of convergence from stale data.
385
- this._needsWarmReset = true;
386
-
387
- if ( this.context ) this.context.removeTexture( 'variance:output' );
388
-
389
- }
390
-
391
- setSize( width, height ) {
392
-
393
- // StorageTextures stay at their max allocation (see constructor).
394
- this._outputTarget.setSize( width, height );
395
- this._outputTarget.texture.needsUpdate = true;
396
- this.resW.value = width;
397
- this.resH.value = height;
398
-
399
- // Update dispatch dimensions
400
- this._dispatchX = Math.ceil( width / 8 );
401
- this._dispatchY = Math.ceil( height / 8 );
402
- this._computeNodeA.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
403
- this._computeNodeB.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
404
-
405
- }
406
-
407
- // Reserved-storage change (e.g. 4K toggle): recreate the MAX-preallocated StorageTextures at the new live
408
- // reserved size + rebuild the compute nodes in place. Same stage object, so refs/wiring stay valid.
409
- reallocateReservedStorage() {
410
-
411
- this._computeNodeA?.dispose();
412
- this._computeNodeB?.dispose();
413
- this._storageTexA?.dispose();
414
- this._storageTexB?.dispose();
415
- const mk = () => {
416
-
417
- const t = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
418
- t.type = FloatType; t.format = RGBAFormat; t.minFilter = LinearFilter; t.magFilter = LinearFilter;
419
- return t;
420
-
421
- };
422
-
423
- this._storageTexA = mk();
424
- this._storageTexB = mk();
425
- this._buildCompute();
426
- this._compiled = false;
427
-
428
- }
429
-
430
- dispose() {
431
-
432
- this._computeNodeA?.dispose();
433
- this._computeNodeB?.dispose();
434
- this._storageTexA?.dispose();
435
- this._storageTexB?.dispose();
436
- this._outputTarget?.dispose();
437
- this._colorTexNode?.dispose();
438
- this._readTexNodeA?.dispose();
439
- this._readTexNodeB?.dispose();
440
-
441
- }
442
-
443
- }