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.
- package/README.md +14 -14
- package/dist/{ArchiveImporter-29Nl1567.js → ArchiveImporter-DYcRXeKE.js} +2 -2
- package/dist/{BidirectionalIntegrator-CJay8tXK.js → BidirectionalIntegrator-BvjRpbTP.js} +289 -289
- package/dist/{EngineEvents-Ci8dVWCP.js → EngineEvents-CLckhYbL.js} +1 -0
- package/dist/{NeuralRetouch-KqGVPV7u.js → NeuralRetouch-ClaMiKYg.js} +1 -1
- package/dist/{NeuralSuperRes-BHxhYzlD.js → NeuralSuperRes-WNEXun9s.js} +2 -2
- package/dist/{PathTracer-Cldt0mXi.js → PathTracer-B9wZPPVL.js} +3197 -3167
- package/dist/{PhysicalSky-CUWrTcmT.js → PhysicalSky-DcjItqKi.js} +1 -1
- package/dist/{RayzeeRenderer-Dd-Dit0v.js → RayzeeRenderer-p-NeE6E2.js} +405 -437
- 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 +5 -5
- package/dist/rayzee.es.js +1003 -1686
- package/dist/rayzee.umd.js +104 -143
- package/dist/{sceneCachePolicy--ta6moJu.js → sceneCachePolicy-B9w3BnaP.js} +2 -2
- package/package.json +2 -2
- package/src/EngineEvents.js +2 -0
- package/src/Passes/OIDNDenoiser.js +106 -100
- package/src/PathTracerApp.js +8 -19
- 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/Processor/TextureReadback.js +18 -1
- package/src/RayzeeRenderer.js +5 -5
- 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 +3 -11
- 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 +93 -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
|
@@ -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';
|
|
@@ -11,7 +11,7 @@ import { createLogger } from '../utils/Logger.js';
|
|
|
11
11
|
const neuralLog = createLogger( 'neural' );
|
|
12
12
|
import { CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
|
|
13
13
|
import {
|
|
14
|
-
DENOISER_DEFAULTS,
|
|
14
|
+
DENOISER_DEFAULTS, DENOISER_STRATEGIES, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS,
|
|
15
15
|
} from '../Stages/DenoiserSettings.js';
|
|
16
16
|
|
|
17
17
|
// A refresh slower than this is a slideshow, not a live view, so the cadence swaps to a cheaper
|
|
@@ -85,7 +85,7 @@ const historyFade = ( samples ) => Math.max( 0, 1 - samples / HISTORY_HANDOFF_SA
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* Orchestrates all denoising, post-processing, and AI upscaling:
|
|
88
|
-
* - Real-time denoiser strategy switching (
|
|
88
|
+
* - Real-time denoiser strategy switching (OIDN / NRD / None)
|
|
89
89
|
* - OIDN (offline denoise on render completion)
|
|
90
90
|
* - AI Upscaler
|
|
91
91
|
* - Auto-exposure coordination
|
|
@@ -118,7 +118,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
118
118
|
this.pipeline = pipeline;
|
|
119
119
|
|
|
120
120
|
// Stage references — only used internally for orchestration
|
|
121
|
-
this._stages = stages; // { pathTracer,
|
|
121
|
+
this._stages = stages; // { pathTracer, normalDepth, motionVector, nrd, autoExposure, compositor }
|
|
122
122
|
|
|
123
123
|
this._getExposure = getExposure;
|
|
124
124
|
this._getSaturation = getSaturation;
|
|
@@ -197,6 +197,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
197
197
|
this._denoiserEndHandler = null;
|
|
198
198
|
this._denoiserTileHandler = null;
|
|
199
199
|
this._denoiserErrorHandler = null;
|
|
200
|
+
this._denoiserLoadHandler = null;
|
|
200
201
|
this._upscalerResChangedHandler = null;
|
|
201
202
|
this._upscalerStartHandler = null;
|
|
202
203
|
this._upscalerProgressHandler = null;
|
|
@@ -348,7 +349,9 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
348
349
|
this.dispatchEvent( { type: EngineEvents.DENOISING_START, continuous: !! e.continuous } );
|
|
349
350
|
this._denoiserEndHandler = e => {
|
|
350
351
|
|
|
351
|
-
|
|
352
|
+
// A refresh that lands after the live view stopped wanting one would cover what replaced it.
|
|
353
|
+
const current = ! e.continuous || ( this.continuousDenoise && ! this._cadenceSuspended );
|
|
354
|
+
if ( this.denoiser?.hasOutput && current ) this._publishOutput();
|
|
352
355
|
this.dispatchEvent( { type: EngineEvents.DENOISING_END, continuous: !! e.continuous } );
|
|
353
356
|
|
|
354
357
|
};
|
|
@@ -377,6 +380,12 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
377
380
|
this.denoiser.addEventListener( 'tileProgress', this._denoiserTileHandler );
|
|
378
381
|
this.denoiser.addEventListener( 'error', this._denoiserErrorHandler );
|
|
379
382
|
|
|
383
|
+
// 'error' too: a failed load never sends 'loaded'. The first load starts inside the constructor, before these listen.
|
|
384
|
+
this._denoiserLoadHandler = e =>
|
|
385
|
+
this.dispatchEvent( { type: EngineEvents.DENOISER_LOADING, loading: e.type === 'loading' } );
|
|
386
|
+
for ( const type of [ 'loading', 'loaded', 'error' ] ) this.denoiser.addEventListener( type, this._denoiserLoadHandler );
|
|
387
|
+
if ( this.denoiser.state?.isLoading ) this.dispatchEvent( { type: EngineEvents.DENOISER_LOADING, loading: true } );
|
|
388
|
+
|
|
380
389
|
}
|
|
381
390
|
|
|
382
391
|
/**
|
|
@@ -440,9 +449,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
440
449
|
// ── Denoiser Strategy ─────────────────────────────────────────
|
|
441
450
|
|
|
442
451
|
/**
|
|
443
|
-
* Active real-time denoiser, derived from stage state so it can't drift from
|
|
444
|
-
*
|
|
445
|
-
* @returns {'asvgf'|'nrd'|'edgeaware'|'none'}
|
|
452
|
+
* Active real-time denoiser, derived from stage state so it can't drift from setDenoiserStrategy.
|
|
453
|
+
* @returns {'none'|'nrd'|'oidn'}
|
|
446
454
|
*/
|
|
447
455
|
get denoiserStrategy() {
|
|
448
456
|
|
|
@@ -450,105 +458,55 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
450
458
|
// belongs in the same one-of-N choice. Two of these running at once would mean paying for
|
|
451
459
|
// a per-frame denoise whose result the OIDN overlay then covers.
|
|
452
460
|
if ( this.continuousDenoise ) return 'oidn';
|
|
453
|
-
if ( this._stages.asvgf?.enabled ) return 'asvgf';
|
|
454
461
|
if ( this._stages.nrd?.enabled ) return 'nrd';
|
|
455
|
-
if ( this._stages.edgeFilter?.enabled ) return 'edgeaware';
|
|
456
462
|
return 'none';
|
|
457
463
|
|
|
458
464
|
}
|
|
459
465
|
|
|
460
466
|
/**
|
|
461
467
|
* Switches the real-time denoiser strategy.
|
|
462
|
-
* @param {string} strategy -
|
|
463
|
-
* @param {string} [preset] - quality preset
|
|
468
|
+
* @param {string} strategy - one of DENOISER_STRATEGIES; any other name is recorded and runs 'none'
|
|
469
|
+
* @param {string} [preset] - NRD quality preset (NRD_QUALITY_PRESETS)
|
|
464
470
|
*/
|
|
465
471
|
setDenoiserStrategy( strategy, preset ) {
|
|
466
472
|
|
|
467
473
|
const s = this._stages;
|
|
468
474
|
|
|
469
|
-
|
|
470
|
-
if ( s.asvgf ) s.asvgf.enabled = false;
|
|
471
|
-
if ( s.nrd ) s.nrd.enabled = false;
|
|
472
|
-
if ( s.variance ) s.variance.enabled = false;
|
|
473
|
-
if ( s.bilateralFilter ) s.bilateralFilter.enabled = false;
|
|
474
|
-
if ( s.edgeFilter ) s.edgeFilter.setFilteringEnabled( false );
|
|
475
|
-
|
|
476
|
-
this._clearDenoiserTextures();
|
|
477
|
-
|
|
478
|
-
// Which denoiser owns the live view. Deliberately says nothing about the finished image:
|
|
479
|
-
// wanting OIDN on the viewport is not the same as opting into a final pass.
|
|
480
|
-
this.setContinuousDenoise( strategy === 'oidn' );
|
|
481
|
-
|
|
482
|
-
switch ( strategy ) {
|
|
475
|
+
if ( ! DENOISER_STRATEGIES.includes( strategy ) ) {
|
|
483
476
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
break;
|
|
477
|
+
this._issues?.record(
|
|
478
|
+
ISSUE_CODES.DENOISER_UNAVAILABLE,
|
|
479
|
+
`'${strategy}' is not a live denoiser (${DENOISER_STRATEGIES.join( ', ' )}) — none is used`,
|
|
480
|
+
{ strategy }
|
|
481
|
+
);
|
|
482
|
+
strategy = 'none';
|
|
491
483
|
|
|
492
|
-
|
|
493
|
-
if ( s.nrd ) {
|
|
494
|
-
|
|
495
|
-
s.nrd.enabled = true;
|
|
496
|
-
// Stale history from the last time it ran describes another view.
|
|
497
|
-
s.nrd.resetHistory?.();
|
|
498
|
-
this._applyNRDPreset( preset || 'medium' );
|
|
499
|
-
|
|
500
|
-
}
|
|
484
|
+
}
|
|
501
485
|
|
|
502
|
-
|
|
486
|
+
// NRD reprojects through motion vectors, which mean nothing once every pixel is its own direction.
|
|
487
|
+
if ( strategy === 'nrd' && this._isPanorama() ) {
|
|
503
488
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
// stage's per-pixel variance to drive its luminance edge-stop.
|
|
507
|
-
if ( s.variance ) s.variance.enabled = true;
|
|
508
|
-
if ( s.edgeFilter ) s.edgeFilter.setFilteringEnabled( true );
|
|
509
|
-
break;
|
|
489
|
+
this._issues?.record( ISSUE_CODES.DENOISER_UNAVAILABLE, 'NRD cannot run in a panorama — oidn is used', { strategy } );
|
|
490
|
+
strategy = 'oidn';
|
|
510
491
|
|
|
511
492
|
}
|
|
512
493
|
|
|
513
|
-
this.
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Enables/disables ASVGF denoising with coordination of related stages.
|
|
519
|
-
* @param {boolean} enabled
|
|
520
|
-
* @param {string} [qualityPreset]
|
|
521
|
-
*/
|
|
522
|
-
setASVGFEnabled( enabled, qualityPreset ) {
|
|
494
|
+
this._clearDenoiserTextures();
|
|
523
495
|
|
|
524
|
-
|
|
525
|
-
if ( s.asvgf ) s.asvgf.enabled = enabled;
|
|
526
|
-
if ( s.variance ) s.variance.enabled = enabled;
|
|
527
|
-
if ( s.bilateralFilter ) s.bilateralFilter.enabled = enabled;
|
|
496
|
+
if ( s.nrd ) s.nrd.enabled = strategy === 'nrd';
|
|
528
497
|
|
|
529
|
-
if ( enabled ) {
|
|
498
|
+
if ( s.nrd?.enabled ) {
|
|
530
499
|
|
|
531
|
-
//
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
this._applyASVGFPreset( qualityPreset || 'medium' );
|
|
500
|
+
// Stale history from the last time it ran describes another view.
|
|
501
|
+
s.nrd.resetHistory?.();
|
|
502
|
+
this._applyNRDPreset( preset || 'medium' );
|
|
535
503
|
|
|
536
504
|
}
|
|
537
505
|
|
|
538
|
-
//
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
this.
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* Applies an ASVGF quality preset.
|
|
547
|
-
* @param {string} presetName - 'low' | 'medium' | 'high'
|
|
548
|
-
*/
|
|
549
|
-
applyASVGFPreset( presetName ) {
|
|
550
|
-
|
|
551
|
-
this._applyASVGFPreset( presetName );
|
|
506
|
+
// Which denoiser owns the live view. Deliberately says nothing about the finished image:
|
|
507
|
+
// wanting OIDN on the viewport is not the same as opting into a final pass. Also re-syncs
|
|
508
|
+
// the G-buffer stages, so NRD must already be set.
|
|
509
|
+
this.setContinuousDenoise( strategy === 'oidn' );
|
|
552
510
|
|
|
553
511
|
}
|
|
554
512
|
|
|
@@ -562,6 +520,13 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
562
520
|
|
|
563
521
|
}
|
|
564
522
|
|
|
523
|
+
/** True while a strategy that reprojects through motion vectors is active. */
|
|
524
|
+
get requiresMotionVectors() {
|
|
525
|
+
|
|
526
|
+
return !! this._stages.nrd?.enabled;
|
|
527
|
+
|
|
528
|
+
}
|
|
529
|
+
|
|
565
530
|
/**
|
|
566
531
|
* Gate the G-buffer stages (NormalDepth, MotionVector) on demand: they only
|
|
567
532
|
* need to run when a real-time denoiser consumes their output. Idling them
|
|
@@ -569,17 +534,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
569
534
|
* navigation and frees their textures. Call after any consumer toggle.
|
|
570
535
|
*
|
|
571
536
|
* MotionVector requires NormalDepth (reads pathtracer:normalDepth) and its
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
537
|
+
* consumer (NRD) also reads NormalDepth, so NormalDepth is always enabled
|
|
538
|
+
* whenever MotionVector is. Adaptive sampling and OIDN's own pass do NOT read
|
|
539
|
+
* these signals; only OIDN's motion history keeps NormalDepth alive.
|
|
575
540
|
*/
|
|
576
|
-
/** True while a strategy that reprojects through motion vectors is active. */
|
|
577
|
-
get requiresMotionVectors() {
|
|
578
|
-
|
|
579
|
-
return !! ( this._stages.asvgf?.enabled || this._stages.nrd?.enabled );
|
|
580
|
-
|
|
581
|
-
}
|
|
582
|
-
|
|
583
541
|
_syncGBufferStages() {
|
|
584
542
|
|
|
585
543
|
const s = this._stages;
|
|
@@ -587,8 +545,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
587
545
|
const mv = s.motionVector;
|
|
588
546
|
|
|
589
547
|
const motionNeeded = this.requiresMotionVectors;
|
|
590
|
-
// pathtracer:normalDepth consumed by
|
|
591
|
-
const normalNeeded = motionNeeded || this.historyWanted
|
|
548
|
+
// pathtracer:normalDepth consumed by NRD and the OIDN history
|
|
549
|
+
const normalNeeded = motionNeeded || this.historyWanted;
|
|
592
550
|
if ( ! this.historyWanted ) this._releaseHistory();
|
|
593
551
|
|
|
594
552
|
if ( nd ) {
|
|
@@ -605,21 +563,14 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
605
563
|
|
|
606
564
|
// On re-enable, force a camera-history reseed (matricesInitialized survives
|
|
607
565
|
// normal resets) so the first frame reports zero motion, not a spike.
|
|
608
|
-
if ( motionNeeded && ! mv.enabled )
|
|
609
|
-
|
|
610
|
-
mv.matricesInitialized = false;
|
|
611
|
-
mv.isFirstFrame = true;
|
|
612
|
-
mv.frameCount = 0;
|
|
613
|
-
|
|
614
|
-
}
|
|
566
|
+
if ( motionNeeded && ! mv.enabled ) mv.matricesInitialized = false;
|
|
615
567
|
|
|
616
568
|
mv.enabled = motionNeeded;
|
|
617
569
|
|
|
618
570
|
}
|
|
619
571
|
|
|
620
|
-
// PathTracer's aux MRT (normalDepth + albedo) is consumed by
|
|
621
|
-
//
|
|
622
|
-
// MRT read-targets directly). When none are active the wavefront skips those writes entirely.
|
|
572
|
+
// PathTracer's aux MRT (normalDepth + albedo) is consumed by NRD (albedo, hit distance) and by
|
|
573
|
+
// OIDN (reads the MRT read-targets directly). When neither is active the wavefront skips those writes.
|
|
623
574
|
s.pathTracer?.setAuxGBufferEnabled?.( normalNeeded || !! this.denoiser?.enabled );
|
|
624
575
|
|
|
625
576
|
// Clean-aux normal: temporally accumulate the aux normal only for OIDN clean-aux models
|
|
@@ -634,7 +585,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
634
585
|
// disabled (lazily re-created on the next dispatch after re-enable). Every strategy/denoiser
|
|
635
586
|
// toggle funnels through here after the enabled flags above are settled, so this is the one
|
|
636
587
|
// choke point. dispose() is idempotent, so re-running it for an already-released stage is a no-op.
|
|
637
|
-
for ( const stage of [ s.
|
|
588
|
+
for ( const stage of [ s.nrd, nd, mv ] ) {
|
|
638
589
|
|
|
639
590
|
if ( stage && ! stage.enabled ) stage.releaseGPUMemory?.();
|
|
640
591
|
|
|
@@ -842,21 +793,29 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
842
793
|
|
|
843
794
|
this._cadenceSuspended = !! suspended;
|
|
844
795
|
this._historyDirty = true;
|
|
845
|
-
if ( ! this._cadenceSuspended ) return;
|
|
846
796
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
797
|
+
if ( this._cadenceSuspended ) {
|
|
798
|
+
|
|
799
|
+
// The accumulation is what a final render shows; a held preview frame would otherwise sit
|
|
800
|
+
// over it until the render finished.
|
|
801
|
+
this.denoiser?.abort();
|
|
802
|
+
this._unpublishOutput();
|
|
803
|
+
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
this._syncGBufferStages();
|
|
851
807
|
|
|
852
808
|
}
|
|
853
809
|
|
|
854
810
|
setContinuousDenoise( enabled ) {
|
|
855
811
|
|
|
812
|
+
// Once nothing else uses OIDN it turns off, and a disabled denoiser can no longer be aborted.
|
|
813
|
+
if ( ! enabled && ! this.finalDenoise ) this.denoiser?.abort();
|
|
856
814
|
this.continuousDenoise = !! enabled;
|
|
857
815
|
this._syncOIDNInUse();
|
|
858
816
|
this._lastCadenceAt = - Infinity;
|
|
859
817
|
this._resetCadence();
|
|
818
|
+
this._syncGBufferStages();
|
|
860
819
|
|
|
861
820
|
}
|
|
862
821
|
|
|
@@ -888,19 +847,24 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
888
847
|
|
|
889
848
|
}
|
|
890
849
|
|
|
891
|
-
// Not at a size proven too slow to denoise while moving
|
|
850
|
+
// Not at a size proven too slow to denoise while moving (the raw render owns the view there), nor
|
|
851
|
+
// through a final render or a panorama, which never reproject it.
|
|
892
852
|
get historyWanted() {
|
|
893
853
|
|
|
894
|
-
return this.temporalHistory && this.continuousDenoise && !! this.denoiser?.enabled && ! this._movingHopeless
|
|
854
|
+
return this.temporalHistory && this.continuousDenoise && !! this.denoiser?.enabled && ! this._movingHopeless
|
|
855
|
+
&& ! this._cadenceSuspended && ! this._isPanorama();
|
|
895
856
|
|
|
896
857
|
}
|
|
897
858
|
|
|
898
859
|
get historyActive() {
|
|
899
860
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
861
|
+
return this.historyWanted && !! this._stages.normalDepth?.enabled && ! ( this._stages.pathTracer?.visMode?.value > 0 );
|
|
862
|
+
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
_isPanorama() {
|
|
866
|
+
|
|
867
|
+
return this._stages.pathTracer?.uniforms?.get( 'cameraProjection' )?.value === CAMERA_PROJECTION_IDS.equirectangular;
|
|
904
868
|
|
|
905
869
|
}
|
|
906
870
|
|
|
@@ -1436,7 +1400,9 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1436
1400
|
// What closes the render, if anything. A full pass at the chosen tier when that switch is
|
|
1437
1401
|
// on; otherwise, if OIDN owns the live view, one last cheap refresh — the cadence's last
|
|
1438
1402
|
// tick lands a few samples short, and the picture should match the render that finished.
|
|
1439
|
-
|
|
1403
|
+
// A final render has no live view to match, so with the switch off it stays undenoised.
|
|
1404
|
+
const live = this.continuousDenoise && ! this._cadenceSuspended;
|
|
1405
|
+
const closing = this.finalDenoise ? 'final' : live ? 'refresh' : null;
|
|
1440
1406
|
|
|
1441
1407
|
// Registered only once the closing denoise has been launched, so the next 'end' is
|
|
1442
1408
|
// unambiguously that one — a cadence run's end can look identical otherwise.
|
|
@@ -1584,6 +1550,13 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1584
1550
|
if ( this._denoiserEndHandler ) this.denoiser.removeEventListener( 'end', this._denoiserEndHandler );
|
|
1585
1551
|
if ( this._denoiserTileHandler ) this.denoiser.removeEventListener( 'tileProgress', this._denoiserTileHandler );
|
|
1586
1552
|
if ( this._denoiserErrorHandler ) this.denoiser.removeEventListener( 'error', this._denoiserErrorHandler );
|
|
1553
|
+
if ( this._denoiserLoadHandler ) {
|
|
1554
|
+
|
|
1555
|
+
for ( const type of [ 'loading', 'loaded', 'error' ] ) this.denoiser.removeEventListener( type, this._denoiserLoadHandler );
|
|
1556
|
+
if ( this.denoiser.state?.isLoading ) this.dispatchEvent( { type: EngineEvents.DENOISER_LOADING, loading: false } );
|
|
1557
|
+
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1587
1560
|
this.denoiser.dispose();
|
|
1588
1561
|
this.denoiser = null;
|
|
1589
1562
|
|
|
@@ -1608,6 +1581,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1608
1581
|
this._denoiserEndHandler = null;
|
|
1609
1582
|
this._denoiserTileHandler = null;
|
|
1610
1583
|
this._denoiserErrorHandler = null;
|
|
1584
|
+
this._denoiserLoadHandler = null;
|
|
1611
1585
|
this._upscalerResChangedHandler = null;
|
|
1612
1586
|
this._upscalerStartHandler = null;
|
|
1613
1587
|
this._upscalerProgressHandler = null;
|
|
@@ -1671,53 +1645,6 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1671
1645
|
// These methods match the DenoisingAPI surface so call sites need
|
|
1672
1646
|
// zero or minimal changes after facade removal.
|
|
1673
1647
|
|
|
1674
|
-
/** Updates ASVGF stage parameters. */
|
|
1675
|
-
setASVGFParams( params ) {
|
|
1676
|
-
|
|
1677
|
-
this._stages.asvgf?.updateParameters( params );
|
|
1678
|
-
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
/**
|
|
1682
|
-
* Toggle the ASVGF heatmap compute pass. When enabled, the stage writes
|
|
1683
|
-
* the heatmap to its public `heatmapTarget` RenderTarget — the host is
|
|
1684
|
-
* responsible for rendering it.
|
|
1685
|
-
*/
|
|
1686
|
-
toggleASVGFHeatmap( enabled ) {
|
|
1687
|
-
|
|
1688
|
-
this._stages.asvgf?.setHeatmapEnabled?.( enabled );
|
|
1689
|
-
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
/**
|
|
1693
|
-
* Configures ASVGF for a specific render mode (multi-stage coordination).
|
|
1694
|
-
* @param {Object} config - { enabled, temporalAlpha, atrousIterations, ... }
|
|
1695
|
-
*/
|
|
1696
|
-
configureASVGFForMode( config ) {
|
|
1697
|
-
|
|
1698
|
-
if ( ! this._stages.asvgf ) return;
|
|
1699
|
-
|
|
1700
|
-
this._stages.asvgf.enabled = config.enabled;
|
|
1701
|
-
if ( this._stages.variance ) this._stages.variance.enabled = config.enabled;
|
|
1702
|
-
if ( this._stages.bilateralFilter ) this._stages.bilateralFilter.enabled = config.enabled;
|
|
1703
|
-
|
|
1704
|
-
if ( config.enabled ) {
|
|
1705
|
-
|
|
1706
|
-
this._stages.asvgf.updateParameters( config );
|
|
1707
|
-
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
this._syncGBufferStages();
|
|
1711
|
-
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
/** Updates edge-aware filtering parameters. */
|
|
1715
|
-
setEdgeAwareParams( params ) {
|
|
1716
|
-
|
|
1717
|
-
this._stages.edgeFilter?.updateUniforms( params );
|
|
1718
|
-
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
1648
|
/** Updates auto-exposure stage parameters. */
|
|
1722
1649
|
setAutoExposureParams( params ) {
|
|
1723
1650
|
|
|
@@ -1835,7 +1762,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1835
1762
|
|
|
1836
1763
|
/**
|
|
1837
1764
|
* Switches strategy with automatic reset (convenience wrapper).
|
|
1838
|
-
* @param {'none'|'
|
|
1765
|
+
* @param {'none'|'nrd'|'oidn'} strategy
|
|
1839
1766
|
* @param {string} [preset]
|
|
1840
1767
|
*/
|
|
1841
1768
|
setStrategy( strategy, preset ) {
|
|
@@ -1904,12 +1831,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1904
1831
|
const ctx = this.pipeline?.context;
|
|
1905
1832
|
if ( ! ctx ) return;
|
|
1906
1833
|
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
'variance:output', 'bilateralFiltering:output',
|
|
1910
|
-
'edgeFiltering:output', 'nrd:output', 'oidn:output',
|
|
1911
|
-
];
|
|
1912
|
-
keys.forEach( k => ctx.removeTexture( k ) );
|
|
1834
|
+
ctx.removeTexture( 'nrd:output' );
|
|
1835
|
+
ctx.removeTexture( 'oidn:output' );
|
|
1913
1836
|
|
|
1914
1837
|
}
|
|
1915
1838
|
|
|
@@ -1924,21 +1847,4 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1924
1847
|
|
|
1925
1848
|
}
|
|
1926
1849
|
|
|
1927
|
-
_applyASVGFPreset( presetName ) {
|
|
1928
|
-
|
|
1929
|
-
const preset = ASVGF_QUALITY_PRESETS[ presetName ];
|
|
1930
|
-
if ( ! preset ) return;
|
|
1931
|
-
// ASVGF consumes temporalAlpha / gradientStrength / maxAccumFrames.
|
|
1932
|
-
// BilateralFilter consumes phi* edge-stopping params and atrousIterations.
|
|
1933
|
-
// Variance consumes varianceBoost. Each stage cherry-picks what it needs.
|
|
1934
|
-
this._stages.asvgf?.updateParameters( preset );
|
|
1935
|
-
this._stages.bilateralFilter?.updateParameters( preset );
|
|
1936
|
-
if ( this._stages.variance && preset.varianceBoost !== undefined ) {
|
|
1937
|
-
|
|
1938
|
-
this._stages.variance.varianceBoost.value = preset.varianceBoost;
|
|
1939
|
-
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
}
|
|
1943
|
-
|
|
1944
1850
|
}
|