rayzee 11.6.0 → 11.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -14
- package/dist/{ArchiveImporter-iofnC8zB.js → ArchiveImporter-DYcRXeKE.js} +2 -2
- package/dist/{BidirectionalIntegrator-CqAilt2I.js → BidirectionalIntegrator-BvjRpbTP.js} +2 -2
- package/dist/{NeuralRetouch-DFI4pOjO.js → NeuralRetouch-ClaMiKYg.js} +1 -1
- package/dist/{NeuralSuperRes-ChCiiilS.js → NeuralSuperRes-WNEXun9s.js} +1 -1
- package/dist/{PathTracer-BX4sJLx7.js → PathTracer-B9wZPPVL.js} +6 -8
- package/dist/{PhysicalSky-Biv39eTA.js → PhysicalSky-DcjItqKi.js} +1 -1
- package/dist/{RayzeeRenderer-D8XRKU3A.js → RayzeeRenderer-p-NeE6E2.js} +2 -3
- package/dist/addons/archives.es.js +1 -1
- package/dist/addons/bidirectional.es.js +1 -1
- package/dist/addons/physical-sky.es.js +1 -1
- package/dist/rayzee-core.es.js +3 -3
- package/dist/rayzee.es.js +865 -1591
- package/dist/rayzee.umd.js +87 -134
- package/dist/{sceneCachePolicy-W5PLbzuR.js → sceneCachePolicy-B9w3BnaP.js} +1 -1
- package/package.json +1 -1
- package/src/Passes/OIDNDenoiser.js +9 -11
- package/src/PathTracerApp.js +6 -17
- package/src/Pipeline/BindingAudit.js +2 -3
- package/src/Pipeline/PipelineContext.js +0 -1
- package/src/Pipeline/RenderPipeline.js +1 -2
- package/src/Pipeline/RenderStage.js +1 -1
- package/src/Processor/PackedRayBuffer.js +2 -2
- package/src/Processor/StorageTexturePool.js +1 -1
- package/src/RayzeeRenderer.js +1 -1
- package/src/Stages/DenoiserSettings.js +5 -81
- package/src/Stages/MotionVector.js +21 -215
- package/src/Stages/NormalDepth.js +5 -5
- package/src/Stages/PathTracer.js +1 -1
- package/src/Stages/PathTracerStage.js +4 -17
- package/src/TSL/FinalWriteKernel.js +2 -2
- package/src/TSL/ShadeKernel.js +3 -3
- package/src/index.js +1 -1
- package/src/managers/DenoisingManager.js +78 -187
- package/src/Stages/ASVGF.js +0 -932
- package/src/Stages/BilateralFilter.js +0 -486
- package/src/Stages/EdgeFilter.js +0 -545
- package/src/Stages/Variance.js +0 -443
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, vec2,
|
|
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
|
|
13
|
-
*
|
|
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.
|
|
16
|
+
* 1. A compute shader writes to a StorageTexture via textureStore
|
|
18
17
|
* 2. After dispatch, copyTextureToTexture transfers StorageTexture → RenderTarget
|
|
19
|
-
* 3. RenderTarget
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
114
|
-
|
|
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 (
|
|
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
|
-
//
|
|
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;
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
533
|
-
|
|
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
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|
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();
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -77,7 +77,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
77
77
|
this._auxGBufferUniform = uniform( 0, 'uint' );
|
|
78
78
|
// Clean-aux mode: temporally accumulate + renormalize the aux NORMAL (like albedo) so a clean-aux
|
|
79
79
|
// OIDN model (calb_cnrm/high, alb_nrm/balanced) gets the prefiltered-ish normal it expects instead
|
|
80
|
-
// of the point-sampled per-frame value that leaks noise. Off
|
|
80
|
+
// of the point-sampled per-frame value that leaks noise. Off otherwise: the fast model wants the bump normal.
|
|
81
81
|
this._cleanAuxNormalEnabled = false;
|
|
82
82
|
this._cleanAuxNormalUniform = uniform( 0, 'uint' );
|
|
83
83
|
// Aux MRT accumulates on an epoch of its own: enabling the denoiser at colour frame N must not leave
|
|
@@ -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
|
-
//
|
|
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
|
-
// =====
|
|
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.
|
|
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
|
}
|
|
@@ -48,7 +48,7 @@ export function buildFinalWriteKernel( params ) {
|
|
|
48
48
|
// on): when off, skip the G-buffer decode, the prev-frame aux mix, and the two aux stores.
|
|
49
49
|
auxGBufferEnabled, gBuffer = true, // compiled in only when requested (PathTracer.requestOutput( 'gBuffer' ))
|
|
50
50
|
// Clean-aux normal (1 = temporally accumulate + renormalize the aux normal). On only for clean-aux
|
|
51
|
-
// OIDN models (calb_cnrm/high, alb_nrm/balanced); off
|
|
51
|
+
// OIDN models (calb_cnrm/high, alb_nrm/balanced); off otherwise, as the fast model wants the bump normal.
|
|
52
52
|
cleanAuxNormalEnabled,
|
|
53
53
|
// Tier-1 convergence early-stop: per-pixel Welford luminance second moment + converged bit (stamped
|
|
54
54
|
// into streak bit 31; counted with 3×3 erosion by countConvergedDilated at the next frame start).
|
|
@@ -126,7 +126,7 @@ export function buildFinalWriteKernel( params ) {
|
|
|
126
126
|
finalHitDist.assign( mix( prevAlbedoSample.w, finalHitDist, auxAccumulationAlpha ) );
|
|
127
127
|
|
|
128
128
|
// NORMAL: by default keep this frame's POINT-SAMPLED normal — it varies with the bump,
|
|
129
|
-
// which fast
|
|
129
|
+
// which the fast model wants to preserve edge detail. But a CLEAN-AUX OIDN model (calb_cnrm/high,
|
|
130
130
|
// alb_nrm/balanced) trusts the aux and is fed per-frame point-sampled NOISE → leaked noise
|
|
131
131
|
// (high) / over-smoothing (balanced). When cleanAuxNormalOn, temporally accumulate it like
|
|
132
132
|
// the colour. Average the RAW unit normals (decode 0.5+0.5 → [-1,1]) and RENORMALIZE — a
|
package/src/TSL/ShadeKernel.js
CHANGED
|
@@ -125,7 +125,7 @@ export function buildShadeKernel( params ) {
|
|
|
125
125
|
emissiveBoost, enableEmissiveTriangleSampling,
|
|
126
126
|
lightBVHNodeCount, reverseMapVec4Offset,
|
|
127
127
|
maxRayCount,
|
|
128
|
-
// Aux G-buffer (normal/depth/albedo
|
|
128
|
+
// Aux G-buffer (normal/depth/albedo) feeds only the denoiser/OIDN MRT. Compiled in only when
|
|
129
129
|
// requested (PathTracer.requestOutput( 'gBuffer' )); then a live uniform (1 = denoiser on) skips the writes
|
|
130
130
|
// while nothing consumes them.
|
|
131
131
|
auxGBufferEnabled, gBuffer = true,
|
|
@@ -289,7 +289,7 @@ export function buildShadeKernel( params ) {
|
|
|
289
289
|
// This bounce's block of sampler dimensions — see the budget table in Random.js.
|
|
290
290
|
const dimBase = int( currentBounce ).mul( int( SAMPLER_DIMS_PER_BOUNCE ) );
|
|
291
291
|
|
|
292
|
-
// DDFA see-through aux tint carried across smooth glass/mirror; committed into the OIDN/
|
|
292
|
+
// DDFA see-through aux tint carried across smooth glass/mirror; committed into the OIDN/NRD
|
|
293
293
|
// albedo guide at the first diffuse-enough surface (or env). Mutated locally, persisted on every
|
|
294
294
|
// deferring continue (a missed persist = stale tint). 1 = untinted (direct hit → today's albedo).
|
|
295
295
|
const featCarry = aux ? readFeatureThroughput( rayBufferRW, rayID ).toVar() : null;
|
|
@@ -424,7 +424,7 @@ export function buildShadeKernel( params ) {
|
|
|
424
424
|
|
|
425
425
|
// The catcher is a real ground surface for the denoiser — write the plane's normal/depth
|
|
426
426
|
// + a neutral albedo (black albedo would break OIDN demodulation) and mark the pixel a
|
|
427
|
-
// valid surface, so OIDN/
|
|
427
|
+
// valid surface, so OIDN/NRD don't smear the caught shadow as a background miss.
|
|
428
428
|
if ( aux ) If( aux, () => {
|
|
429
429
|
|
|
430
430
|
const planeDepth = computeNDCDepth( { worldPos: planePoint, cameraProjectionMatrix, cameraViewMatrix } );
|
package/src/index.js
CHANGED
|
@@ -63,7 +63,7 @@ export {
|
|
|
63
63
|
} from './EngineDefaults.js';
|
|
64
64
|
export { MAX_RESERVABLE_RENDER_SIZE } from './Processor/StorageTexturePool.js';
|
|
65
65
|
export {
|
|
66
|
-
DENOISER_DEFAULTS,
|
|
66
|
+
DENOISER_DEFAULTS, DENOISER_STRATEGIES, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS, NRD_HIT_DIST_A,
|
|
67
67
|
NRD_HIT_DIST_B,
|
|
68
68
|
} from './Stages/DenoiserSettings.js';
|
|
69
69
|
export { SKY_DEFAULTS, DEFAULT_SUN_PATH } from './managers/EnvironmentManager.js';
|