rayzee 11.5.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 (41) hide show
  1. package/README.md +14 -14
  2. package/dist/{ArchiveImporter-29Nl1567.js → ArchiveImporter-DYcRXeKE.js} +2 -2
  3. package/dist/{BidirectionalIntegrator-CJay8tXK.js → BidirectionalIntegrator-BvjRpbTP.js} +289 -289
  4. package/dist/{EngineEvents-Ci8dVWCP.js → EngineEvents-CLckhYbL.js} +1 -0
  5. package/dist/{NeuralRetouch-KqGVPV7u.js → NeuralRetouch-ClaMiKYg.js} +1 -1
  6. package/dist/{NeuralSuperRes-BHxhYzlD.js → NeuralSuperRes-WNEXun9s.js} +2 -2
  7. package/dist/{PathTracer-Cldt0mXi.js → PathTracer-B9wZPPVL.js} +3197 -3167
  8. package/dist/{PhysicalSky-CUWrTcmT.js → PhysicalSky-DcjItqKi.js} +1 -1
  9. package/dist/{RayzeeRenderer-Dd-Dit0v.js → RayzeeRenderer-p-NeE6E2.js} +405 -437
  10. package/dist/addons/archives.es.js +1 -1
  11. package/dist/addons/bidirectional.es.js +1 -1
  12. package/dist/addons/physical-sky.es.js +1 -1
  13. package/dist/rayzee-core.es.js +5 -5
  14. package/dist/rayzee.es.js +1003 -1686
  15. package/dist/rayzee.umd.js +104 -143
  16. package/dist/{sceneCachePolicy--ta6moJu.js → sceneCachePolicy-B9w3BnaP.js} +2 -2
  17. package/package.json +2 -2
  18. package/src/EngineEvents.js +2 -0
  19. package/src/Passes/OIDNDenoiser.js +106 -100
  20. package/src/PathTracerApp.js +8 -19
  21. package/src/Pipeline/BindingAudit.js +2 -3
  22. package/src/Pipeline/PipelineContext.js +0 -1
  23. package/src/Pipeline/RenderPipeline.js +1 -2
  24. package/src/Pipeline/RenderStage.js +1 -1
  25. package/src/Processor/PackedRayBuffer.js +2 -2
  26. package/src/Processor/StorageTexturePool.js +1 -1
  27. package/src/Processor/TextureReadback.js +18 -1
  28. package/src/RayzeeRenderer.js +5 -5
  29. package/src/Stages/DenoiserSettings.js +5 -81
  30. package/src/Stages/MotionVector.js +21 -215
  31. package/src/Stages/NormalDepth.js +5 -5
  32. package/src/Stages/PathTracer.js +3 -11
  33. package/src/Stages/PathTracerStage.js +4 -17
  34. package/src/TSL/FinalWriteKernel.js +2 -2
  35. package/src/TSL/ShadeKernel.js +3 -3
  36. package/src/index.js +1 -1
  37. package/src/managers/DenoisingManager.js +93 -187
  38. package/src/Stages/ASVGF.js +0 -932
  39. package/src/Stages/BilateralFilter.js +0 -486
  40. package/src/Stages/EdgeFilter.js +0 -545
  41. package/src/Stages/Variance.js +0 -443
@@ -1,932 +0,0 @@
1
- import { Fn, vec3, vec4, float, int, uint, ivec2, uvec2, uniform,
2
- If, dot, max, min, abs, mix, pow, exp, sqrt, select,
3
- textureLoad, textureStore, workgroupArray, workgroupBarrier, localId, workgroupId } from 'three/tsl';
4
- import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
5
- import { HalfFloatType, FloatType, RGBAFormat, NearestFilter, LinearFilter, Box2, Vector2 } from 'three';
6
- import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
7
- import { luminance } from '../TSL/Common.js';
8
- import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
9
- import { ALBEDO_EPS, ASVGF_QUALITY_PRESETS } from './DenoiserSettings.js';
10
-
11
- // Replace NaN/±Inf with a bounded value so one firefly can't permanently poison the
12
- // temporal EMA (mix() propagates NaN forever). Per-channel: NaN (x!=x) → 0, ±Inf → [0,1e7].
13
- const sanitize1 = ( x ) => select( x.equal( x ), x, float( 0.0 ) ).clamp( 0.0, 1e7 );
14
- const sanitizeRGB = ( c ) => vec3( sanitize1( c.x ), sanitize1( c.y ), sanitize1( c.z ) );
15
-
16
- /**
17
- * ASVGF — SVGF temporal denoising with albedo demodulation + adaptive-α.
18
- *
19
- * Adaptive temporal gradient (the "A"): the gradient kernel compares this
20
- * frame's demodulated lighting against the reprojected accumulated history,
21
- * floored by a per-pixel σ (max(Δ − k·σ, 0)), max-normalised and squared
22
- * (Q2RTX get_gradient). gradientStrength scales it into effectiveAlpha =
23
- * mix(baseAlpha, 1, gradient·gradientStrength) — high change ⇒ drop history
24
- * (anti-lag). All quality presets enable it (gradientStrength > 0); a static
25
- * scene reads gradient ≈ 0, so convergence is unaffected.
26
- *
27
- * Reads: pathtracer:color, pathtracer:albedo, pathtracer:normalDepth,
28
- * pathtracer:prevNormalDepth, motionVector:screenSpace
29
- * Publishes: asvgf:output (modulated), asvgf:demodulated (lighting + history),
30
- * asvgf:gradient
31
- */
32
- export class ASVGF extends RenderStage {
33
-
34
- constructor( renderer, options = {} ) {
35
-
36
- super( 'ASVGF', {
37
- ...options,
38
- executionMode: StageExecutionMode.PER_CYCLE
39
- } );
40
-
41
- this.renderer = renderer;
42
-
43
- this.temporalAlpha = uniform( options.temporalAlpha ?? ASVGF_QUALITY_PRESETS.medium.temporalAlpha );
44
- // > 0 is required for firefly rejection, not just anti-lag — see DENOISER_DEFAULTS.
45
- this.gradientStrength = uniform( options.gradientStrength ?? ASVGF_QUALITY_PRESETS.medium.gradientStrength );
46
- // σ multiplier for the per-pixel noise floor (NRD luminanceSigmaScale ≈ 2).
47
- this.gradientSigmaScale = uniform( options.gradientSigmaScale ?? ASVGF_QUALITY_PRESETS.medium.gradientSigmaScale );
48
- // Secondary relative floor on the normalised gradient (0 = rely on σ alone).
49
- this.gradientNoiseFloor = uniform( options.gradientNoiseFloor ?? ASVGF_QUALITY_PRESETS.medium.gradientNoiseFloor );
50
- this.maxAccumFrames = uniform( options.maxAccumFrames ?? ASVGF_QUALITY_PRESETS.medium.maxAccumFrames );
51
-
52
- this.resW = uniform( options.width || 1 );
53
- this.resH = uniform( options.height || 1 );
54
-
55
- this.temporalEnabledU = uniform( 1.0 );
56
- // 1.0 for one frame after pipeline:historyReset → forces a fresh sample so stale pre-reset
57
- // (wrong-scene) history isn't blended in (mirrors Variance's _needsWarmReset).
58
- this.forceResetU = uniform( 0.0 );
59
-
60
- this._colorTexNode = new TextureNode();
61
- this._albedoTexNode = new TextureNode();
62
- this._motionTexNode = new TextureNode();
63
- this._normalDepthTexNode = new TextureNode();
64
- this._prevNormalDepthTexNode = new TextureNode();
65
- this._readTemporalTexNode = new TextureNode();
66
- this._gradientReadTexNode = new TextureNode();
67
-
68
- const w = options.width || 1;
69
- const h = options.height || 1;
70
-
71
- // FloatType for ping-pong: demodulated lighting on dark materials
72
- // (lighting ≈ color/0.01) exceeds HalfFloat's 65k cap on HDR.
73
- // LinearFilter is required for textureLoad codegen on StorageTextures.
74
- this._temporalTexA = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
75
- this._temporalTexA.type = FloatType;
76
- this._temporalTexA.format = RGBAFormat;
77
- this._temporalTexA.minFilter = LinearFilter;
78
- this._temporalTexA.magFilter = LinearFilter;
79
-
80
- this._temporalTexB = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
81
- this._temporalTexB.type = FloatType;
82
- this._temporalTexB.format = RGBAFormat;
83
- this._temporalTexB.minFilter = LinearFilter;
84
- this._temporalTexB.magFilter = LinearFilter;
85
-
86
- this._outputModulatedTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
87
- this._outputModulatedTex.type = FloatType;
88
- this._outputModulatedTex.format = RGBAFormat;
89
- this._outputModulatedTex.minFilter = LinearFilter;
90
- this._outputModulatedTex.magFilter = LinearFilter;
91
-
92
- this._gradientStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
93
- this._gradientStorageTex.type = HalfFloatType;
94
- this._gradientStorageTex.format = RGBAFormat;
95
- this._gradientStorageTex.minFilter = LinearFilter;
96
- this._gradientStorageTex.magFilter = LinearFilter;
97
-
98
- // Over-allocated StorageTextures are sampled by UV downstream; copy the
99
- // active region into right-sized RTs and publish those instead.
100
- this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
101
-
102
- this._demodulatedRT = new RenderTarget( w, h, {
103
- type: FloatType,
104
- format: RGBAFormat,
105
- minFilter: LinearFilter,
106
- magFilter: LinearFilter,
107
- depthBuffer: false,
108
- stencilBuffer: false
109
- } );
110
-
111
- this._outputRT = new RenderTarget( w, h, {
112
- type: FloatType,
113
- format: RGBAFormat,
114
- minFilter: LinearFilter,
115
- magFilter: LinearFilter,
116
- depthBuffer: false,
117
- stencilBuffer: false
118
- } );
119
-
120
- this._gradientRT = new RenderTarget( w, h, {
121
- type: HalfFloatType,
122
- format: RGBAFormat,
123
- minFilter: LinearFilter,
124
- magFilter: LinearFilter,
125
- depthBuffer: false,
126
- stencilBuffer: false
127
- } );
128
-
129
- this.currentMoments = 0; // 0 = write A, read B; 1 = write B, read A
130
- this._compiled = false;
131
- this._needsWarmReset = false;
132
-
133
- this._dispatchX = Math.ceil( w / 8 );
134
- this._dispatchY = Math.ceil( h / 8 );
135
-
136
- this._buildGradientCompute();
137
- this._buildTemporalCompute();
138
-
139
- this.showHeatmap = false;
140
- this.debugMode = uniform( 0, 'int' );
141
-
142
- this._heatmapStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
143
- this._heatmapStorageTex.type = FloatType;
144
- this._heatmapStorageTex.format = RGBAFormat;
145
- this._heatmapStorageTex.minFilter = NearestFilter;
146
- this._heatmapStorageTex.magFilter = NearestFilter;
147
-
148
- this.heatmapTarget = new RenderTarget( w, h, {
149
- type: FloatType,
150
- format: RGBAFormat,
151
- minFilter: NearestFilter,
152
- magFilter: NearestFilter,
153
- depthBuffer: false,
154
- stencilBuffer: false
155
- } );
156
-
157
- this._heatmapRawColorTexNode = new TextureNode();
158
- this._heatmapColorTexNode = new TextureNode();
159
- this._heatmapTemporalTexNode = new TextureNode();
160
- this._heatmapNDTexNode = new TextureNode();
161
- this._heatmapMotionTexNode = new TextureNode();
162
- this._heatmapGradientTexNode = new TextureNode();
163
-
164
- this._buildHeatmapCompute();
165
-
166
- }
167
-
168
- // Adaptive temporal gradient: per pixel, compare this frame's DEMODULATED
169
- // lighting against the motion-reprojected accumulated history (low-noise),
170
- // floored by a per-pixel σ from the 5×5 spatial neighbourhood. The σ floor
171
- // is what the old fixed 0.15 constant couldn't give — on a static scene the
172
- // 1-SPP sample sits within ±k·σ of the converged estimate so the gradient
173
- // reads ~0 (no convergence penalty); only real change (moving light, anim,
174
- // disocclusion) exceeds it. Demodulation (vs raw color) keeps albedo/texture
175
- // edges from false-firing. Output .x feeds effectiveAlpha in the temporal pass.
176
- _buildGradientCompute() {
177
-
178
- const colorTex = this._colorTexNode;
179
- const albedoTex = this._albedoTexNode;
180
- const motionTex = this._motionTexNode;
181
- // Accumulated history (demodulated lighting in .xyz, history count in .w).
182
- // Same node the temporal pass reads; set to readTemporal before dispatch.
183
- const histTex = this._readTemporalTexNode;
184
- const noiseFloor = this.gradientNoiseFloor;
185
- const sigmaScale = this.gradientSigmaScale;
186
- const gradientStorageTex = this._gradientStorageTex;
187
- const resW = this.resW;
188
- const resH = this.resH;
189
-
190
- // 12×12 tile = 8×8 workgroup + 2px border for the 5×5 stencil.
191
- const TILE_W = 12;
192
- const TILE_BORDER = 2;
193
- const TILE_TOTAL = TILE_W * TILE_W; // 144
194
- const WG_SIZE = 8;
195
- const WG_THREADS = WG_SIZE * WG_SIZE; // 64
196
-
197
- const sharedCurLum = workgroupArray( 'float', TILE_TOTAL );
198
- const sharedHistLum = workgroupArray( 'float', TILE_TOTAL );
199
-
200
- const computeFn = Fn( () => {
201
-
202
- const lx = localId.x;
203
- const ly = localId.y;
204
- const linearIdx = ly.mul( WG_SIZE ).add( lx );
205
-
206
- // Hoisted outside loadEntry so all 3 load rounds reuse the same nodes.
207
- const tileOriginX = int( workgroupId.x ).mul( WG_SIZE ).sub( TILE_BORDER ).toVar();
208
- const tileOriginY = int( workgroupId.y ).mul( WG_SIZE ).sub( TILE_BORDER ).toVar();
209
-
210
- const loadEntry = ( k ) => {
211
-
212
- const sx = k.mod( uint( TILE_W ) );
213
- const sy = k.div( uint( TILE_W ) );
214
- const gxL = tileOriginX.add( int( sx ) ).clamp( int( 0 ), int( resW ).sub( 1 ) );
215
- const gyL = tileOriginY.add( int( sy ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
216
-
217
- // Demodulated current lighting luminance (matches the temporal pass:
218
- // safeAlbedo = max(albedo, ALBEDO_EPS) keeps sky/dark-material round-trip).
219
- const curColor = textureLoad( colorTex, ivec2( gxL, gyL ) ).xyz;
220
- const curAlbedo = textureLoad( albedoTex, ivec2( gxL, gyL ) ).xyz;
221
- const curLighting = sanitizeRGB( curColor.div( max( curAlbedo, vec3( ALBEDO_EPS ) ) ) );
222
- const curLum = luminance( curLighting ).toVar();
223
- sharedCurLum.element( k ).assign( curLum );
224
-
225
- // Reproject the accumulated history to this pixel; read its lighting
226
- // luminance. Invalid motion → mirror current so the delta is 0
227
- // (disocclusion handled by the temporal pass's geometric gate).
228
- const motion = textureLoad( motionTex, ivec2( gxL, gyL ) );
229
- const prevXf = float( gxL ).sub( motion.x.mul( resW ) );
230
- const prevYf = float( gyL ).sub( motion.y.mul( resH ) );
231
- // Round-to-nearest (not truncate) — the gradient does a nearest-neighbour
232
- // history lookup; +0.5 removes the half-pixel floor bias vs the temporal tap.
233
- const prevX = int( prevXf.add( 0.5 ) ).clamp( int( 0 ), int( resW ).sub( 1 ) );
234
- const prevY = int( prevYf.add( 0.5 ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
235
- const motionValid = motion.w.greaterThan( 0.5 );
236
- const histLighting = textureLoad( histTex, ivec2( prevX, prevY ) ).xyz;
237
- const histLum = motionValid.select( luminance( histLighting ), curLum );
238
- sharedHistLum.element( k ).assign( histLum );
239
-
240
- };
241
-
242
- // 144 entries / 64 threads → 3 rounds, last partially populated.
243
- loadEntry( linearIdx );
244
-
245
- const idx2 = linearIdx.add( uint( WG_THREADS ) );
246
- If( idx2.lessThan( uint( TILE_TOTAL ) ), () => {
247
-
248
- loadEntry( idx2 );
249
-
250
- } );
251
-
252
- const idx3 = linearIdx.add( uint( WG_THREADS * 2 ) );
253
- If( idx3.lessThan( uint( TILE_TOTAL ) ), () => {
254
-
255
- loadEntry( idx3 );
256
-
257
- } );
258
-
259
- workgroupBarrier();
260
-
261
- const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( lx ) );
262
- const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( ly ) );
263
-
264
- If( gx.lessThan( int( resW ) ).and( gy.lessThan( int( resH ) ) ), () => {
265
-
266
- // Pass 1 — per-pixel noise σ from the 5×5 (demodulated) neighbourhood.
267
- const sumLum = float( 0.0 ).toVar();
268
- const sumLumSq = float( 0.0 ).toVar();
269
-
270
- for ( let dy = - TILE_BORDER; dy <= TILE_BORDER; dy ++ ) {
271
-
272
- for ( let dx = - TILE_BORDER; dx <= TILE_BORDER; dx ++ ) {
273
-
274
- const idx = ly.add( uint( TILE_BORDER + dy ) )
275
- .mul( uint( TILE_W ) )
276
- .add( lx.add( uint( TILE_BORDER + dx ) ) );
277
- const cL = sharedCurLum.element( idx );
278
- sumLum.addAssign( cL );
279
- sumLumSq.addAssign( cL.mul( cL ) );
280
-
281
- }
282
-
283
- }
284
-
285
- const meanLum = sumLum.div( 25.0 );
286
- const variance = max( sumLumSq.div( 25.0 ).sub( meanLum.mul( meanLum ) ), float( 0.0 ) );
287
- const sigmaFloor = sqrt( variance ).mul( sigmaScale ).toVar();
288
-
289
- // Pass 2 — 5×5 average of the squared, σ-floored, max-normalised
290
- // temporal change. Squaring (Q2RTX get_gradient) suppresses residual
291
- // MC noise while staying reactive in high contrast.
292
- const sumG = float( 0.0 ).toVar();
293
-
294
- for ( let dy = - TILE_BORDER; dy <= TILE_BORDER; dy ++ ) {
295
-
296
- for ( let dx = - TILE_BORDER; dx <= TILE_BORDER; dx ++ ) {
297
-
298
- const idx = ly.add( uint( TILE_BORDER + dy ) )
299
- .mul( uint( TILE_W ) )
300
- .add( lx.add( uint( TILE_BORDER + dx ) ) );
301
- const cL = sharedCurLum.element( idx );
302
- const hL = sharedHistLum.element( idx );
303
- const floored = max( abs( cL.sub( hL ) ).sub( sigmaFloor ), float( 0.0 ) );
304
- const g = floored.div( max( max( cL, hL ), float( 0.001 ) ) );
305
- // Optional secondary relative floor (noiseFloor=0 → no-op).
306
- const gf = max( g.sub( noiseFloor ), float( 0.0 ) )
307
- .div( max( float( 1.0 ).sub( noiseFloor ), float( 0.0001 ) ) );
308
- sumG.addAssign( gf.mul( gf ) );
309
-
310
- }
311
-
312
- }
313
-
314
- const gradientRaw = sumG.div( 25.0 ).clamp( 0.0, 1.0 ).toVar();
315
-
316
- // Trust the gradient only once enough history has accumulated at the
317
- // reprojected centre — early frames have noisy history → false fires.
318
- const cMotion = textureLoad( motionTex, ivec2( gx, gy ) );
319
- const cPrevX = int( float( gx ).sub( cMotion.x.mul( resW ) ).add( 0.5 ) ).clamp( int( 0 ), int( resW ).sub( 1 ) );
320
- const cPrevY = int( float( gy ).sub( cMotion.y.mul( resH ) ).add( 0.5 ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
321
- const histLen = cMotion.w.greaterThan( 0.5 )
322
- .select( textureLoad( histTex, ivec2( cPrevX, cPrevY ) ).w, float( 0.0 ) );
323
- const confidence = histLen.div( 4.0 ).clamp( 0.0, 1.0 );
324
-
325
- const gradient = gradientRaw.mul( confidence );
326
-
327
- textureStore(
328
- gradientStorageTex,
329
- uvec2( uint( gx ), uint( gy ) ),
330
- vec4( gradient, gradientRaw, sigmaFloor, 1.0 )
331
- ).toWriteOnly();
332
-
333
- } );
334
-
335
- } );
336
-
337
- this._gradientNode = computeFn().compute(
338
- [ this._dispatchX, this._dispatchY, 1 ],
339
- [ WG_SIZE, WG_SIZE, 1 ]
340
- );
341
-
342
- }
343
-
344
- // One temporal node per ping-pong direction.
345
- _buildTemporalCompute() {
346
-
347
- this._temporalNodeA = this._buildTemporalForDirection( this._temporalTexA );
348
- this._temporalNodeB = this._buildTemporalForDirection( this._temporalTexB );
349
-
350
- }
351
-
352
- _buildTemporalForDirection( writeTemporalTex ) {
353
-
354
- const NORMAL_POWER = 16.0; // pow(dot, p): smooth surfaces ≈ 1, real edges ≈ 0
355
- const DEPTH_SIGMA = 0.05; // exp(-relDelta/σ)
356
- const VALIDITY_THRESHOLD = 0.01; // wSum below → disocclusion → fresh sample
357
-
358
- const colorTex = this._colorTexNode;
359
- const albedoTex = this._albedoTexNode;
360
- const motionTex = this._motionTexNode;
361
- const ndTex = this._normalDepthTexNode;
362
- const prevNDTex = this._prevNormalDepthTexNode;
363
- const prevTemporalTex = this._readTemporalTexNode;
364
- const gradientTex = this._gradientReadTexNode;
365
- const outputModulatedTex = this._outputModulatedTex;
366
-
367
- const maxAccumFrames = this.maxAccumFrames;
368
- const temporalAlphaMin = this.temporalAlpha;
369
- const gradientStrength = this.gradientStrength;
370
- const temporalEnabledU = this.temporalEnabledU;
371
- const forceResetU = this.forceResetU;
372
- const resW = this.resW;
373
- const resH = this.resH;
374
-
375
- const WG_SIZE = 8;
376
-
377
- const computeFn = Fn( () => {
378
-
379
- const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
380
- const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
381
-
382
- If( gx.lessThan( int( resW ) ).and( gy.lessThan( int( resH ) ) ), () => {
383
-
384
- const coord = ivec2( gx, gy );
385
- const currentSample = textureLoad( colorTex, coord );
386
- const currentColor = currentSample.xyz;
387
- // Coverage from the path tracer — 0 on background rays when
388
- // transparentBackground is on. Forcing 1.0 here made the whole frame opaque,
389
- // so a transparent render composited as black.
390
- const currentAlpha = currentSample.w;
391
- const currentAlbedo = textureLoad( albedoTex, coord ).xyz;
392
-
393
- // Same safeAlbedo on both demod and re-mod sides → exact
394
- // round-trip for sky/miss rays where albedo=0.
395
- const safeAlbedo = max( currentAlbedo, vec3( ALBEDO_EPS ) );
396
- const currentLighting = sanitizeRGB( currentColor.div( safeAlbedo ) );
397
-
398
- // Defaults = fresh sample (no temporal blend).
399
- const demodResult = vec4( currentLighting, 1.0 ).toVar();
400
- const modulatedResult = vec4( currentColor, currentAlpha ).toVar();
401
-
402
- // forceResetU skips the blend for one frame after pipeline:historyReset → re-anchors
403
- // history to the current (post-reset) scene instead of the stale ping-pong.
404
- If( temporalEnabledU.greaterThan( 0.5 ).and( forceResetU.lessThan( 0.5 ) ), () => {
405
-
406
- const motion = textureLoad( motionTex, coord );
407
- const motionValid = motion.w.greaterThan( 0.5 );
408
-
409
- const prevXf = float( gx ).sub( motion.x.mul( resW ) );
410
- const prevYf = float( gy ).sub( motion.y.mul( resH ) );
411
- // Upper bound is the inclusive last pixel (< res, not < res-1): the 2×2 taps
412
- // already clamp to [0,res-1] and wSum gates bad taps, so res-1 wrongly
413
- // rejected the trailing column/row. Matches MotionVector's inclusive UV.
414
- const prevOnScreen = prevXf.greaterThanEqual( 0.0 )
415
- .and( prevXf.lessThan( float( resW ) ) )
416
- .and( prevYf.greaterThanEqual( 0.0 ) )
417
- .and( prevYf.lessThan( float( resH ) ) );
418
-
419
- If( motionValid.and( prevOnScreen ), () => {
420
-
421
- const ndCurrent = textureLoad( ndTex, coord );
422
- const nCurrent = ndCurrent.xyz.mul( 2.0 ).sub( 1.0 );
423
- const depthCurrent = ndCurrent.w;
424
-
425
- const x0 = int( prevXf );
426
- const y0 = int( prevYf );
427
- const x1 = x0.add( int( 1 ) );
428
- const y1 = y0.add( int( 1 ) );
429
- const fx = prevXf.sub( float( x0 ) );
430
- const fy = prevYf.sub( float( y0 ) );
431
-
432
- const x0c = x0.clamp( int( 0 ), int( resW ).sub( 1 ) );
433
- const x1c = x1.clamp( int( 0 ), int( resW ).sub( 1 ) );
434
- const y0c = y0.clamp( int( 0 ), int( resH ).sub( 1 ) );
435
- const y1c = y1.clamp( int( 0 ), int( resH ).sub( 1 ) );
436
-
437
- const w00 = float( 1.0 ).sub( fx ).mul( float( 1.0 ).sub( fy ) );
438
- const w10 = fx.mul( float( 1.0 ).sub( fy ) );
439
- const w01 = float( 1.0 ).sub( fx ).mul( fy );
440
- const w11 = fx.mul( fy );
441
-
442
- // SVGF soft per-tap weight: bilinear × normal × depth
443
- // (prev-frame normalDepth, geometric normals — stable).
444
- const tapValid = ( xi, yi, bilinearW ) => {
445
-
446
- const prevND = textureLoad( prevNDTex, ivec2( xi, yi ) );
447
- const nPrev = prevND.xyz.mul( 2.0 ).sub( 1.0 );
448
- const depthPrev = prevND.w;
449
- const normalDot = dot( nCurrent, nPrev ).clamp( 0.0, 1.0 );
450
- const normalW = pow( normalDot, float( NORMAL_POWER ) );
451
- const depthDelta = abs( depthCurrent.sub( depthPrev ) )
452
- .div( max( depthCurrent, float( 0.001 ) ) );
453
- const depthW = exp( depthDelta.div( float( DEPTH_SIGMA ) ).negate() );
454
- return bilinearW.mul( normalW ).mul( depthW );
455
-
456
- };
457
-
458
- const v00 = tapValid( x0c, y0c, w00 );
459
- const v10 = tapValid( x1c, y0c, w10 );
460
- const v01 = tapValid( x0c, y1c, w01 );
461
- const v11 = tapValid( x1c, y1c, w11 );
462
-
463
- const wSum = v00.add( v10 ).add( v01 ).add( v11 );
464
-
465
- If( wSum.greaterThan( float( VALIDITY_THRESHOLD ) ), () => {
466
-
467
- const p00 = textureLoad( prevTemporalTex, ivec2( x0c, y0c ) );
468
- const p10 = textureLoad( prevTemporalTex, ivec2( x1c, y0c ) );
469
- const p01 = textureLoad( prevTemporalTex, ivec2( x0c, y1c ) );
470
- const p11 = textureLoad( prevTemporalTex, ivec2( x1c, y1c ) );
471
-
472
- const invWSum = float( 1.0 ).div( wSum );
473
- const prevLighting = p00.xyz.mul( v00 )
474
- .add( p10.xyz.mul( v10 ) )
475
- .add( p01.xyz.mul( v01 ) )
476
- .add( p11.xyz.mul( v11 ) )
477
- .mul( invWSum );
478
-
479
- const prevHistory = p00.w.mul( v00 )
480
- .add( p10.w.mul( v10 ) )
481
- .add( p01.w.mul( v01 ) )
482
- .add( p11.w.mul( v11 ) )
483
- .mul( invWSum );
484
-
485
- // adaptive α — gradient·gradientStrength boosts toward 1 on change.
486
- const gradient = textureLoad( gradientTex, coord ).x;
487
- const adaptiveBoost = gradient.mul( gradientStrength ).clamp( 0.0, 1.0 );
488
-
489
- const baseAlpha = max(
490
- float( 1.0 ).div( prevHistory.add( 1.0 ) ),
491
- temporalAlphaMin
492
- );
493
- const effectiveAlpha = mix( baseAlpha, float( 1.0 ), adaptiveBoost );
494
-
495
- // Sanitize the blend too: a NaN already baked into prevLighting
496
- // would otherwise survive mix() and re-poison the write target.
497
- const blendedLighting = sanitizeRGB( mix( prevLighting, currentLighting, effectiveAlpha ) );
498
- const newHistory = min( prevHistory.add( 1.0 ), maxAccumFrames );
499
-
500
- demodResult.assign( vec4( blendedLighting, newHistory ) );
501
- modulatedResult.assign( vec4( blendedLighting.mul( safeAlbedo ), currentAlpha ) );
502
-
503
- } );
504
-
505
- } );
506
-
507
- } );
508
-
509
- textureStore(
510
- writeTemporalTex,
511
- uvec2( uint( gx ), uint( gy ) ),
512
- demodResult
513
- ).toWriteOnly();
514
-
515
- textureStore(
516
- outputModulatedTex,
517
- uvec2( uint( gx ), uint( gy ) ),
518
- modulatedResult
519
- ).toWriteOnly();
520
-
521
- } );
522
-
523
- } );
524
-
525
- return computeFn().compute(
526
- [ this._dispatchX, this._dispatchY, 1 ],
527
- [ WG_SIZE, WG_SIZE, 1 ]
528
- );
529
-
530
- }
531
-
532
- _buildHeatmapCompute() {
533
-
534
- const rawColorTex = this._heatmapRawColorTexNode;
535
- const colorTex = this._heatmapColorTexNode;
536
- const temporalTex = this._heatmapTemporalTexNode;
537
- const ndTex = this._heatmapNDTexNode;
538
- const motionTex = this._heatmapMotionTexNode;
539
- const gradientTex = this._heatmapGradientTexNode;
540
- const heatmapOut = this._heatmapStorageTex;
541
- const mode = this.debugMode;
542
- const resW = this.resW;
543
- const resH = this.resH;
544
-
545
- const WG_SIZE = 8;
546
-
547
- const computeFn = Fn( () => {
548
-
549
- const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
550
- const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
551
-
552
- If( gx.lessThan( int( resW ) ).and( gy.lessThan( int( resH ) ) ), () => {
553
-
554
- const coord = ivec2( gx, gy );
555
- const result = vec4( 0.0, 0.0, 0.0, 1.0 ).toVar();
556
-
557
- // Chained If/ElseIf — separate If blocks let inactive-branch
558
- // texture samples contaminate the output.
559
- If( mode.equal( int( 0 ) ), () => {
560
-
561
- // 0: beauty (modulated ASVGF output)
562
- const c = textureLoad( colorTex, coord ).xyz;
563
- result.assign( vec4( c, 1.0 ) );
564
-
565
- } ).ElseIf( mode.equal( int( 1 ) ), () => {
566
-
567
- // 1: 3×3 luminance variance of raw PT input
568
- const meanLum = float( 0.0 ).toVar();
569
- const meanLumSq = float( 0.0 ).toVar();
570
-
571
- for ( let dy = - 1; dy <= 1; dy ++ ) {
572
-
573
- for ( let dx = - 1; dx <= 1; dx ++ ) {
574
-
575
- const sx = gx.add( dx ).clamp( int( 0 ), int( resW ).sub( 1 ) );
576
- const sy = gy.add( dy ).clamp( int( 0 ), int( resH ).sub( 1 ) );
577
- const s = textureLoad( rawColorTex, ivec2( sx, sy ) ).xyz;
578
- const lum = luminance( s );
579
- meanLum.addAssign( lum );
580
- meanLumSq.addAssign( lum.mul( lum ) );
581
-
582
- }
583
-
584
- }
585
-
586
- meanLum.divAssign( 9.0 );
587
- meanLumSq.divAssign( 9.0 );
588
- const variance = max( meanLumSq.sub( meanLum.mul( meanLum ) ), float( 0.0 ) );
589
- // Normalise by mean for HDR, scale to [0,1] for ramp.
590
- const relVar = variance.div( max( meanLum.mul( meanLum ), float( 0.0001 ) ) );
591
- const t = relVar.mul( 10.0 ).clamp( 0.0, 1.0 );
592
-
593
- // blue → cyan → green → yellow → red
594
- const r = t.sub( 0.5 ).mul( 4.0 ).clamp( 0.0, 1.0 );
595
- const g = t.mul( 4.0 ).clamp( 0.0, 1.0 ).sub(
596
- t.sub( 0.75 ).mul( 4.0 ).clamp( 0.0, 1.0 )
597
- );
598
- const b = float( 1.0 ).sub( t.sub( 0.25 ).mul( 4.0 ).clamp( 0.0, 1.0 ) );
599
- result.assign( vec4( r, g, b, 1.0 ) );
600
-
601
- } ).ElseIf( mode.equal( int( 2 ) ), () => {
602
-
603
- // 2: history length
604
- const historyLength = textureLoad( temporalTex, coord ).w;
605
- const t = historyLength.div( 32.0 ).clamp( 0.0, 1.0 );
606
- result.assign( vec4( float( 1.0 ).sub( t ), t, float( 0.2 ), 1.0 ) );
607
-
608
- } ).ElseIf( mode.equal( int( 3 ) ), () => {
609
-
610
- // 3: motion vectors
611
- const motion = textureLoad( motionTex, coord );
612
- const mx = abs( motion.x ).mul( 100.0 ).clamp( 0.0, 1.0 );
613
- const my = abs( motion.y ).mul( 100.0 ).clamp( 0.0, 1.0 );
614
- const magnitude = mx.add( my ).clamp( 0.0, 1.0 );
615
- result.assign( vec4( mx, my, magnitude.mul( 0.3 ), 1.0 ) );
616
-
617
- } ).ElseIf( mode.equal( int( 4 ) ), () => {
618
-
619
- // 4: normals
620
- const nd = textureLoad( ndTex, coord );
621
- result.assign( vec4( nd.xyz, 1.0 ) );
622
-
623
- } ).Else( () => {
624
-
625
- // 5: temporal-luminance gradient
626
- const grad = textureLoad( gradientTex, coord ).x;
627
- const t = grad.mul( 5.0 ).clamp( 0.0, 1.0 );
628
- result.assign( vec4( t, t.mul( 0.5 ), float( 1.0 ).sub( t ), 1.0 ) );
629
-
630
- } );
631
-
632
- textureStore(
633
- heatmapOut,
634
- uvec2( uint( gx ), uint( gy ) ),
635
- result
636
- ).toWriteOnly();
637
-
638
- } );
639
-
640
- } );
641
-
642
- this._heatmapComputeNode = computeFn().compute(
643
- [ this._dispatchX, this._dispatchY, 1 ],
644
- [ WG_SIZE, WG_SIZE, 1 ]
645
- );
646
-
647
- }
648
-
649
- setupEventListeners() {
650
-
651
- this.on( 'pipeline:historyReset', () => this.resetTemporalData() );
652
-
653
- }
654
-
655
- render( context ) {
656
-
657
- if ( ! this.enabled ) return;
658
-
659
- const colorTex = context.getTexture( 'pathtracer:color' );
660
- const albedoTex = context.getTexture( 'pathtracer:albedo' );
661
- const normalDepthTex = context.getTexture( 'pathtracer:normalDepth' );
662
- // First frame fallback — alias current ND.
663
- const prevNormalDepthTex = context.getTexture( 'pathtracer:prevNormalDepth' )
664
- || normalDepthTex;
665
- const motionTex = context.getTexture( 'motionVector:screenSpace' );
666
-
667
- if ( ! colorTex ) return;
668
-
669
- const img = colorTex.image;
670
- if ( img && img.width > 0 && img.height > 0 ) {
671
-
672
- // Compare against an active-size RT, not the fixed-2048 StorageTexture.
673
- if ( img.width !== this._outputRT.width ||
674
- img.height !== this._outputRT.height ) {
675
-
676
- this.setSize( img.width, img.height );
677
-
678
- }
679
-
680
- }
681
-
682
- this._colorTexNode.value = colorTex;
683
- if ( albedoTex ) this._albedoTexNode.value = albedoTex;
684
- if ( motionTex ) this._motionTexNode.value = motionTex;
685
- if ( normalDepthTex ) this._normalDepthTexNode.value = normalDepthTex;
686
- if ( prevNormalDepthTex ) this._prevNormalDepthTexNode.value = prevNormalDepthTex;
687
-
688
- const readTemporal = this.currentMoments === 0
689
- ? this._temporalTexB : this._temporalTexA;
690
- const writeNode = this.currentMoments === 0
691
- ? this._temporalNodeA : this._temporalNodeB;
692
- const writeTemporal = this.currentMoments === 0
693
- ? this._temporalTexA : this._temporalTexB;
694
-
695
- // First-frame compile while StorageTexture-typed nodes still hold
696
- // EmptyTexture, so textureLoad codegen emits the required `level`
697
- // parameter. Binding StorageTextures only AFTER compile keeps the
698
- // codegen path correct (otherwise reads would return zero at runtime).
699
- if ( ! this._compiled ) {
700
-
701
- this.renderer.compute( this._gradientNode );
702
- this.renderer.compute( this._temporalNodeA );
703
- this.renderer.compute( this._temporalNodeB );
704
- this._compiled = true;
705
-
706
- }
707
-
708
- this._readTemporalTexNode.value = readTemporal;
709
- this._gradientReadTexNode.value = this._gradientStorageTex;
710
-
711
- // Skip the gradient dispatch when nothing consumes it. The temporal
712
- // pass reads gradientTex unconditionally but multiplies by
713
- // gradientStrength=0 → the stale prior frame's gradient texture is
714
- // fine (the result is zeroed out anyway).
715
- const needsGradient = this.gradientStrength.value > 0 || this.showHeatmap;
716
- if ( needsGradient ) {
717
-
718
- this.renderer.compute( this._gradientNode );
719
-
720
- }
721
-
722
- // One-shot fresh re-anchor after pipeline:historyReset, threaded through the SINGLE
723
- // writeNode dispatch (a dual-node warmup would alias the read target — see setSize).
724
- this.forceResetU.value = this._needsWarmReset ? 1.0 : 0.0;
725
- this.renderer.compute( writeNode );
726
- this._needsWarmReset = false;
727
-
728
- // Copy active region out of the over-allocated StorageTextures into
729
- // right-sized RTs; downstream stages UV-sample these.
730
- this._srcRegion.max.set( this.resW.value, this.resH.value );
731
-
732
- this.renderer.copyTextureToTexture( writeTemporal, this._demodulatedRT.texture, this._srcRegion );
733
- this.renderer.copyTextureToTexture( this._outputModulatedTex, this._outputRT.texture, this._srcRegion );
734
- if ( needsGradient ) {
735
-
736
- this.renderer.copyTextureToTexture( this._gradientStorageTex, this._gradientRT.texture, this._srcRegion );
737
-
738
- }
739
-
740
- context.setTexture( 'asvgf:demodulated', this._demodulatedRT.texture );
741
- context.setTexture( 'asvgf:output', this._outputRT.texture );
742
- context.setTexture( 'asvgf:gradient', this._gradientRT.texture );
743
-
744
- this.currentMoments = 1 - this.currentMoments;
745
-
746
- if ( this.showHeatmap ) {
747
-
748
- // Mode 0 needs modulated for direct display; mode 2 needs the
749
- // ping-pong's history length in alpha.
750
- this._heatmapRawColorTexNode.value = colorTex;
751
- this._heatmapColorTexNode.value = this._outputModulatedTex;
752
- this._heatmapTemporalTexNode.value = writeTemporal;
753
- if ( normalDepthTex ) this._heatmapNDTexNode.value = normalDepthTex;
754
- if ( motionTex ) this._heatmapMotionTexNode.value = motionTex;
755
- this._heatmapGradientTexNode.value = this._gradientStorageTex;
756
-
757
- this.renderer.compute( this._heatmapComputeNode );
758
- this._srcRegion.max.set( this.heatmapTarget.width, this.heatmapTarget.height );
759
- this.renderer.copyTextureToTexture( this._heatmapStorageTex, this.heatmapTarget.texture, this._srcRegion );
760
-
761
- }
762
-
763
- }
764
-
765
- setHeatmapEnabled( enabled ) {
766
-
767
- this.showHeatmap = enabled;
768
-
769
- }
770
-
771
- setTemporalEnabled( enabled ) {
772
-
773
- this.temporalEnabledU.value = enabled ? 1.0 : 0.0;
774
-
775
- }
776
-
777
- updateParameters( params ) {
778
-
779
- if ( ! params ) return;
780
- if ( params.temporalAlpha !== undefined ) this.temporalAlpha.value = params.temporalAlpha;
781
- if ( params.gradientStrength !== undefined ) this.gradientStrength.value = params.gradientStrength;
782
- if ( params.gradientSigmaScale !== undefined ) this.gradientSigmaScale.value = params.gradientSigmaScale;
783
- if ( params.gradientNoiseFloor !== undefined ) this.gradientNoiseFloor.value = params.gradientNoiseFloor;
784
- if ( params.maxAccumFrames !== undefined ) this.maxAccumFrames.value = params.maxAccumFrames;
785
- if ( params.debugMode !== undefined ) this.debugMode.value = params.debugMode;
786
-
787
- }
788
-
789
- resetTemporalData() {
790
-
791
- this.currentMoments = 0;
792
- // Re-anchor history on the next frame — the ping-pong textures still hold
793
- // pre-reset (wrong-scene) lighting + history count, which would otherwise blend in.
794
- this._needsWarmReset = true;
795
-
796
- }
797
-
798
- // Reserved-storage change (e.g. 4K toggle): recreate the MAX-preallocated StorageTextures at the new live
799
- // reserved size + rebuild all compute nodes in place. Same stage object, so refs/wiring stay valid. Fresh
800
- // build ⇒ the first-render warmup re-runs (no aliasing — the nodes are new, matching initial construction).
801
- reallocateReservedStorage() {
802
-
803
- this._gradientNode?.dispose();
804
- this._temporalNodeA?.dispose();
805
- this._temporalNodeB?.dispose();
806
- this._heatmapComputeNode?.dispose();
807
- this._temporalTexA?.dispose();
808
- this._temporalTexB?.dispose();
809
- this._outputModulatedTex?.dispose();
810
- this._gradientStorageTex?.dispose();
811
- this._heatmapStorageTex?.dispose();
812
-
813
- const mk = ( type, filter ) => {
814
-
815
- const t = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
816
- t.type = type; t.format = RGBAFormat; t.minFilter = filter; t.magFilter = filter;
817
- return t;
818
-
819
- };
820
-
821
- this._temporalTexA = mk( FloatType, LinearFilter );
822
- this._temporalTexB = mk( FloatType, LinearFilter );
823
- this._outputModulatedTex = mk( FloatType, LinearFilter );
824
- this._gradientStorageTex = mk( HalfFloatType, LinearFilter );
825
- this._heatmapStorageTex = mk( FloatType, NearestFilter );
826
-
827
- this._buildGradientCompute();
828
- this._buildTemporalCompute();
829
- this._buildHeatmapCompute();
830
- this._compiled = false;
831
- this.currentMoments = 0;
832
- this._needsWarmReset = true;
833
-
834
- }
835
-
836
- setSize( width, height ) {
837
-
838
- // StorageTextures stay at max alloc — see resize crash fix (three.js #33061).
839
- this._demodulatedRT.setSize( width, height );
840
- this._demodulatedRT.texture.needsUpdate = true;
841
- this._outputRT.setSize( width, height );
842
- this._outputRT.texture.needsUpdate = true;
843
- this._gradientRT.setSize( width, height );
844
- this._gradientRT.texture.needsUpdate = true;
845
- this.heatmapTarget.setSize( width, height );
846
- this.heatmapTarget.texture.needsUpdate = true;
847
- this.resW.value = width;
848
- this.resH.value = height;
849
-
850
- this._dispatchX = Math.ceil( width / 8 );
851
- this._dispatchY = Math.ceil( height / 8 );
852
- this._gradientNode.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
853
- this._temporalNodeA.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
854
- this._temporalNodeB.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
855
- this._heatmapComputeNode.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
856
-
857
- // StorageTextures are over-allocated (never reallocated on resize), so the
858
- // compute kernels stay valid — do NOT reset _compiled. Re-running the warmup
859
- // would dispatch both temporal ping-pong nodes while _readTemporalTexNode still
860
- // aliases one node's write target, producing a "write-only storage +
861
- // TextureBinding in same synchronization scope" validation error.
862
-
863
- }
864
-
865
- reset() {
866
-
867
- // No-op: motion vectors handle camera moves; explicit pipeline:historyReset
868
- // clears history on scene/mode change.
869
-
870
- }
871
-
872
- // Free the over-allocated 2048² StorageTextures' GPU memory when the stage is disabled, so toggling
873
- // the denoiser off reclaims VRAM. The JS texture objects + compiled compute nodes are kept; three.js
874
- // lazily re-creates each GPUTexture on the next dispatch after re-enable. History re-anchors via the
875
- // warm reset (the RTs are render-res, not 2048² — left alone).
876
- releaseGPUMemory() {
877
-
878
- this._temporalTexA?.dispose();
879
- this._temporalTexB?.dispose();
880
- this._outputModulatedTex?.dispose();
881
- this._gradientStorageTex?.dispose();
882
- this._heatmapStorageTex?.dispose();
883
- // Render-res RT textures (sized to render resolution → meaningful VRAM at high res). Dispose the
884
- // .texture, not the RenderTarget (RT.dispose() doesn't free the backing GPUTexture here); these RTs
885
- // are copyTextureToTexture targets, so three.js reallocates on the next dispatch after re-enable.
886
- // Drop the published context outputs first so the Compositor fallback (reads asvgf:output) can't
887
- // sample a freed texture; render() re-publishes on re-enable.
888
- this.context?.removeTexture( 'asvgf:demodulated' );
889
- this.context?.removeTexture( 'asvgf:output' );
890
- this.context?.removeTexture( 'asvgf:gradient' );
891
- this._demodulatedRT?.texture?.dispose();
892
- this._outputRT?.texture?.dispose();
893
- this._gradientRT?.texture?.dispose();
894
- this.heatmapTarget?.texture?.dispose();
895
- this.resetTemporalData();
896
-
897
- }
898
-
899
- dispose() {
900
-
901
- this._gradientNode?.dispose();
902
- this._temporalNodeA?.dispose();
903
- this._temporalNodeB?.dispose();
904
- this._temporalTexA?.dispose();
905
- this._temporalTexB?.dispose();
906
- this._outputModulatedTex?.dispose();
907
- this._gradientStorageTex?.dispose();
908
- this._demodulatedRT?.dispose();
909
- this._outputRT?.dispose();
910
- this._gradientRT?.dispose();
911
- this._heatmapComputeNode?.dispose();
912
- this._heatmapStorageTex?.dispose();
913
- this.heatmapTarget?.dispose();
914
-
915
- this._colorTexNode?.dispose();
916
- this._albedoTexNode?.dispose();
917
- this._motionTexNode?.dispose();
918
- this._normalDepthTexNode?.dispose();
919
- this._prevNormalDepthTexNode?.dispose();
920
- this._readTemporalTexNode?.dispose();
921
- this._gradientReadTexNode?.dispose();
922
-
923
- this._heatmapRawColorTexNode?.dispose();
924
- this._heatmapColorTexNode?.dispose();
925
- this._heatmapTemporalTexNode?.dispose();
926
- this._heatmapNDTexNode?.dispose();
927
- this._heatmapMotionTexNode?.dispose();
928
- this._heatmapGradientTexNode?.dispose();
929
-
930
- }
931
-
932
- }