rayzee 7.10.1 → 7.10.3

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.
@@ -6,7 +6,7 @@ import { ENGINE_DEFAULTS as DEFAULT_STATE, ASVGF_QUALITY_PRESETS } from '../Engi
6
6
 
7
7
  /**
8
8
  * Orchestrates all denoising, post-processing, and AI upscaling:
9
- * - Real-time denoiser strategy switching (ASVGF / SSRC / EdgeAware / None)
9
+ * - Real-time denoiser strategy switching (ASVGF / EdgeAware / None)
10
10
  * - OIDN (offline denoise on render completion)
11
11
  * - AI Upscaler
12
12
  * - Auto-exposure coordination
@@ -41,7 +41,7 @@ export class DenoisingManager extends EventDispatcher {
41
41
  this.pipeline = pipeline;
42
42
 
43
43
  // Stage references — only used internally for orchestration
44
- this._stages = stages; // { pathTracer, asvgf, variance, bilateralFilter, edgeFilter, ssrc, autoExposure, compositor }
44
+ this._stages = stages; // { pathTracer, asvgf, variance, bilateralFilter, edgeFilter, autoExposure, compositor }
45
45
 
46
46
  this._getExposure = getExposure;
47
47
  this._getSaturation = getSaturation;
@@ -222,7 +222,7 @@ export class DenoisingManager extends EventDispatcher {
222
222
 
223
223
  /**
224
224
  * Switches the real-time denoiser strategy.
225
- * @param {string} strategy - 'none' | 'asvgf' | 'ssrc' | 'edgeaware'
225
+ * @param {string} strategy - 'none' | 'asvgf' | 'edgeaware'
226
226
  * @param {string} [asvgfPreset] - ASVGF quality preset when strategy is 'asvgf'
227
227
  */
228
228
  setDenoiserStrategy( strategy, asvgfPreset ) {
@@ -234,7 +234,6 @@ export class DenoisingManager extends EventDispatcher {
234
234
  if ( s.variance ) s.variance.enabled = false;
235
235
  if ( s.bilateralFilter ) s.bilateralFilter.enabled = false;
236
236
  if ( s.edgeFilter ) s.edgeFilter.setFilteringEnabled( false );
237
- if ( s.ssrc ) s.ssrc.enabled = false;
238
237
 
239
238
  this._clearDenoiserTextures();
240
239
 
@@ -248,11 +247,10 @@ export class DenoisingManager extends EventDispatcher {
248
247
  this._applyASVGFPreset( asvgfPreset || 'medium' );
249
248
  break;
250
249
 
251
- case 'ssrc':
252
- if ( s.ssrc ) s.ssrc.enabled = true;
253
- break;
254
-
255
250
  case 'edgeaware':
251
+ // EdgeAware is a spatial-only SVGF à-trous — it consumes the Variance
252
+ // stage's per-pixel variance to drive its luminance edge-stop.
253
+ if ( s.variance ) s.variance.enabled = true;
256
254
  if ( s.edgeFilter ) s.edgeFilter.setFilteringEnabled( true );
257
255
  break;
258
256
 
@@ -325,7 +323,7 @@ export class DenoisingManager extends EventDispatcher {
325
323
  * navigation and frees their textures. Call after any consumer toggle.
326
324
  *
327
325
  * MotionVector requires NormalDepth (reads pathtracer:normalDepth) and its
328
- * consumers (ASVGF, SSRC) are a subset of NormalDepth's, so NormalDepth is
326
+ * consumers (ASVGF) are a subset of NormalDepth's, so NormalDepth is
329
327
  * always enabled whenever MotionVector is. Adaptive sampling / Variance / OIDN
330
328
  * do NOT read these signals, so they don't keep the G-buffer alive.
331
329
  */
@@ -335,9 +333,9 @@ export class DenoisingManager extends EventDispatcher {
335
333
  const nd = s.normalDepth;
336
334
  const mv = s.motionVector;
337
335
 
338
- // motionVector:* consumed by ASVGF + SSRC
339
- const motionNeeded = !! ( s.asvgf?.enabled || s.ssrc?.enabled );
340
- // pathtracer:normalDepth consumed by ASVGF, SSRC, EdgeFilter, BilateralFilter
336
+ // motionVector:* consumed by ASVGF
337
+ const motionNeeded = !! ( s.asvgf?.enabled );
338
+ // pathtracer:normalDepth consumed by ASVGF, EdgeFilter, BilateralFilter
341
339
  const normalNeeded = motionNeeded || !! ( s.edgeFilter?.enabled || s.bilateralFilter?.enabled );
342
340
 
343
341
  if ( nd ) {
@@ -366,7 +364,7 @@ export class DenoisingManager extends EventDispatcher {
366
364
  }
367
365
 
368
366
  // PathTracer's aux MRT (normalDepth + albedo) is consumed by the real-time denoisers (ASVGF/
369
- // BilateralFilter read albedo; ASVGF/SSRC/EdgeFilter read normalDepth) and by OIDN (reads the
367
+ // BilateralFilter read albedo; ASVGF/EdgeFilter read normalDepth) and by OIDN (reads the
370
368
  // MRT read-targets directly). When none are active the wavefront skips those writes entirely.
371
369
  s.pathTracer?.setAuxGBufferEnabled?.( normalNeeded || !! this.denoiser?.enabled );
372
370
 
@@ -374,7 +372,7 @@ export class DenoisingManager extends EventDispatcher {
374
372
  // disabled (lazily re-created on the next dispatch after re-enable). Every strategy/denoiser
375
373
  // toggle funnels through here after the enabled flags above are settled, so this is the one
376
374
  // choke point. dispose() is idempotent, so re-running it for an already-released stage is a no-op.
377
- for ( const stage of [ s.asvgf, s.variance, s.bilateralFilter, s.ssrc, s.edgeFilter, nd, mv ] ) {
375
+ for ( const stage of [ s.asvgf, s.variance, s.bilateralFilter, s.edgeFilter, nd, mv ] ) {
378
376
 
379
377
  if ( stage && ! stage.enabled ) stage.releaseGPUMemory?.();
380
378
 
@@ -583,13 +581,6 @@ export class DenoisingManager extends EventDispatcher {
583
581
 
584
582
  }
585
583
 
586
- /** Updates SSRC stage parameters. */
587
- setSSRCParams( params ) {
588
-
589
- this._stages.ssrc?.updateParameters( params );
590
-
591
- }
592
-
593
584
  /** Updates edge-aware filtering parameters. */
594
585
  setEdgeAwareParams( params ) {
595
586
 
@@ -667,7 +658,7 @@ export class DenoisingManager extends EventDispatcher {
667
658
 
668
659
  /**
669
660
  * Switches strategy with automatic reset (convenience wrapper).
670
- * @param {'none'|'asvgf'|'ssrc'|'edgeaware'} strategy
661
+ * @param {'none'|'asvgf'|'edgeaware'} strategy
671
662
  * @param {string} [asvgfPreset]
672
663
  */
673
664
  setStrategy( strategy, asvgfPreset ) {
@@ -713,7 +704,7 @@ export class DenoisingManager extends EventDispatcher {
713
704
  const keys = [
714
705
  'asvgf:output', 'asvgf:demodulated', 'asvgf:gradient',
715
706
  'variance:output', 'bilateralFiltering:output',
716
- 'edgeFiltering:output', 'ssrc:output',
707
+ 'edgeFiltering:output',
717
708
  ];
718
709
  keys.forEach( k => ctx.removeTexture( k ) );
719
710
 
@@ -1,328 +0,0 @@
1
- // Screen-Space Radiance Cache (SSRC) Stage
2
- //
3
- // Two compute passes per frame:
4
- // Pass 1 (Temporal): Reproject previous cache via motion vectors + EMA blend
5
- // Pass 2 (Spatial): 8-tap neighbor reuse weighted by normal/depth similarity
6
- //
7
- // Execution: PER_CYCLE
8
- //
9
- // Events listened: pipeline:reset, camera:moved
10
- //
11
- // Textures published: ssrc:output
12
- // Textures read: pathtracer:color, pathtracer:normalDepth, motionVector:screenSpace
13
-
14
- import { uniform } from 'three/tsl';
15
- import { StorageTexture, TextureNode, RenderTarget } from 'three/webgpu';
16
- import { HalfFloatType, RGBAFormat, NearestFilter, LinearFilter, Box2, Vector2 } from 'three';
17
- import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
18
- import { buildTemporalPass, buildSpatialPass } from '../TSL/SSRC.js';
19
- import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
20
-
21
- export class SSRC extends RenderStage {
22
-
23
- constructor( renderer, options = {} ) {
24
-
25
- super( 'SSRC', {
26
- ...options,
27
- executionMode: StageExecutionMode.PER_CYCLE,
28
- } );
29
-
30
- this.renderer = renderer;
31
-
32
- // ─── Uniforms ───
33
- this.resW = uniform( 1 );
34
- this.resH = uniform( 1 );
35
- this.temporalAlpha = uniform( options.temporalAlpha ?? 0.1 );
36
- this.phiNormal = uniform( options.phiNormal ?? 128.0 );
37
- this.phiDepth = uniform( options.phiDepth ?? 0.5 );
38
- this.maxHistory = uniform( options.maxHistory ?? 128.0 );
39
- this.spatialRadius = uniform( options.spatialRadius ?? 4, 'int' );
40
- this.spatialWeight = uniform( options.spatialWeight ?? 0.4 );
41
- // 0 on reset → temporal pass skips cache; incremented each render frame
42
- this._framesSinceReset = uniform( 0, 'int' );
43
-
44
- // ─── Input TextureNodes (set from pipeline context each frame) ───
45
- this._colorTexNode = new TextureNode();
46
- this._ndTexNode = new TextureNode();
47
- this._motionTexNode = new TextureNode();
48
-
49
- // ─── Read-side wrappers for ping-pong StorageTextures ───
50
- this._readCacheTexNode = new TextureNode(); // prev cache (for temporal pass)
51
- this._readPrevNDTexNode = new TextureNode(); // prev normalDepth (for edge-stopping)
52
- this._readPass1CacheTexNode = new TextureNode(); // current cache (for spatial pass)
53
-
54
- // ─── StorageTextures (5 total) ───
55
- // StorageTextures stay at max alloc — see resize crash fix (three.js #33061).
56
- const w = 1, h = 1; // RTs/uniforms resized on first render
57
-
58
- // Ping-pong temporal cache: .rgb = radiance, .w = history count
59
- this._cacheTexA = this._createStorageTex( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE, NearestFilter );
60
- this._cacheTexB = this._createStorageTex( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE, NearestFilter );
61
-
62
- // Ping-pong previous-frame normalDepth (for edge-stopping in temporal pass)
63
- this._prevNDTexA = this._createStorageTex( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE, NearestFilter );
64
- this._prevNDTexB = this._createStorageTex( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE, NearestFilter );
65
-
66
- // Final output (LinearFilter for Display fragment shader sampling)
67
- this._outputTex = this._createStorageTex( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE, LinearFilter );
68
-
69
- // Active-region copy target — published downstream (storage tex is over-allocated)
70
- this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
71
- this.outputTarget = new RenderTarget( w, h, {
72
- type: HalfFloatType,
73
- format: RGBAFormat,
74
- minFilter: LinearFilter,
75
- magFilter: LinearFilter,
76
- depthBuffer: false,
77
- stencilBuffer: false
78
- } );
79
-
80
- // ─── State ───
81
- this._currentPingPong = 0; // 0: read B, write A; 1: read A, write B
82
- this._dispatchX = 1;
83
- this._dispatchY = 1;
84
-
85
- // ─── Compute nodes ───
86
- this._buildComputeNodes();
87
-
88
- }
89
-
90
- // ──────────────────────────────────────────────────
91
- // Lifecycle
92
- // ──────────────────────────────────────────────────
93
-
94
- setupEventListeners() {
95
-
96
- this.on( 'pipeline:reset', () => this._resetCache() );
97
- this.on( 'camera:moved', () => this._resetCache() );
98
-
99
- }
100
-
101
- render( context ) {
102
-
103
- if ( ! this.enabled ) {
104
-
105
- context.removeTexture( 'ssrc:output' );
106
- return;
107
-
108
- }
109
-
110
- // Auto-resize if render resolution changed
111
- const colorTex = context.getTexture( 'pathtracer:color' );
112
- if ( colorTex?.image ) {
113
-
114
- const { width, height } = colorTex.image;
115
- if ( width !== this.outputTarget.width || height !== this.outputTarget.height ) {
116
-
117
- this.setSize( width, height );
118
-
119
- }
120
-
121
- }
122
-
123
- // Bind current-frame inputs from context
124
- const normalDepthTex = context.getTexture( 'pathtracer:normalDepth' );
125
- if ( ! normalDepthTex || ! colorTex ) return;
126
-
127
- this._colorTexNode.value = colorTex;
128
- this._ndTexNode.value = normalDepthTex;
129
-
130
- const motionTex = context.getTexture( 'motionVector:screenSpace' );
131
- if ( motionTex ) this._motionTexNode.value = motionTex;
132
-
133
- // ─── Ping-pong assignment ───
134
- // _currentPingPong 0: pass1 reads B, writes A; pass2 reads A
135
- // _currentPingPong 1: pass1 reads A, writes B; pass2 reads B
136
- const [ readCache, writeCache, readPrevND ] =
137
- this._currentPingPong === 0
138
- ? [ this._cacheTexB, this._cacheTexA, this._prevNDTexB ]
139
- : [ this._cacheTexA, this._cacheTexB, this._prevNDTexA ];
140
-
141
- // ─── Pass 1: Temporal ───
142
- this._readCacheTexNode.value = readCache;
143
- this._readPrevNDTexNode.value = readPrevND;
144
-
145
- // patch the write-side storages by recreating nodes is NOT needed — the pass was built
146
- // with the actual StorageTexture references (not TextureNode wrappers), so we swap them
147
- // via the ping-pong writeCacheTex / writePrevNDTex references in the closure.
148
- // Since TSL captures StorageTexture directly for writes, we must rebuild or use a flag.
149
- // Instead we use the swappable-write pattern: build two pass1 nodes (one per write target).
150
- this.renderer.compute( this._currentPingPong === 0 ? this._pass1NodeA : this._pass1NodeB );
151
-
152
- // ─── Pass 2: Spatial ───
153
- // Spatial pass reads the just-written cache from pass 1
154
- this._readPass1CacheTexNode.value = writeCache;
155
-
156
- this.renderer.compute( this._pass2Node );
157
-
158
- // Advance frames-since-reset counter (capped to avoid overflow)
159
- this._framesSinceReset.value = Math.min( this._framesSinceReset.value + 1, 9999 );
160
-
161
- // Copy active region out of the over-allocated StorageTexture into the
162
- // right-sized RenderTarget; downstream stages UV-sample the latter.
163
- this._srcRegion.max.set( this.outputTarget.width, this.outputTarget.height );
164
- this.renderer.copyTextureToTexture( this._outputTex, this.outputTarget.texture, this._srcRegion );
165
-
166
- // Publish final output
167
- context.setTexture( 'ssrc:output', this.outputTarget.texture );
168
-
169
- // Advance ping-pong
170
- this._currentPingPong = 1 - this._currentPingPong;
171
-
172
- }
173
-
174
- // Free the 2048² StorageTextures when disabled; three.js re-creates them on the next dispatch
175
- // after re-enable, and reset() clears the temporal cache. See ASVGF.releaseGPUMemory.
176
- releaseGPUMemory() {
177
-
178
- this._cacheTexA?.dispose();
179
- this._cacheTexB?.dispose();
180
- this._prevNDTexA?.dispose();
181
- this._prevNDTexB?.dispose();
182
- this._outputTex?.dispose();
183
- // Render-res RT texture (dispose .texture, not the RT — RT.dispose() doesn't free it here).
184
- this.context?.removeTexture( 'ssrc:output' );
185
- this.outputTarget?.texture?.dispose();
186
- this.reset();
187
-
188
- }
189
-
190
- reset() {
191
-
192
- this._resetCache();
193
-
194
- }
195
-
196
- setSize( width, height ) {
197
-
198
- if ( width < 1 || height < 1 ) return;
199
-
200
- // StorageTextures stay at their max allocation (see constructor).
201
- this.outputTarget.setSize( width, height );
202
- this.outputTarget.texture.needsUpdate = true;
203
-
204
- this.resW.value = width;
205
- this.resH.value = height;
206
-
207
- this._dispatchX = Math.ceil( width / 8 );
208
- this._dispatchY = Math.ceil( height / 8 );
209
-
210
- const dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
211
- if ( this._pass1NodeA ) this._pass1NodeA.dispatchSize = dispatchSize;
212
- if ( this._pass1NodeB ) this._pass1NodeB.dispatchSize = dispatchSize;
213
- if ( this._pass2Node ) this._pass2Node.dispatchSize = dispatchSize;
214
-
215
- this._resetCache();
216
-
217
- }
218
-
219
- dispose() {
220
-
221
- this._pass1NodeA?.dispose();
222
- this._pass1NodeB?.dispose();
223
- this._pass2Node?.dispose();
224
- this._cacheTexA.dispose();
225
- this._cacheTexB.dispose();
226
- this._prevNDTexA.dispose();
227
- this._prevNDTexB.dispose();
228
- this._outputTex.dispose();
229
- this.outputTarget?.dispose();
230
- this._colorTexNode?.dispose();
231
- this._ndTexNode?.dispose();
232
- this._motionTexNode?.dispose();
233
- this._readCacheTexNode?.dispose();
234
- this._readPrevNDTexNode?.dispose();
235
- this._readPass1CacheTexNode?.dispose();
236
-
237
- }
238
-
239
- updateParameters( params ) {
240
-
241
- if ( params.temporalAlpha !== undefined ) this.temporalAlpha.value = params.temporalAlpha;
242
- if ( params.phiNormal !== undefined ) this.phiNormal.value = params.phiNormal;
243
- if ( params.phiDepth !== undefined ) this.phiDepth.value = params.phiDepth;
244
- if ( params.maxHistory !== undefined ) this.maxHistory.value = params.maxHistory;
245
- if ( params.spatialRadius !== undefined ) this.spatialRadius.value = params.spatialRadius;
246
- if ( params.spatialWeight !== undefined ) this.spatialWeight.value = params.spatialWeight;
247
-
248
- }
249
-
250
- // ──────────────────────────────────────────────────
251
- // Private
252
- // ──────────────────────────────────────────────────
253
-
254
- _createStorageTex( w, h, filter ) {
255
-
256
- const tex = new StorageTexture( w, h );
257
- tex.type = HalfFloatType;
258
- tex.format = RGBAFormat;
259
- tex.minFilter = filter;
260
- tex.magFilter = filter;
261
- return tex;
262
-
263
- }
264
-
265
- _resetCache() {
266
-
267
- this._currentPingPong = 0;
268
- this._framesSinceReset.value = 0;
269
-
270
- }
271
-
272
- _buildComputeNodes() {
273
-
274
- const commonArgs = {
275
- colorTexNode: this._colorTexNode,
276
- ndTexNode: this._ndTexNode,
277
- motionTexNode: this._motionTexNode,
278
- readCacheTexNode: this._readCacheTexNode,
279
- readPrevNDTexNode: this._readPrevNDTexNode,
280
- resW: this.resW,
281
- resH: this.resH,
282
- temporalAlpha: this.temporalAlpha,
283
- phiNormal: this.phiNormal,
284
- phiDepth: this.phiDepth,
285
- maxHistory: this.maxHistory,
286
- };
287
-
288
- // Build two temporal nodes — one writing to cacheTexA, one to cacheTexB.
289
- // This is required because StorageTexture write targets are fixed at compile time.
290
- const pass1FnA = buildTemporalPass( {
291
- ...commonArgs,
292
- writeCacheTex: this._cacheTexA,
293
- writePrevNDTex: this._prevNDTexA,
294
- framesSinceReset: this._framesSinceReset,
295
- } );
296
-
297
- const pass1FnB = buildTemporalPass( {
298
- ...commonArgs,
299
- writeCacheTex: this._cacheTexB,
300
- writePrevNDTex: this._prevNDTexB,
301
- framesSinceReset: this._framesSinceReset,
302
- } );
303
-
304
- const dispatchCount = [ this._dispatchX, this._dispatchY, 1 ];
305
- const wgSize = [ 8, 8, 1 ];
306
-
307
- this._pass1NodeA = pass1FnA().compute( dispatchCount, wgSize );
308
- this._pass1NodeB = pass1FnB().compute( dispatchCount, wgSize );
309
-
310
- // Spatial pass: reads from _readPass1CacheTexNode (assigned per-frame to the just-written cache)
311
- const pass2Fn = buildSpatialPass( {
312
- colorTexNode: this._colorTexNode,
313
- ndTexNode: this._ndTexNode,
314
- readCacheTexNode: this._readPass1CacheTexNode,
315
- outputTex: this._outputTex,
316
- resW: this.resW,
317
- resH: this.resH,
318
- spatialRadius: this.spatialRadius,
319
- spatialWeight: this.spatialWeight,
320
- phiNormal: this.phiNormal,
321
- phiDepth: this.phiDepth,
322
- } );
323
-
324
- this._pass2Node = pass2Fn().compute( dispatchCount, wgSize );
325
-
326
- }
327
-
328
- }