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
@@ -15,7 +15,7 @@ import { usesTextureCoordinates } from './Processor/GeometryExtractor.js';
15
15
  import { MAX_STORAGE_TEXTURE_SIZE, MAX_RESERVABLE_RENDER_SIZE, setReservedRenderSize } from './Processor/StorageTexturePool.js';
16
16
  import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory, disposeRenderer } from './Processor/utils.js';
17
17
  import { BuildTimer } from './Processor/BuildTimer.js';
18
- import { TextureReadback } from './Processor/TextureReadback.js';
18
+ import { TextureReadback, readPixels } from './Processor/TextureReadback.js';
19
19
  import { createLogger, fmt } from './utils/Logger.js';
20
20
  import { EngineEvents, LEGACY_EVENT_NAMES } from './EngineEvents.js';
21
21
  import { IssueLog, ISSUE_CODES, EngineIssueError } from './EngineIssues.js';
@@ -2730,7 +2730,7 @@ export class RayzeeRenderer extends EventDispatcher {
2730
2730
  * accumulation
2731
2731
  * @returns {Promise<{data: Float32Array|Uint8ClampedArray, width: number, height: number, colorSpace: string,
2732
2732
  * source: string, toneMappedOn?: 'gpu'|'cpu'}>} `source` is what was read: `'accumulation'`, or the
2733
- * denoiser that published it (`'oidn'`, `'asvgf'`, `'nrd'`, `'edgeFiltering'`, `'bilateralFiltering'`).
2733
+ * denoiser that published it (`'oidn'`, `'nrd'`).
2734
2734
  * `toneMappedOn` is set for `'srgb'` only.
2735
2735
  */
2736
2736
  async renderToBuffer( { colorSpace = 'srgb', preserveAlpha = false, source = 'accumulation' } = {} ) {
@@ -2766,14 +2766,14 @@ export class RayzeeRenderer extends EventDispatcher {
2766
2766
  const fn = ! gpu && tone.gain ? await tone.gain.cpu() : null;
2767
2767
  const data = gpu ?? toneMapToRGBA8( shown
2768
2768
  ? await this._readTexture( shown.texture, width, height )
2769
- : await this.renderer.readRenderTargetPixelsAsync( target, 0, 0, width, height, 0 ), { ...tone, pixelGain: fn && { fn, width, height } } );
2769
+ : await readPixels( this.renderer, target, width, height ), { ...tone, pixelGain: fn && { fn, width, height } } );
2770
2770
  return { data, width, height, colorSpace, source: shown?.source ?? 'accumulation', toneMappedOn: gpu ? 'gpu' : 'cpu' };
2771
2771
 
2772
2772
  }
2773
2773
 
2774
2774
  const { data: linear, source: read } = source === 'display'
2775
2775
  ? await this._readDisplaySource( target, width, height )
2776
- : { data: await this.renderer.readRenderTargetPixelsAsync( target, 0, 0, width, height, 0 ), source: 'accumulation' };
2776
+ : { data: await readPixels( this.renderer, target, width, height ), source: 'accumulation' };
2777
2777
 
2778
2778
  // `colorSpace` stays 'linear' — callers branch on it. `workingSpace` is the new, additive
2779
2779
  // answer to "linear in what primaries", which only means something once a config is loaded.
@@ -3450,7 +3450,7 @@ export class RayzeeRenderer extends EventDispatcher {
3450
3450
  async _readDisplaySource( accumulation, width, height ) {
3451
3451
 
3452
3452
  const shown = this._displaySource( accumulation );
3453
- if ( ! shown ) return { data: await this.renderer.readRenderTargetPixelsAsync( accumulation, 0, 0, width, height, 0 ), source: 'accumulation' };
3453
+ if ( ! shown ) return { data: await readPixels( this.renderer, accumulation, width, height ), source: 'accumulation' };
3454
3454
  return { data: await this._readTexture( shown.texture, width, height ), source: shown.source };
3455
3455
 
3456
3456
  }
@@ -4,57 +4,12 @@
4
4
  export const GBUFFER_MISS_DEPTH = 65504.0;
5
5
  export const GBUFFER_MISS_THRESHOLD = 6e4;
6
6
 
7
- // Albedo demodulation safety floor. ASVGF and BilateralFilter MUST use the
8
- // same value — demod (`color / safeAlbedo`) and remod (`lighting * safeAlbedo`)
9
- // only round-trip exactly when both sides agree.
7
+ // Albedo demodulation safety floor. Demod (`color / safeAlbedo`) and remod (`lighting * safeAlbedo`)
8
+ // only round-trip exactly when both sides use the same value.
10
9
  export const ALBEDO_EPS = 0.01;
11
10
 
12
- export const ASVGF_QUALITY_PRESETS = {
13
- // phiColor / phiDepth are RELATIVE tolerances (fractions). Bigger = more
14
- // permissive. The adaptive temporal gradient (gradientStrength > 0) is always
15
- // on: it measures real change in units of noise σ (gradientSigmaScale), so a
16
- // static scene reads ~0 (no convergence penalty) and only moving lights / anim
17
- // / disocclusion drop history. See ASVGF._buildGradientCompute.
18
- low: {
19
- temporalAlpha: 0.1,
20
- gradientStrength: 0.8,
21
- gradientSigmaScale: 2.5,
22
- gradientNoiseFloor: 0.05,
23
- atrousIterations: 3,
24
- phiColor: 1.0,
25
- phiNormal: 64.0,
26
- phiDepth: 0.1,
27
- phiLuminance: 6.0,
28
- maxAccumFrames: 16,
29
- varianceBoost: 0.5
30
- },
31
- medium: {
32
- temporalAlpha: 0.03,
33
- gradientStrength: 1.0,
34
- gradientSigmaScale: 2.5,
35
- gradientNoiseFloor: 0.05,
36
- atrousIterations: 4,
37
- phiColor: 0.5,
38
- phiNormal: 128.0,
39
- phiDepth: 0.05,
40
- phiLuminance: 4.0,
41
- maxAccumFrames: 64,
42
- varianceBoost: 1.0
43
- },
44
- high: {
45
- temporalAlpha: 0.0,
46
- gradientStrength: 1.0,
47
- gradientSigmaScale: 2.5,
48
- gradientNoiseFloor: 0.05,
49
- atrousIterations: 6,
50
- phiColor: 0.3,
51
- phiNormal: 256.0,
52
- phiDepth: 0.02,
53
- phiLuminance: 2.0,
54
- maxAccumFrames: 128,
55
- varianceBoost: 1.5
56
- }
57
- };
11
+ // The live view's denoisers, for setDenoiserStrategy.
12
+ export const DENOISER_STRATEGIES = Object.freeze( [ 'none', 'nrd', 'oidn' ] );
58
13
 
59
14
  // normHitDist = hitDist / (A + B·viewZ). A constant, not a uniform: the Shade write and the NRD
60
15
  // decode must agree, and the normalization is a pure round trip.
@@ -122,21 +77,9 @@ export const NRD_QUALITY_PRESETS = {
122
77
  },
123
78
  };
124
79
 
125
- const ASVGF_MEDIUM = ASVGF_QUALITY_PRESETS.medium;
126
-
127
80
  // What the live denoisers, the final OIDN pass and the AI upscaler start with; a host's own state seeds from these.
128
81
  export const DENOISER_DEFAULTS = {
129
- denoiserStrategy: 'none', // the live view's denoiser: 'none' | 'edgeaware' | 'asvgf' | 'nrd' | 'oidn'
130
-
131
- // EdgeAware denoiser (spatial-only SVGF à-trous). filterStrength: final blend
132
- // (0 = raw, 1 = filtered). edgeAtrousIterations: à-trous passes (step 1,2,4,8,16).
133
- // edgePhiLuminance: variance-scaled luminance edge-stop. edgePhiNormal: normal cone
134
- // exponent. edgePhiDepth: RELATIVE depth tolerance (fraction of ray distance).
135
- filterStrength: 1.0,
136
- edgeAtrousIterations: 5,
137
- edgePhiLuminance: 4.0,
138
- edgePhiNormal: 64.0,
139
- edgePhiDepth: 0.1,
82
+ denoiserStrategy: 'none', // the live view's denoiser, one of DENOISER_STRATEGIES
140
83
 
141
84
  enableOIDN: false,
142
85
  oidnQuality: 'fast',
@@ -157,25 +100,6 @@ export const DENOISER_DEFAULTS = {
157
100
  upscalerQuality: 'fast',
158
101
  upscalerHdr: true,
159
102
 
160
- // ASVGF runs its quality preset whenever it is switched on, so its values are that preset's.
161
- enableASVGF: false,
162
- asvgfQualityPreset: 'medium',
163
- asvgfTemporalAlpha: ASVGF_MEDIUM.temporalAlpha,
164
- asvgfAtrousIterations: ASVGF_MEDIUM.atrousIterations,
165
- asvgfPhiColor: ASVGF_MEDIUM.phiColor,
166
- asvgfPhiNormal: ASVGF_MEDIUM.phiNormal,
167
- asvgfPhiDepth: ASVGF_MEDIUM.phiDepth,
168
- asvgfPhiLuminance: ASVGF_MEDIUM.phiLuminance,
169
- asvgfVarianceBoost: ASVGF_MEDIUM.varianceBoost,
170
- asvgfMaxAccumFrames: ASVGF_MEDIUM.maxAccumFrames,
171
- // Must be > 0: the gradient also rejects fireflies before the EMA smears them across
172
- // ~1/alpha frames. At 0 the denoiser measures ~3x worse than no denoiser at 1 spp.
173
- asvgfGradientStrength: ASVGF_MEDIUM.gradientStrength,
174
- asvgfGradientSigmaScale: ASVGF_MEDIUM.gradientSigmaScale,
175
- asvgfGradientNoiseFloor: ASVGF_MEDIUM.gradientNoiseFloor,
176
- asvgfDebugMode: 0,
177
- showAsvgfHeatmap: false,
178
-
179
103
  // NRD (ReBLUR port) real-time denoiser — see NRD_DEFAULTS / NRD_QUALITY_PRESETS.
180
104
  nrdQualityPreset: 'medium',
181
105
  nrdDebugMode: 0,
@@ -1,4 +1,4 @@
1
- import { Fn, vec2, vec3, vec4, float, int, uint, ivec2, uvec2, uniform, If,
1
+ import { Fn, vec2, vec4, float, int, uint, ivec2, uvec2, uniform, If,
2
2
  textureLoad, textureStore, workgroupId, localId } from 'three/tsl';
3
3
  import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
4
4
  import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4, Box2, Vector2 } from 'three';
@@ -9,14 +9,13 @@ import { cameraRayPoint, cameraRayUniforms } from '../TSL/CameraRay.js';
9
9
  /**
10
10
  * WebGPU Motion Vector Stage (Compute Shader)
11
11
  *
12
- * Computes per-pixel screen-space and world-space motion vectors by
13
- * reconstructing world positions from linear ray depth and reprojecting
14
- * to the previous frame.
12
+ * Computes per-pixel screen-space motion vectors by reconstructing world
13
+ * positions from linear ray depth and reprojecting to the previous frame.
15
14
  *
16
15
  * Architecture (copy approach — proven working in PathTracer):
17
- * 1. Two compute shaders write to StorageTextures via textureStore
16
+ * 1. A compute shader writes to a StorageTexture via textureStore
18
17
  * 2. After dispatch, copyTextureToTexture transfers StorageTexture → RenderTarget
19
- * 3. RenderTarget textures are published to context (NOT StorageTextures —
18
+ * 3. The RenderTarget texture is published to context (NOT the StorageTexture —
20
19
  * cross-dispatch reads from StorageTexture return zeros in Three.js WebGPU)
21
20
  *
22
21
  * Algorithm:
@@ -26,9 +25,8 @@ import { cameraRayPoint, cameraRayUniforms } from '../TSL/CameraRay.js';
26
25
  * 4. Project to previous frame: prevVP * worldPos
27
26
  * 5. Motion = currentUV - prevUV
28
27
  *
29
- * Output formats (RGBA Float → copied to RGBA HalfFloat RenderTarget):
28
+ * Output format (RGBA Float → copied to RGBA HalfFloat RenderTarget):
30
29
  * screenSpace — xy=motion (UV-space), z=depth, w=validity
31
- * worldSpace — xyz=world velocity, w=validity
32
30
  *
33
31
  * Critical design decisions:
34
32
  * - matricesInitialized is NOT reset on pipeline:reset.
@@ -40,13 +38,8 @@ import { cameraRayPoint, cameraRayUniforms } from '../TSL/CameraRay.js';
40
38
  *
41
39
  * Execution: ALWAYS — motion vectors are needed every frame.
42
40
  *
43
- * Events listened:
44
- * pipeline:reset — reset frame counter (but NOT matrices)
45
- *
46
41
  * Textures published:
47
42
  * motionVector:screenSpace (from RenderTarget, not StorageTexture)
48
- * motionVector:worldSpace (from RenderTarget, not StorageTexture)
49
- * motionVector:motion (alias for screenSpace)
50
43
  *
51
44
  * Textures read:
52
45
  * pathtracer:normalDepth — linear depth for world position reconstruction
@@ -72,8 +65,6 @@ export class MotionVector extends RenderStage {
72
65
  this.currentViewProjectionMatrix = new Matrix4();
73
66
 
74
67
  this.matricesInitialized = false;
75
- this.isFirstFrame = true;
76
- this.frameCount = 0;
77
68
 
78
69
  // Camera uniforms for world position reconstruction
79
70
  // Synced from PathTracer each frame (same source as NormalDepth)
@@ -81,9 +72,6 @@ export class MotionVector extends RenderStage {
81
72
  this.cameraProjectionMatrixInverse = uniform( new Matrix4(), 'mat4' );
82
73
  this.cameraRay = cameraRayUniforms();
83
74
  this.prevVP = uniform( new Matrix4(), 'mat4' );
84
- this.isFirstFrameU = uniform( 1.0 ); // 1.0 = true, 0.0 = false
85
- this.deltaTime = uniform( 1.0 / 60.0 );
86
- this.velocityScale = uniform( 1.0 );
87
75
 
88
76
  // Resolution uniforms (for compute pixel coords)
89
77
  this.resolutionWidth = uniform( width );
@@ -92,7 +80,7 @@ export class MotionVector extends RenderStage {
92
80
  // Input texture node (swappable — no shader recompile)
93
81
  this._normalDepthTexNode = new TextureNode();
94
82
 
95
- // Write-only StorageTextures (compute output).
83
+ // Write-only StorageTexture (compute output).
96
84
  // Pre-allocate at max — StorageTexture.setSize() destroys the GPU texture
97
85
  // while the compute bind group keeps the stale view (three.js #33061).
98
86
  this._screenSpaceStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
@@ -101,35 +89,24 @@ export class MotionVector extends RenderStage {
101
89
  this._screenSpaceStorageTex.minFilter = NearestFilter;
102
90
  this._screenSpaceStorageTex.magFilter = NearestFilter;
103
91
 
104
- this._worldSpaceStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
105
- this._worldSpaceStorageTex.type = HalfFloatType;
106
- this._worldSpaceStorageTex.format = RGBAFormat;
107
- this._worldSpaceStorageTex.minFilter = NearestFilter;
108
- this._worldSpaceStorageTex.magFilter = NearestFilter;
109
-
110
- // Reused per-copy: copy only the active region out of the over-alloc texs.
92
+ // Reused per-copy: copy only the active region out of the over-alloc tex.
111
93
  this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
112
94
 
113
- // Readable RenderTargets (copy destinations — published to context)
114
- const rtOpts = {
95
+ // Readable RenderTarget (copy destination — published to context)
96
+ this.screenSpaceTarget = new RenderTarget( width, height, {
115
97
  type: HalfFloatType,
116
98
  format: RGBAFormat,
117
99
  minFilter: NearestFilter,
118
100
  magFilter: NearestFilter,
119
101
  depthBuffer: false,
120
102
  stencilBuffer: false
121
- };
122
-
123
- this.screenSpaceTarget = new RenderTarget( width, height, rtOpts );
124
- this.worldSpaceTarget = new RenderTarget( width, height, rtOpts );
103
+ } );
125
104
 
126
- // Dispatch dimensions (8x8 workgroups)
105
+ // Dispatch dimensions (16x16 workgroups)
127
106
  this._dispatchX = Math.ceil( width / 16 );
128
107
  this._dispatchY = Math.ceil( height / 16 );
129
108
 
130
- // Build compute nodes
131
109
  this._buildScreenSpaceCompute();
132
- this._buildWorldSpaceCompute();
133
110
 
134
111
  }
135
112
 
@@ -222,95 +199,6 @@ export class MotionVector extends RenderStage {
222
199
 
223
200
  }
224
201
 
225
- /**
226
- * World-space velocity compute shader.
227
- *
228
- * Approximates world-space velocity from UV displacement,
229
- * scaled by deltaTime and velocityScale.
230
- */
231
- _buildWorldSpaceCompute() {
232
-
233
- const normalDepthTex = this._normalDepthTexNode;
234
- const camWorldMat = this.cameraWorldMatrix;
235
- const camProjInvMat = this.cameraProjectionMatrixInverse;
236
- const prevVP = this.prevVP;
237
- const isFirstFrameU = this.isFirstFrameU;
238
- const deltaTime = this.deltaTime;
239
- const velocityScale = this.velocityScale;
240
- const resW = this.resolutionWidth;
241
- const resH = this.resolutionHeight;
242
- const outputTex = this._worldSpaceStorageTex;
243
-
244
- const WG_SIZE = 16;
245
-
246
- const computeFn = Fn( ( [ cwm, cpi ] ) => {
247
-
248
- const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
249
- const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
250
-
251
- If( gx.lessThan( int( resW ) ).and( gy.lessThan( int( resH ) ) ), () => {
252
-
253
- const nd = textureLoad( normalDepthTex, ivec2( gx, gy ) );
254
- const linearDepth = nd.w;
255
-
256
- const result = vec4( 0.0, 0.0, 0.0, 0.0 ).toVar();
257
-
258
- // Skip first frame and sky (depth >= 65504 sentinel)
259
- If( isFirstFrameU.lessThan( 0.5 ).and( linearDepth.lessThan( float( 6e4 ) ) ), () => {
260
-
261
- // Current pixel UV
262
- const currentUV = vec2(
263
- float( gx ).add( 0.5 ).div( resW ),
264
- float( gy ).add( 0.5 ).div( resH )
265
- );
266
- const worldPos = cameraRayPoint( currentUV, linearDepth, cwm, cpi, this.cameraRay );
267
-
268
- // Project to previous frame
269
- const prevClip = prevVP.mul( vec4( worldPos, 1.0 ) );
270
- // Flip prevUV.y to match WebGPU convention
271
- const prevNDC = prevClip.xy.div( prevClip.w );
272
- const prevUV = vec2(
273
- prevNDC.x.mul( 0.5 ).add( 0.5 ),
274
- prevNDC.y.mul( - 0.5 ).add( 0.5 )
275
- );
276
-
277
- const valid = prevUV.x.greaterThanEqual( 0.0 )
278
- .and( prevUV.x.lessThanEqual( 1.0 ) )
279
- .and( prevUV.y.greaterThanEqual( 0.0 ) )
280
- .and( prevUV.y.lessThanEqual( 1.0 ) );
281
-
282
- // World-space velocity approximation from UV displacement
283
- const motionUV = currentUV.sub( prevUV );
284
- const worldVelocity = vec3(
285
- motionUV.x.div( deltaTime ).mul( velocityScale ),
286
- motionUV.y.div( deltaTime ).mul( velocityScale ),
287
- 0.0
288
- );
289
-
290
- result.assign( valid.select(
291
- vec4( worldVelocity, 1.0 ),
292
- vec4( 0.0, 0.0, 0.0, 0.5 )
293
- ) );
294
-
295
- } );
296
-
297
- textureStore(
298
- outputTex,
299
- uvec2( uint( gx ), uint( gy ) ),
300
- result
301
- ).toWriteOnly();
302
-
303
- } );
304
-
305
- } );
306
-
307
- this._worldSpaceComputeNode = computeFn( camWorldMat, camProjInvMat ).compute(
308
- [ this._dispatchX, this._dispatchY, 1 ],
309
- [ WG_SIZE, WG_SIZE, 1 ]
310
- );
311
-
312
- }
313
-
314
202
  // ──────────────────────────────────────────────────
315
203
  // Camera matrix management
316
204
  // ──────────────────────────────────────────────────
@@ -387,16 +275,6 @@ export class MotionVector extends RenderStage {
387
275
  // Pipeline lifecycle
388
276
  // ──────────────────────────────────────────────────
389
277
 
390
- setupEventListeners() {
391
-
392
- this.on( 'pipeline:reset', () => {
393
-
394
- this.reset();
395
-
396
- } );
397
-
398
- }
399
-
400
278
  render( context ) {
401
279
 
402
280
  if ( ! this.enabled ) return;
@@ -408,11 +286,6 @@ export class MotionVector extends RenderStage {
408
286
  // Update camera matrices (CPU-side) — synced from PathTracer
409
287
  this._updateCameraMatrices();
410
288
 
411
- // Update isFirstFrame uniform
412
- this.isFirstFrameU.value = this.isFirstFrame ? 1.0 : 0.0;
413
-
414
- this.frameCount ++;
415
-
416
289
  // Auto-size to match normalDepth
417
290
  const img = normalDepthTex.image;
418
291
  if ( img && img.width > 0 && img.height > 0 ) {
@@ -429,67 +302,31 @@ export class MotionVector extends RenderStage {
429
302
  // Swap input texture (no shader recompile, just swap value)
430
303
  this._normalDepthTexNode.value = normalDepthTex;
431
304
 
432
- // Dispatch screen-space motion vector compute
433
305
  this.renderer.compute( this._screenSpaceComputeNode );
434
306
 
435
- // Dispatch world-space velocity compute
436
- this.renderer.compute( this._worldSpaceComputeNode );
437
-
438
- // Copy StorageTextures → RenderTargets (cross-dispatch reads from
307
+ // Copy StorageTexture → RenderTarget (cross-dispatch reads from
439
308
  // StorageTexture return zeros — must use RenderTarget for downstream stages).
440
- // srcRegion = active size; StorageTextures are over-allocated at 2048.
309
+ // srcRegion = active size; the StorageTexture is over-allocated at 2048.
441
310
  this._srcRegion.max.set( this.screenSpaceTarget.width, this.screenSpaceTarget.height );
442
311
  this.renderer.copyTextureToTexture( this._screenSpaceStorageTex, this.screenSpaceTarget.texture, this._srcRegion );
443
- this.renderer.copyTextureToTexture( this._worldSpaceStorageTex, this.worldSpaceTarget.texture, this._srcRegion );
444
312
 
445
- // Publish RenderTarget textures to context
446
313
  context.setTexture( 'motionVector:screenSpace', this.screenSpaceTarget.texture );
447
- context.setTexture( 'motionVector:worldSpace', this.worldSpaceTarget.texture );
448
- context.setTexture( 'motionVector:motion', this.screenSpaceTarget.texture );
449
-
450
- // Emit
451
- this.emit( 'motionvector:computed', {
452
- frame: this.frameCount,
453
- isFirstFrame: this.isFirstFrame
454
- } );
455
-
456
- this.isFirstFrame = false;
457
314
 
458
315
  }
459
316
 
460
- /**
461
- * Reset — intentionally does NOT reset matricesInitialized.
462
- * Motion vectors must track camera motion across pipeline resets.
463
- */
464
- // Free the 2048² StorageTextures when disabled (no consumer); three.js re-creates them on the next
317
+ // Free the 2048² StorageTexture when disabled (no consumer); three.js re-creates it on the next
465
318
  // dispatch after re-enable. _syncGBufferStages additionally reseeds camera history on re-enable.
466
319
  releaseGPUMemory() {
467
320
 
468
321
  this._screenSpaceStorageTex?.dispose();
469
- this._worldSpaceStorageTex?.dispose();
470
- this.reset();
471
-
472
- }
473
-
474
- reset() {
475
-
476
- if ( ! this.matricesInitialized ) {
477
-
478
- this.isFirstFrame = true;
479
-
480
- }
481
-
482
- this.frameCount = 0;
483
322
 
484
323
  }
485
324
 
486
325
  setSize( width, height ) {
487
326
 
488
- // StorageTextures stay at their max allocation (see constructor).
327
+ // The StorageTexture stays at its max allocation (see constructor).
489
328
  this.screenSpaceTarget.setSize( width, height );
490
329
  this.screenSpaceTarget.texture.needsUpdate = true;
491
- this.worldSpaceTarget.setSize( width, height );
492
- this.worldSpaceTarget.texture.needsUpdate = true;
493
330
  this.resolutionWidth.value = width;
494
331
  this.resolutionHeight.value = height;
495
332
 
@@ -502,57 +339,26 @@ export class MotionVector extends RenderStage {
502
339
 
503
340
  }
504
341
 
505
- if ( this._worldSpaceComputeNode ) {
506
-
507
- this._worldSpaceComputeNode.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
508
-
509
- }
510
-
511
342
  }
512
343
 
513
- setVelocityScale( scale ) {
514
-
515
- this.velocityScale.value = scale;
516
-
517
- }
518
-
519
- setDeltaTime( dt ) {
520
-
521
- this.deltaTime.value = dt;
522
-
523
- }
524
-
525
- // Reserved-storage change (e.g. 4K toggle): recreate the MAX-preallocated StorageTextures at the new live
526
- // reserved size + rebuild the compute nodes in place. Same stage object, so refs/wiring stay valid.
344
+ // Reserved-storage change (e.g. 4K toggle): recreate the MAX-preallocated StorageTexture at the new live
345
+ // reserved size + rebuild the compute node in place. Same stage object, so refs/wiring stay valid.
527
346
  reallocateReservedStorage() {
528
347
 
529
348
  this._screenSpaceComputeNode?.dispose();
530
- this._worldSpaceComputeNode?.dispose();
531
349
  this._screenSpaceStorageTex?.dispose();
532
- this._worldSpaceStorageTex?.dispose();
533
- const mk = () => {
534
-
535
- const t = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
536
- t.type = HalfFloatType; t.format = RGBAFormat; t.minFilter = NearestFilter; t.magFilter = NearestFilter;
537
- return t;
538
-
539
- };
540
-
541
- this._screenSpaceStorageTex = mk();
542
- this._worldSpaceStorageTex = mk();
350
+ const t = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
351
+ t.type = HalfFloatType; t.format = RGBAFormat; t.minFilter = NearestFilter; t.magFilter = NearestFilter;
352
+ this._screenSpaceStorageTex = t;
543
353
  this._buildScreenSpaceCompute();
544
- this._buildWorldSpaceCompute();
545
354
 
546
355
  }
547
356
 
548
357
  dispose() {
549
358
 
550
359
  this._screenSpaceComputeNode?.dispose();
551
- this._worldSpaceComputeNode?.dispose();
552
360
  this._screenSpaceStorageTex?.dispose();
553
- this._worldSpaceStorageTex?.dispose();
554
361
  this.screenSpaceTarget?.dispose();
555
- this.worldSpaceTarget?.dispose();
556
362
  this._normalDepthTexNode?.dispose();
557
363
 
558
364
  }
@@ -13,7 +13,7 @@ import { computeUVCache, processNormal, processBump, processMetalnessRoughness,
13
13
  import { withSceneResources } from '../TSL/SceneResources.js';
14
14
 
15
15
  /**
16
- * NormalDepth — primary-ray G-buffer for SVGF gates.
16
+ * NormalDepth — primary-ray G-buffer for the denoisers' edge tests (NRD, the OIDN motion history).
17
17
  *
18
18
  * RGB = geometric world normal · 0.5 + 0.5, A = linear ray distance (sky=65504, the
19
19
  * max HalfFloat value — a finite sentinel so miss−miss depth diffs stay 0, not Inf−Inf=NaN).
@@ -70,7 +70,7 @@ export class NormalDepth extends RenderStage {
70
70
  this._outputStorageTex.magFilter = NearestFilter;
71
71
 
72
72
  // Shading-normal output (geometric normal perturbed by normal/bump map).
73
- // Single buffer — only the spatial filter (current frame) consumes it.
73
+ // Single buffer — its readers (NRD, the OIDN history) use only the current frame.
74
74
  this._shadingStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
75
75
  this._shadingStorageTex.type = HalfFloatType;
76
76
  this._shadingStorageTex.format = RGBAFormat;
@@ -137,7 +137,7 @@ export class NormalDepth extends RenderStage {
137
137
  // _hasHistory is deliberately NOT cleared, mirroring MotionVector.reset(): the
138
138
  // prev-frame G-buffer tracks camera motion, not accumulation, and a camera move
139
139
  // resets accumulation every frame of a drag. Clearing it made render() overwrite
140
- // prevRT with the current frame, so ASVGF's temporal gate could never reject.
140
+ // prevRT with the current frame, so the OIDN history could never see a disocclusion.
141
141
  this._dirty = true;
142
142
 
143
143
  } );
@@ -374,7 +374,7 @@ export class NormalDepth extends RenderStage {
374
374
  this.renderer.copyTextureToTexture( this._shadingStorageTex, this._shadingRT.texture, this._srcRegion );
375
375
  if ( this._leafRT ) this.renderer.copyTextureToTexture( this._leafStorageTex, this._leafRT.texture, this._srcRegion );
376
376
 
377
- // First dispatch: seed prev from current so ASVGF doesn't see false
377
+ // First dispatch: seed prev from current so the history doesn't see false
378
378
  // disocclusion on frame 1.
379
379
  if ( ! this._hasHistory ) {
380
380
 
@@ -393,7 +393,7 @@ export class NormalDepth extends RenderStage {
393
393
  }
394
394
 
395
395
  // Free the 2048² StorageTextures when disabled (no consumer); three.js re-creates them on the next
396
- // dispatch after re-enable, and reset() re-arms the dirty/history fast-path. See ASVGF.releaseGPUMemory.
396
+ // dispatch after re-enable, and reset() re-arms the dirty/history fast-path. See NRD.releaseGPUMemory.
397
397
  releaseGPUMemory() {
398
398
 
399
399
  this._outputStorageTex?.dispose();
@@ -28,6 +28,7 @@ import {
28
28
  } from '../TSL/SortGlobalKernels.js';
29
29
  import { ENGINE_DEFAULTS } from '../EngineDefaults.js';
30
30
  import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
31
+ import { readPixels } from '../Processor/TextureReadback.js';
31
32
  import { ISSUE_CODES } from '../EngineIssues.js';
32
33
  import { createLogger, fmt } from '../utils/Logger.js';
33
34
  import { deviceMemoryGB } from '../Processor/HostMemory.js';
@@ -76,7 +77,7 @@ export class PathTracer extends PathTracerStage {
76
77
  this._auxGBufferUniform = uniform( 0, 'uint' );
77
78
  // Clean-aux mode: temporally accumulate + renormalize the aux NORMAL (like albedo) so a clean-aux
78
79
  // OIDN model (calb_cnrm/high, alb_nrm/balanced) gets the prefiltered-ish normal it expects instead
79
- // of the point-sampled per-frame value that leaks noise. Off for fast/ASVGF (they want the bump normal).
80
+ // of the point-sampled per-frame value that leaks noise. Off otherwise: the fast model wants the bump normal.
80
81
  this._cleanAuxNormalEnabled = false;
81
82
  this._cleanAuxNormalUniform = uniform( 0, 'uint' );
82
83
  // Aux MRT accumulates on an epoch of its own: enabling the denoiser at colour frame N must not leave
@@ -758,16 +759,7 @@ export class PathTracer extends PathTracerStage {
758
759
  const height = pool.renderHeight;
759
760
  const pixels = width * height;
760
761
 
761
- const readAttachment = async index => {
762
-
763
- const padded = await this.renderer.readRenderTargetPixelsAsync( pool.readTarget, 0, 0, width, height, index );
764
- const stride = Math.ceil( width * 16 / 256 ) * 64;
765
- if ( stride === width * 4 ) return new Float32Array( padded.buffer, padded.byteOffset, pixels * 4 ).slice();
766
- const tight = new Float32Array( pixels * 4 );
767
- for ( let y = 0; y < height; y ++ ) tight.set( padded.subarray( y * stride, y * stride + width * 4 ), y * width * 4 );
768
- return tight;
769
-
770
- };
762
+ const readAttachment = index => readPixels( this.renderer, pool.readTarget, width, height, index );
771
763
 
772
764
  const readBuffer = async ( attr, Type ) => attr ? new Type( await this.renderer.getArrayBufferAsync( attr, null, 0, pixels * 4 ) ) : null;
773
765
  const aux = this._auxGBufferEnabled && this._auxSamples > 0;
@@ -185,9 +185,6 @@ export class PathTracerStage extends RenderStage {
185
185
  this.renderModeChangeDelay = 50;
186
186
  this.pendingRenderMode = null;
187
187
 
188
- // Track interaction mode state for accumulation
189
- this.lastInteractionModeState = false;
190
-
191
188
  // Track changes for event emission
192
189
  this.cameraChanged = false;
193
190
  // When the view was last moved, for viewIsChanging.
@@ -372,12 +369,7 @@ export class PathTracerStage extends RenderStage {
372
369
  },
373
370
  onEnter: () => this.emit( 'pathtracer:interactionStart' ),
374
371
  onExit: () => this.emit( 'pathtracer:interactionEnd' ),
375
- onReset: () => {
376
-
377
- this.reset();
378
- this.emit( 'pathtracer:viewpointChanged' );
379
-
380
- }
372
+ onReset: () => this.reset(),
381
373
  } );
382
374
 
383
375
  }
@@ -561,8 +553,7 @@ export class PathTracerStage extends RenderStage {
561
553
 
562
554
  // lastRenderMode is deliberately NOT invalidated here — clearing it made
563
555
  // resetHistoryOnRenderModeChange see a phantom mode change 50 ms after every reset, wiping
564
- // ASVGF's history. A real mode change is still caught by the renderMode comparison.
565
- this.lastInteractionModeState = false;
556
+ // the denoisers' history. A real mode change is still caught by the renderMode comparison.
566
557
 
567
558
  // Only deterministic mode and lockstep readbacks rewind the seed axis; see _pinSeedToFrame.
568
559
  if ( this._pinSeedToFrame || this._lockstep ) this._seedTick = 0;
@@ -1472,7 +1463,6 @@ export class PathTracerStage extends RenderStage {
1472
1463
  _updateAccumulationUniforms( frameValue ) {
1473
1464
 
1474
1465
  const currentInteractionMode = this.cameraOptimizer?.isInInteractionMode() ?? false;
1475
- this.lastInteractionModeState = currentInteractionMode;
1476
1466
 
1477
1467
  if ( this.accumulationEnabled ) {
1478
1468
 
@@ -1512,7 +1502,6 @@ export class PathTracerStage extends RenderStage {
1512
1502
  // Not the same as the context's own `accumulatedFrames`, which counts pipeline renders — this
1513
1503
  // freezes when the frame retires or the camera moves, which is what a denoiser needs.
1514
1504
  context.setState( 'pathtracer:samples', this.frameCount );
1515
- context.setState( 'interactionMode', this.cameraOptimizer?.isInInteractionMode() ?? false );
1516
1505
  context.setState( 'renderMode', this.renderMode.value );
1517
1506
 
1518
1507
  }
@@ -1557,7 +1546,7 @@ export class PathTracerStage extends RenderStage {
1557
1546
 
1558
1547
  }
1559
1548
 
1560
- // ===== ASVGF DENOISING MANAGEMENT =====
1549
+ // ===== DENOISER HISTORY =====
1561
1550
 
1562
1551
  resetHistoryOnRenderModeChange( renderMode ) {
1563
1552
 
@@ -1576,9 +1565,7 @@ export class PathTracerStage extends RenderStage {
1576
1565
  if ( this.pendingRenderMode !== null && this.pendingRenderMode !== this.lastRenderMode ) {
1577
1566
 
1578
1567
  this.lastRenderMode = this.pendingRenderMode;
1579
- // History from the previous mode is not comparable. temporalAlpha is NOT
1580
- // touched here — it is owned by ASVGF_QUALITY_PRESETS, and overwriting it
1581
- // with a hardcoded value meant `medium` and `high` never took effect.
1568
+ // History from the previous mode is not comparable.
1582
1569
  this.emit( 'pipeline:historyReset' );
1583
1570
 
1584
1571
  }