rayzee 7.29.0 → 7.30.1
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/dist/rayzee.es.js +3724 -3009
- package/dist/rayzee.umd.js +56 -56
- package/package.json +1 -1
- package/src/EngineDefaults.js +272 -19
- package/src/EngineIssues.js +1 -0
- package/src/PathTracerApp.js +10 -1
- package/src/Processor/GeometryExtractor.js +21 -3
- package/src/Processor/PackedRayBuffer.js +26 -5
- package/src/Processor/SceneProcessor.js +71 -24
- package/src/Processor/StorageTexturePool.js +8 -4
- package/src/Processor/TextureCreator.js +8 -9
- package/src/Processor/Workers/TexturesWorker.js +13 -14
- package/src/Processor/utils.js +0 -6
- package/src/RenderSettings.js +4 -4
- package/src/Stages/Compositor.js +1 -0
- package/src/Stages/NRD.js +1395 -0
- package/src/Stages/NormalDepth.js +8 -4
- package/src/Stages/PathTracerStage.js +4 -0
- package/src/TSL/Common.js +8 -1
- package/src/TSL/FinalWriteKernel.js +9 -4
- package/src/TSL/GenerateKernel.js +1 -1
- package/src/TSL/ShadeKernel.js +14 -2
- package/src/TSL/TextureSampling.js +3 -2
- package/src/index.js +5 -0
- package/src/managers/DenoisingManager.js +80 -21
|
@@ -8,7 +8,7 @@ import { Ray, HitInfo, RayTracingMaterial, UVCache } from '../TSL/Struct.js';
|
|
|
8
8
|
import { traverseBVH } from '../TSL/BVHTraversal.js';
|
|
9
9
|
import { cameraRayDirection } from '../TSL/CameraRay.js';
|
|
10
10
|
import { getMaterial } from '../TSL/Common.js';
|
|
11
|
-
import { computeUVCache, processNormal, processBump, triangleUVTangent, buildBucketTextureNodes, refreshBucketTextureNodes, setMaterialBucketTextures } from '../TSL/TextureSampling.js';
|
|
11
|
+
import { computeUVCache, processNormal, processBump, processMetalnessRoughness, triangleUVTangent, buildBucketTextureNodes, refreshBucketTextureNodes, setMaterialBucketTextures } from '../TSL/TextureSampling.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* NormalDepth — primary-ray G-buffer for SVGF gates.
|
|
@@ -29,9 +29,10 @@ import { computeUVCache, processNormal, processBump, triangleUVTangent, buildBuc
|
|
|
29
29
|
* map, recomputed from the SAME deterministic hit — no extra ray) so the
|
|
30
30
|
* spatial denoiser's edge-stop can see normal-map detail the flat geometric
|
|
31
31
|
* normal hides. Deterministic ⇒ jitter-free, so it's safe for the gates.
|
|
32
|
+
* Its .w is the sampled material roughness (NRD's normal+roughness guide).
|
|
32
33
|
*
|
|
33
34
|
* Publishes: pathtracer:normalDepth, pathtracer:prevNormalDepth,
|
|
34
|
-
* pathtracer:shadingNormal
|
|
35
|
+
* pathtracer:shadingNormal (rgb = shading normal·0.5+0.5, a = roughness)
|
|
35
36
|
*/
|
|
36
37
|
export class NormalDepth extends RenderStage {
|
|
37
38
|
|
|
@@ -257,6 +258,7 @@ export class NormalDepth extends RenderStage {
|
|
|
257
258
|
// Shading normal: perturb the geometric normal by the normal/bump map
|
|
258
259
|
// from the SAME hit (deterministic UV → jitter-free). Miss → geo default.
|
|
259
260
|
const shadingNormal = hit.normal.toVar();
|
|
261
|
+
const roughness = float( 1.0 ).toVar();
|
|
260
262
|
If( hit.didHit, () => {
|
|
261
263
|
|
|
262
264
|
const material = RayTracingMaterial.wrap(
|
|
@@ -274,12 +276,14 @@ export class NormalDepth extends RenderStage {
|
|
|
274
276
|
|
|
275
277
|
const mapped = processNormal( hit.normal, material, uvCache, uvTangent ).toVar();
|
|
276
278
|
shadingNormal.assign( processBump( mapped, material, uvCache ) );
|
|
279
|
+
// Same floor the Shade kernel applies before shading.
|
|
280
|
+
roughness.assign( processMetalnessRoughness( material, uvCache ).y.clamp( 0.05, 1.0 ) );
|
|
277
281
|
|
|
278
282
|
} );
|
|
279
283
|
|
|
280
284
|
const shadingResult = hit.didHit.select(
|
|
281
|
-
vec4( shadingNormal.mul( 0.5 ).add( 0.5 ),
|
|
282
|
-
vec4( 0.0, 0.0, 0.0,
|
|
285
|
+
vec4( shadingNormal.mul( 0.5 ).add( 0.5 ), roughness ),
|
|
286
|
+
vec4( 0.0, 0.0, 0.0, 1.0 )
|
|
283
287
|
);
|
|
284
288
|
|
|
285
289
|
textureStore(
|
|
@@ -1157,6 +1157,9 @@ export class PathTracerStage extends RenderStage {
|
|
|
1157
1157
|
context.setTexture( 'pathtracer:normalDepth', writeTex.normalDepth );
|
|
1158
1158
|
context.setTexture( 'pathtracer:albedo', writeTex.albedo );
|
|
1159
1159
|
|
|
1160
|
+
// Not the same as the context's own `accumulatedFrames`, which counts pipeline renders — this
|
|
1161
|
+
// freezes when the frame retires or the camera moves, which is what a denoiser needs.
|
|
1162
|
+
context.setState( 'pathtracer:samples', this.frameCount );
|
|
1160
1163
|
context.setState( 'interactionMode', this.cameraOptimizer?.isInInteractionMode() ?? false );
|
|
1161
1164
|
context.setState( 'renderMode', this.renderMode.value );
|
|
1162
1165
|
|
|
@@ -1224,6 +1227,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
1224
1227
|
// touched here — it is owned by ASVGF_QUALITY_PRESETS, and overwriting it
|
|
1225
1228
|
// with a hardcoded value meant `medium` and `high` never took effect.
|
|
1226
1229
|
this.emit( 'asvgf:reset' );
|
|
1230
|
+
this.emit( 'denoiser:reset' );
|
|
1227
1231
|
|
|
1228
1232
|
}
|
|
1229
1233
|
|
package/src/TSL/Common.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, wgslFn, float, vec2, vec3, vec4, int, mat3, If, max, dot, clamp } from 'three/tsl';
|
|
1
|
+
import { Fn, wgslFn, float, vec2, vec3, vec4, int, mat3, If, max, dot, clamp, select } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
AnisoFrame,
|
|
@@ -17,6 +17,13 @@ export const MIN_CLEARCOAT_ROUGHNESS = 0.089;
|
|
|
17
17
|
export const MAX_ROUGHNESS = 1.0;
|
|
18
18
|
export const MIN_PDF = 0.001;
|
|
19
19
|
export const REC709_LUMINANCE_COEFFICIENTS = vec3( 0.2126, 0.7152, 0.0722 );
|
|
20
|
+
|
|
21
|
+
export const FP16_MAX = 65504.0;
|
|
22
|
+
|
|
23
|
+
// NaN/±Inf guard: mix() propagates NaN, so one firefly would poison a temporal history forever.
|
|
24
|
+
export const sanitize1 = ( x, ceiling = 1e7 ) => select( x.equal( x ), x, float( 0.0 ) ).clamp( 0.0, ceiling );
|
|
25
|
+
export const sanitizeRGB = ( c, ceiling = 1e7 ) =>
|
|
26
|
+
vec3( sanitize1( c.x, ceiling ), sanitize1( c.y, ceiling ), sanitize1( c.z, ceiling ) );
|
|
20
27
|
import { MATERIAL_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
21
28
|
|
|
22
29
|
export const MATERIAL_SLOTS = MATERIAL_DATA_LAYOUT.SLOTS_PER_MATERIAL;
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from 'three/tsl';
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
|
-
readRayRadiance, readGBuffer, gbDecodeNormalDepth, gbDecodeAlbedo,
|
|
17
|
+
readRayRadiance, readGBuffer, gbDecodeNormalDepth, gbDecodeAlbedo, gbDecodeHitDist,
|
|
18
18
|
} from '../Processor/PackedRayBuffer.js';
|
|
19
19
|
import { luminance } from './Common.js';
|
|
20
20
|
|
|
@@ -96,11 +96,14 @@ export function buildFinalWriteKernel( params ) {
|
|
|
96
96
|
// auxOn gates the decode + stores so a no-denoiser frame does no G-buffer read and no aux writes.
|
|
97
97
|
const finalNormalDepth = vec4( 0.0 ).toVar();
|
|
98
98
|
const finalAlbedo = vec4( 0.0 ).xyz.toVar();
|
|
99
|
+
// Albedo .w carries the hit distance — OIDN reads albedo as 3 channels, so it is free.
|
|
100
|
+
const finalHitDist = float( 0.0 ).toVar();
|
|
99
101
|
If( auxOn, () => {
|
|
100
102
|
|
|
101
103
|
const gbuf = readGBuffer( gBufferRO, rayID );
|
|
102
104
|
finalNormalDepth.assign( gbDecodeNormalDepth( gbuf ) );
|
|
103
105
|
finalAlbedo.assign( vec4( gbDecodeAlbedo( gbuf ), 0.0 ).xyz );
|
|
106
|
+
finalHitDist.assign( gbDecodeHitDist( gbuf ) );
|
|
104
107
|
|
|
105
108
|
} );
|
|
106
109
|
|
|
@@ -117,8 +120,10 @@ export function buildFinalWriteKernel( params ) {
|
|
|
117
120
|
finalColor.assign( select( wasFrozen, prevAccumSample.xyz, mix( prevAccumSample.xyz, sampleColor.xyz, accumulationAlpha ) ) );
|
|
118
121
|
If( auxOn.and( hasPreviousAux ), () => {
|
|
119
122
|
|
|
120
|
-
// Albedo averages cleanly (it's a colour).
|
|
121
|
-
|
|
123
|
+
// Albedo averages cleanly (it's a colour); so does the normalized hit distance in .w.
|
|
124
|
+
const prevAlbedoSample = texture( prevAlbedoTexture, prevUV, 0 ).toVar();
|
|
125
|
+
finalAlbedo.assign( mix( prevAlbedoSample.xyz, finalAlbedo, auxAccumulationAlpha ) );
|
|
126
|
+
finalHitDist.assign( mix( prevAlbedoSample.w, finalHitDist, auxAccumulationAlpha ) );
|
|
122
127
|
|
|
123
128
|
// NORMAL: by default keep this frame's POINT-SAMPLED normal — it varies with the bump,
|
|
124
129
|
// which fast/ASVGF want to preserve edge detail. But a CLEAN-AUX OIDN model (calb_cnrm/high,
|
|
@@ -251,7 +256,7 @@ export function buildFinalWriteKernel( params ) {
|
|
|
251
256
|
If( auxOn, () => {
|
|
252
257
|
|
|
253
258
|
textureStore( writeNDTex, uintCoord, finalNormalDepth ).toWriteOnly();
|
|
254
|
-
textureStore( writeAlbedoTex, uintCoord, vec4( finalAlbedo,
|
|
259
|
+
textureStore( writeAlbedoTex, uintCoord, vec4( finalAlbedo, finalHitDist ) ).toWriteOnly();
|
|
255
260
|
|
|
256
261
|
} );
|
|
257
262
|
|
|
@@ -96,7 +96,7 @@ export function buildGenerateKernel( params ) {
|
|
|
96
96
|
If( auxOn, () => {
|
|
97
97
|
|
|
98
98
|
// default: normal +Z, depth 1 (far), black albedo (background/miss). Per-CHUNK G-buffer → LOCAL slot.
|
|
99
|
-
writeGBuffer( gBufferRW, rayID, vec3( 0.0, 0.0, 1.0 ), float( 1.0 ), vec3( 0.0 ) );
|
|
99
|
+
writeGBuffer( gBufferRW, rayID, vec3( 0.0, 0.0, 1.0 ), float( 1.0 ), vec3( 0.0 ), float( 0.0 ) );
|
|
100
100
|
// DDFA: seed the see-through aux tint to white (no tint yet). RMW preserves slot-5 xyz (sigmaA).
|
|
101
101
|
writeFeatureThroughput( rayBufferRW, rayID, vec3( 1.0 ) );
|
|
102
102
|
|
package/src/TSL/ShadeKernel.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import {
|
|
8
8
|
Fn, float, vec2, vec3, vec4, int, uint,
|
|
9
9
|
bool as tslBool,
|
|
10
|
-
If, Loop, normalize, max, exp, log, clamp, dot, length, select, smoothstep, mix,
|
|
10
|
+
If, Loop, normalize, max, min, exp, log, clamp, dot, length, select, smoothstep, mix,
|
|
11
11
|
instanceIndex,
|
|
12
12
|
sampler,
|
|
13
13
|
atomicAdd, atomicLoad, atomicStore, uintBitsToFloat,
|
|
@@ -29,6 +29,7 @@ import { IndirectLightingResult, sampleCone } from './LightsCore.js';
|
|
|
29
29
|
import { regularizePathContribution, generateSampledDirection, computeNDCDepth, handleRussianRoulette } from './PathTracerCore.js';
|
|
30
30
|
import { evaluateDFG } from './MaterialProperties.js';
|
|
31
31
|
import { dielectricF0 } from './Fresnel.js';
|
|
32
|
+
import { NRD_HIT_DIST_A, NRD_HIT_DIST_B } from '../EngineDefaults.js';
|
|
32
33
|
import { sampleClearcoat, ClearcoatResult } from './Clearcoat.js';
|
|
33
34
|
import { refineDisplacedIntersection, DisplacementResult } from './Displacement.js';
|
|
34
35
|
import { calculateEmissiveTriangleContribution, calculateEmissiveLightPdf, EmissiveSample } from './EmissiveSampling.js';
|
|
@@ -57,7 +58,7 @@ import {
|
|
|
57
58
|
readHitDistance, readHitBarycentrics, readHitNormal,
|
|
58
59
|
readHitMaterialIndex, readHitTriangleIndex,
|
|
59
60
|
writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf, writeRayRadiance,
|
|
60
|
-
writeGBuffer, readGBuffer, gbDecodeNormalDepth,
|
|
61
|
+
writeGBuffer, writeGBufferHitDist, readGBuffer, gbDecodeNormalDepth,
|
|
61
62
|
readRayRadiance,
|
|
62
63
|
readFeatureThroughput, writeFeatureThroughput,
|
|
63
64
|
} from '../Processor/PackedRayBuffer.js';
|
|
@@ -226,6 +227,17 @@ export function buildShadeKernel( params ) {
|
|
|
226
227
|
const sssSteps = readSssSteps( rayBufferRW, rayID ).toVar();
|
|
227
228
|
const transparentCount = readTransparentCount( rayBufferRW, rayID ).toVar();
|
|
228
229
|
|
|
230
|
+
// NRD guide: first segment after the primary scatter, plus any alpha-skip run so a cutout hole
|
|
231
|
+
// doesn't shorten it. Unconditional at depth 1, so the post-skip segment wins.
|
|
232
|
+
If( auxOn.and( cameraDepth.equal( 1 ) ), () => {
|
|
233
|
+
|
|
234
|
+
const scatterViewZ = cameraViewMatrix.mul( vec4( origin, 1.0 ) ).z.abs();
|
|
235
|
+
const norm = scatterViewZ.mul( NRD_HIT_DIST_B ).add( NRD_HIT_DIST_A );
|
|
236
|
+
const total = readMisRayT( rayBufferRW, rayID ).add( min( hitDist, float( 1e6 ) ) );
|
|
237
|
+
writeGBufferHitDist( gBufferRW, pixelIndex, total.div( max( norm, float( 1e-4 ) ) ).clamp( 0.0, 1.0 ) );
|
|
238
|
+
|
|
239
|
+
} );
|
|
240
|
+
|
|
229
241
|
// ── Analytic ground-plane shadow catcher (primary ray only, no geometry) ──
|
|
230
242
|
// A horizontal plane at y = groundCatcherHeight. For a bounce-0 ray that crosses it
|
|
231
243
|
// closer than any BVH hit (hitDist is MISS_DIST on sky rays, so the plane wins over the
|
|
@@ -10,11 +10,12 @@ import { getDatafromStorageBuffer } from './Common.js';
|
|
|
10
10
|
import { TEXTURE_CONSTANTS } from '../EngineDefaults.js';
|
|
11
11
|
|
|
12
12
|
// ================================================================================
|
|
13
|
-
// CONSOLIDATED
|
|
13
|
+
// CONSOLIDATED SHAPE-BUCKETED MATERIAL TEXTURES
|
|
14
14
|
// ================================================================================
|
|
15
15
|
// Material maps are packed into two colorSpace pools (sRGB: albedo+emissive; linear:
|
|
16
16
|
// normal/bump/roughness/metalness/displacement), each split into MATERIAL_BUCKET_COUNT
|
|
17
|
-
//
|
|
17
|
+
// (width, height) buckets so a small or oddly-shaped map no longer pays a large neighbour's
|
|
18
|
+
// footprint. Bucket shapes are planned per scene (planTextureBuckets) — not powers of two.
|
|
18
19
|
// A map's stored index encodes (bucket, layer) as bucket * BUCKET_LAYER_STRIDE + layer.
|
|
19
20
|
//
|
|
20
21
|
// The bucket texture nodes live at module level (same pattern as gobo/IES/shadowAlbedo):
|
package/src/index.js
CHANGED
|
@@ -39,6 +39,11 @@ export { Logger, createLogger, fmt, LOG_LEVELS } from './utils/Logger.js';
|
|
|
39
39
|
export {
|
|
40
40
|
ENGINE_DEFAULTS,
|
|
41
41
|
ASVGF_QUALITY_PRESETS,
|
|
42
|
+
NRD_DEFAULTS,
|
|
43
|
+
NRD_QUALITY_PRESETS,
|
|
44
|
+
NRD_PRESET_KEYS,
|
|
45
|
+
NRD_HIT_DIST_A,
|
|
46
|
+
NRD_HIT_DIST_B,
|
|
42
47
|
CAMERA_PRESETS,
|
|
43
48
|
CAMERA_RANGES,
|
|
44
49
|
SKY_PRESETS,
|
|
@@ -2,11 +2,11 @@ import { EventDispatcher } from 'three';
|
|
|
2
2
|
import { OIDNDenoiser } from '../Passes/OIDNDenoiser.js';
|
|
3
3
|
import { AIUpscaler } from '../Passes/AIUpscaler.js';
|
|
4
4
|
import { EngineEvents } from '../EngineEvents.js';
|
|
5
|
-
import { ENGINE_DEFAULTS as DEFAULT_STATE, ASVGF_QUALITY_PRESETS } from '../EngineDefaults.js';
|
|
5
|
+
import { ENGINE_DEFAULTS as DEFAULT_STATE, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS } from '../EngineDefaults.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Orchestrates all denoising, post-processing, and AI upscaling:
|
|
9
|
-
* - Real-time denoiser strategy switching (ASVGF / EdgeAware / None)
|
|
9
|
+
* - Real-time denoiser strategy switching (ASVGF / NRD / EdgeAware / None)
|
|
10
10
|
* - OIDN (offline denoise on render completion)
|
|
11
11
|
* - AI Upscaler
|
|
12
12
|
* - Auto-exposure coordination
|
|
@@ -41,7 +41,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
41
41
|
this.pipeline = pipeline;
|
|
42
42
|
|
|
43
43
|
// Stage references — only used internally for orchestration
|
|
44
|
-
this._stages = stages; // { pathTracer, asvgf, variance, bilateralFilter, edgeFilter, autoExposure, compositor }
|
|
44
|
+
this._stages = stages; // { pathTracer, asvgf, nrd, variance, bilateralFilter, edgeFilter, autoExposure, compositor }
|
|
45
45
|
|
|
46
46
|
this._getExposure = getExposure;
|
|
47
47
|
this._getSaturation = getSaturation;
|
|
@@ -237,30 +237,32 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
237
237
|
|
|
238
238
|
// ── Denoiser Strategy ─────────────────────────────────────────
|
|
239
239
|
|
|
240
|
-
/**
|
|
241
|
-
* Switches the real-time denoiser strategy.
|
|
242
|
-
* @param {string} strategy - 'none' | 'asvgf' | 'edgeaware'
|
|
243
|
-
* @param {string} [asvgfPreset] - ASVGF quality preset when strategy is 'asvgf'
|
|
244
|
-
*/
|
|
245
240
|
/**
|
|
246
241
|
* Active real-time denoiser, derived from stage state so it can't drift from
|
|
247
242
|
* setDenoiserStrategy / setASVGFEnabled.
|
|
248
|
-
* @returns {'asvgf'|'edgeaware'|'none'}
|
|
243
|
+
* @returns {'asvgf'|'nrd'|'edgeaware'|'none'}
|
|
249
244
|
*/
|
|
250
245
|
get denoiserStrategy() {
|
|
251
246
|
|
|
252
247
|
if ( this._stages.asvgf?.enabled ) return 'asvgf';
|
|
248
|
+
if ( this._stages.nrd?.enabled ) return 'nrd';
|
|
253
249
|
if ( this._stages.edgeFilter?.enabled ) return 'edgeaware';
|
|
254
250
|
return 'none';
|
|
255
251
|
|
|
256
252
|
}
|
|
257
253
|
|
|
258
|
-
|
|
254
|
+
/**
|
|
255
|
+
* Switches the real-time denoiser strategy.
|
|
256
|
+
* @param {string} strategy - 'none' | 'asvgf' | 'nrd' | 'edgeaware'
|
|
257
|
+
* @param {string} [preset] - quality preset for 'asvgf' (ASVGF_QUALITY_PRESETS) or 'nrd' (NRD_QUALITY_PRESETS)
|
|
258
|
+
*/
|
|
259
|
+
setDenoiserStrategy( strategy, preset ) {
|
|
259
260
|
|
|
260
261
|
const s = this._stages;
|
|
261
262
|
|
|
262
263
|
// Disable all real-time denoisers first
|
|
263
264
|
if ( s.asvgf ) s.asvgf.enabled = false;
|
|
265
|
+
if ( s.nrd ) s.nrd.enabled = false;
|
|
264
266
|
if ( s.variance ) s.variance.enabled = false;
|
|
265
267
|
if ( s.bilateralFilter ) s.bilateralFilter.enabled = false;
|
|
266
268
|
if ( s.edgeFilter ) s.edgeFilter.setFilteringEnabled( false );
|
|
@@ -274,7 +276,19 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
274
276
|
if ( s.variance ) s.variance.enabled = true;
|
|
275
277
|
if ( s.bilateralFilter ) s.bilateralFilter.enabled = true;
|
|
276
278
|
s.asvgf.setTemporalEnabled?.( true );
|
|
277
|
-
this._applyASVGFPreset(
|
|
279
|
+
this._applyASVGFPreset( preset || 'medium' );
|
|
280
|
+
break;
|
|
281
|
+
|
|
282
|
+
case 'nrd':
|
|
283
|
+
if ( s.nrd ) {
|
|
284
|
+
|
|
285
|
+
s.nrd.enabled = true;
|
|
286
|
+
// Stale history from the last time it ran describes another view.
|
|
287
|
+
s.nrd.resetHistory?.();
|
|
288
|
+
this._applyNRDPreset( preset || 'medium' );
|
|
289
|
+
|
|
290
|
+
}
|
|
291
|
+
|
|
278
292
|
break;
|
|
279
293
|
|
|
280
294
|
case 'edgeaware':
|
|
@@ -304,6 +318,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
304
318
|
|
|
305
319
|
if ( enabled ) {
|
|
306
320
|
|
|
321
|
+
// One real-time denoiser at a time.
|
|
322
|
+
if ( s.nrd ) s.nrd.enabled = false;
|
|
307
323
|
s.asvgf?.setTemporalEnabled?.( true );
|
|
308
324
|
this._applyASVGFPreset( qualityPreset || 'medium' );
|
|
309
325
|
|
|
@@ -353,19 +369,25 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
353
369
|
* navigation and frees their textures. Call after any consumer toggle.
|
|
354
370
|
*
|
|
355
371
|
* MotionVector requires NormalDepth (reads pathtracer:normalDepth) and its
|
|
356
|
-
* consumers (ASVGF) are a subset of NormalDepth's, so NormalDepth is
|
|
372
|
+
* consumers (ASVGF, NRD) are a subset of NormalDepth's, so NormalDepth is
|
|
357
373
|
* always enabled whenever MotionVector is. Adaptive sampling / Variance / OIDN
|
|
358
374
|
* do NOT read these signals, so they don't keep the G-buffer alive.
|
|
359
375
|
*/
|
|
376
|
+
/** True while a strategy that reprojects through motion vectors is active. */
|
|
377
|
+
get requiresMotionVectors() {
|
|
378
|
+
|
|
379
|
+
return !! ( this._stages.asvgf?.enabled || this._stages.nrd?.enabled );
|
|
380
|
+
|
|
381
|
+
}
|
|
382
|
+
|
|
360
383
|
_syncGBufferStages() {
|
|
361
384
|
|
|
362
385
|
const s = this._stages;
|
|
363
386
|
const nd = s.normalDepth;
|
|
364
387
|
const mv = s.motionVector;
|
|
365
388
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
// pathtracer:normalDepth consumed by ASVGF, EdgeFilter, BilateralFilter
|
|
389
|
+
const motionNeeded = this.requiresMotionVectors;
|
|
390
|
+
// pathtracer:normalDepth consumed by ASVGF, NRD, EdgeFilter, BilateralFilter
|
|
369
391
|
const normalNeeded = motionNeeded || !! ( s.edgeFilter?.enabled || s.bilateralFilter?.enabled );
|
|
370
392
|
|
|
371
393
|
if ( nd ) {
|
|
@@ -406,7 +428,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
406
428
|
// disabled (lazily re-created on the next dispatch after re-enable). Every strategy/denoiser
|
|
407
429
|
// toggle funnels through here after the enabled flags above are settled, so this is the one
|
|
408
430
|
// choke point. dispose() is idempotent, so re-running it for an already-released stage is a no-op.
|
|
409
|
-
for ( const stage of [ s.asvgf, s.variance, s.bilateralFilter, s.edgeFilter, nd, mv ] ) {
|
|
431
|
+
for ( const stage of [ s.asvgf, s.nrd, s.variance, s.bilateralFilter, s.edgeFilter, nd, mv ] ) {
|
|
410
432
|
|
|
411
433
|
if ( stage && ! stage.enabled ) stage.releaseGPUMemory?.();
|
|
412
434
|
|
|
@@ -691,16 +713,42 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
691
713
|
|
|
692
714
|
/**
|
|
693
715
|
* Switches strategy with automatic reset (convenience wrapper).
|
|
694
|
-
* @param {'none'|'asvgf'|'edgeaware'} strategy
|
|
695
|
-
* @param {string} [
|
|
716
|
+
* @param {'none'|'asvgf'|'nrd'|'edgeaware'} strategy
|
|
717
|
+
* @param {string} [preset]
|
|
696
718
|
*/
|
|
697
|
-
setStrategy( strategy,
|
|
719
|
+
setStrategy( strategy, preset ) {
|
|
698
720
|
|
|
699
|
-
this.setDenoiserStrategy( strategy,
|
|
721
|
+
this.setDenoiserStrategy( strategy, preset );
|
|
700
722
|
this._onReset?.();
|
|
701
723
|
|
|
702
724
|
}
|
|
703
725
|
|
|
726
|
+
// ── NRD (ReBLUR) ─────────────────────────────────────────────
|
|
727
|
+
|
|
728
|
+
/** Updates NRD stage parameters (nrd::ReblurSettings names, see NRD_DEFAULTS). */
|
|
729
|
+
setNRDParams( params ) {
|
|
730
|
+
|
|
731
|
+
this._stages.nrd?.updateParameters( params );
|
|
732
|
+
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Applies an NRD quality preset.
|
|
737
|
+
* @param {string} presetName - 'low' | 'medium' | 'high'
|
|
738
|
+
*/
|
|
739
|
+
applyNRDPreset( presetName ) {
|
|
740
|
+
|
|
741
|
+
this._applyNRDPreset( presetName );
|
|
742
|
+
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
/** Selects the NRD debug view (0 = beauty; see NRD.js for the others). */
|
|
746
|
+
setNRDDebugMode( mode ) {
|
|
747
|
+
|
|
748
|
+
this._stages.nrd?.updateParameters( { debugMode: mode } );
|
|
749
|
+
|
|
750
|
+
}
|
|
751
|
+
|
|
704
752
|
// ── Private ───────────────────────────────────────────────────
|
|
705
753
|
|
|
706
754
|
_setTileHelper( enabled ) {
|
|
@@ -737,12 +785,23 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
737
785
|
const keys = [
|
|
738
786
|
'asvgf:output', 'asvgf:demodulated', 'asvgf:gradient',
|
|
739
787
|
'variance:output', 'bilateralFiltering:output',
|
|
740
|
-
'edgeFiltering:output',
|
|
788
|
+
'edgeFiltering:output', 'nrd:output',
|
|
741
789
|
];
|
|
742
790
|
keys.forEach( k => ctx.removeTexture( k ) );
|
|
743
791
|
|
|
744
792
|
}
|
|
745
793
|
|
|
794
|
+
_applyNRDPreset( presetName ) {
|
|
795
|
+
|
|
796
|
+
const preset = NRD_QUALITY_PRESETS[ presetName ];
|
|
797
|
+
if ( ! preset ) return;
|
|
798
|
+
|
|
799
|
+
const params = {};
|
|
800
|
+
for ( const key of NRD_PRESET_KEYS ) params[ key ] = NRD_DEFAULTS[ key ];
|
|
801
|
+
this._stages.nrd?.updateParameters( { ...params, ...preset } );
|
|
802
|
+
|
|
803
|
+
}
|
|
804
|
+
|
|
746
805
|
_applyASVGFPreset( presetName ) {
|
|
747
806
|
|
|
748
807
|
const preset = ASVGF_QUALITY_PRESETS[ presetName ];
|