rayzee 7.28.2 → 7.30.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/dist/rayzee.es.js +3641 -3021
- package/dist/rayzee.umd.js +56 -56
- package/package.json +1 -1
- package/src/EngineDefaults.js +75 -0
- package/src/PathTracerApp.js +10 -1
- package/src/Processor/PackedRayBuffer.js +26 -5
- package/src/Processor/SceneProcessor.js +1 -1
- package/src/Processor/StorageTexturePool.js +8 -4
- package/src/Processor/TextureCreator.js +102 -5
- 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/index.js +5 -0
- package/src/managers/DenoisingManager.js +80 -21
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -224,6 +224,10 @@ export const ENGINE_DEFAULTS = {
|
|
|
224
224
|
asvgfQualityPreset: 'medium',
|
|
225
225
|
showAsvgfHeatmap: false,
|
|
226
226
|
|
|
227
|
+
// NRD (ReBLUR port) real-time denoiser — see NRD_DEFAULTS / NRD_QUALITY_PRESETS.
|
|
228
|
+
nrdQualityPreset: 'medium',
|
|
229
|
+
nrdDebugMode: 0,
|
|
230
|
+
|
|
227
231
|
// Auto-exposure settings
|
|
228
232
|
autoExposure: false,
|
|
229
233
|
autoExposureKeyValue: 0.18,
|
|
@@ -233,6 +237,16 @@ export const ENGINE_DEFAULTS = {
|
|
|
233
237
|
autoExposureAdaptSpeedDark: 0.5,
|
|
234
238
|
};
|
|
235
239
|
|
|
240
|
+
// Ray distance NormalDepth writes on a miss. Max finite half-float, so miss−miss diffs stay 0
|
|
241
|
+
// rather than Inf−Inf=NaN.
|
|
242
|
+
export const GBUFFER_MISS_DEPTH = 65504.0;
|
|
243
|
+
export const GBUFFER_MISS_THRESHOLD = 6e4;
|
|
244
|
+
|
|
245
|
+
// normHitDist = hitDist / (A + B·viewZ). A constant, not a uniform: the Shade write and the NRD
|
|
246
|
+
// decode must agree, and the normalization is a pure round trip.
|
|
247
|
+
export const NRD_HIT_DIST_A = 3.0;
|
|
248
|
+
export const NRD_HIT_DIST_B = 0.1;
|
|
249
|
+
|
|
236
250
|
// Albedo demodulation safety floor. ASVGF and BilateralFilter MUST use the
|
|
237
251
|
// same value — demod (`color / safeAlbedo`) and remod (`lighting * safeAlbedo`)
|
|
238
252
|
// only round-trip exactly when both sides agree.
|
|
@@ -307,6 +321,67 @@ export const ASVGF_QUALITY_PRESETS = {
|
|
|
307
321
|
}
|
|
308
322
|
};
|
|
309
323
|
|
|
324
|
+
// NRD ReBLUR port (Stages/NRD.js). Names follow nrd::ReblurSettings so NVIDIA's tuning notes apply.
|
|
325
|
+
export const NRD_DEFAULTS = {
|
|
326
|
+
maxAccumulatedFrameNum: 30,
|
|
327
|
+
maxFastAccumulatedFrameNum: 6,
|
|
328
|
+
maxStabilizedFrameNum: 63,
|
|
329
|
+
historyFixFrameNum: 3,
|
|
330
|
+
historyFixBasePixelStride: 14,
|
|
331
|
+
prepassBlurRadius: 30,
|
|
332
|
+
minBlurRadius: 1,
|
|
333
|
+
maxBlurRadius: 30,
|
|
334
|
+
lobeAngleFraction: 0.15,
|
|
335
|
+
roughnessFraction: 0.15,
|
|
336
|
+
planeDistanceSensitivity: 0.02,
|
|
337
|
+
minHitDistanceWeight: 0.1,
|
|
338
|
+
fastHistoryClampingSigmaScale: 2.0,
|
|
339
|
+
fireflySuppressorMinRelativeScale: 2.0,
|
|
340
|
+
enableAntiFirefly: true,
|
|
341
|
+
antilagLuminanceSigmaScale: 2.0,
|
|
342
|
+
antilagLuminanceSensitivity: 3.0,
|
|
343
|
+
disocclusionThreshold: 0.01,
|
|
344
|
+
convergenceS: 1.0,
|
|
345
|
+
convergenceB: 0.2,
|
|
346
|
+
convergenceP: 0.8,
|
|
347
|
+
// Share of the lobe the normal weight accepts before any history exists. NRD's own constant is
|
|
348
|
+
// 0.75, which its source flags as probably too much; at that width it smears curved surfaces.
|
|
349
|
+
lobeVolumePercent: 0.1,
|
|
350
|
+
// Progressive handover: input sample count at which the denoiser passes the render through untouched.
|
|
351
|
+
// 0 = 2 · maxAccumulatedFrameNum.
|
|
352
|
+
handoverFrames: 0,
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Keys a preset may override. Applying one resets every key to its default first, so a preset only
|
|
356
|
+
// states its deltas and `medium` can be empty.
|
|
357
|
+
export const NRD_PRESET_KEYS = [
|
|
358
|
+
'maxAccumulatedFrameNum', 'maxFastAccumulatedFrameNum', 'maxStabilizedFrameNum',
|
|
359
|
+
'historyFixFrameNum', 'prepassBlurRadius', 'maxBlurRadius', 'enableAntiFirefly',
|
|
360
|
+
];
|
|
361
|
+
|
|
362
|
+
export const NRD_QUALITY_PRESETS = {
|
|
363
|
+
// Short history + no pre-pass: most responsive, noisiest.
|
|
364
|
+
low: {
|
|
365
|
+
maxAccumulatedFrameNum: 16,
|
|
366
|
+
maxFastAccumulatedFrameNum: 4,
|
|
367
|
+
maxStabilizedFrameNum: 16,
|
|
368
|
+
historyFixFrameNum: 2,
|
|
369
|
+
prepassBlurRadius: 0,
|
|
370
|
+
maxBlurRadius: 20,
|
|
371
|
+
enableAntiFirefly: false,
|
|
372
|
+
},
|
|
373
|
+
// nrd::ReblurSettings defaults.
|
|
374
|
+
medium: {},
|
|
375
|
+
// Longer history and wider kernels: smoother, more lag on lighting change.
|
|
376
|
+
high: {
|
|
377
|
+
maxAccumulatedFrameNum: 45,
|
|
378
|
+
maxFastAccumulatedFrameNum: 8,
|
|
379
|
+
historyFixFrameNum: 4,
|
|
380
|
+
prepassBlurRadius: 40,
|
|
381
|
+
maxBlurRadius: 40,
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
|
|
310
385
|
export const CAMERA_RANGES = {
|
|
311
386
|
fov: {
|
|
312
387
|
min: 10,
|
package/src/PathTracerApp.js
CHANGED
|
@@ -9,6 +9,7 @@ import { PathTracer } from './Stages/PathTracer.js';
|
|
|
9
9
|
import { NormalDepth } from './Stages/NormalDepth.js';
|
|
10
10
|
import { MotionVector } from './Stages/MotionVector.js';
|
|
11
11
|
import { ASVGF } from './Stages/ASVGF.js';
|
|
12
|
+
import { NRD } from './Stages/NRD.js';
|
|
12
13
|
import { Variance } from './Stages/Variance.js';
|
|
13
14
|
import { BilateralFilter } from './Stages/BilateralFilter.js';
|
|
14
15
|
import { EdgeFilter } from './Stages/EdgeFilter.js';
|
|
@@ -524,7 +525,12 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
524
525
|
if ( this.pipeline ) {
|
|
525
526
|
|
|
526
527
|
this.pipeline.reset();
|
|
527
|
-
if ( ! soft )
|
|
528
|
+
if ( ! soft ) {
|
|
529
|
+
|
|
530
|
+
this.pipeline.eventBus.emit( 'asvgf:reset' );
|
|
531
|
+
this.pipeline.eventBus.emit( 'denoiser:reset' );
|
|
532
|
+
|
|
533
|
+
}
|
|
528
534
|
|
|
529
535
|
}
|
|
530
536
|
|
|
@@ -2857,6 +2863,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2857
2863
|
this.pipeline.addStage( this.stages.pathTracer );
|
|
2858
2864
|
this.pipeline.addStage( this.stages.normalDepth );
|
|
2859
2865
|
this.pipeline.addStage( this.stages.motionVector );
|
|
2866
|
+
this.pipeline.addStage( this.stages.nrd );
|
|
2860
2867
|
this.pipeline.addStage( this.stages.asvgf );
|
|
2861
2868
|
this.pipeline.addStage( this.stages.variance );
|
|
2862
2869
|
this.pipeline.addStage( this.stages.bilateralFilter );
|
|
@@ -3090,6 +3097,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3090
3097
|
pathTracer: this.stages.pathTracer
|
|
3091
3098
|
} );
|
|
3092
3099
|
this.stages.asvgf = new ASVGF( this.renderer, { enabled: false } );
|
|
3100
|
+
this.stages.nrd = new NRD( this.renderer, { enabled: false, pathTracer: this.stages.pathTracer } );
|
|
3093
3101
|
this.stages.variance = new Variance( this.renderer, { enabled: false } );
|
|
3094
3102
|
this.stages.bilateralFilter = new BilateralFilter( this.renderer, { enabled: false } );
|
|
3095
3103
|
this.stages.edgeFilter = new EdgeFilter( this.renderer, { enabled: false } );
|
|
@@ -3114,6 +3122,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
3114
3122
|
normalDepth: this.stages.normalDepth,
|
|
3115
3123
|
motionVector: this.stages.motionVector,
|
|
3116
3124
|
asvgf: this.stages.asvgf,
|
|
3125
|
+
nrd: this.stages.nrd,
|
|
3117
3126
|
variance: this.stages.variance,
|
|
3118
3127
|
bilateralFilter: this.stages.bilateralFilter,
|
|
3119
3128
|
edgeFilter: this.stages.edgeFilter,
|
|
@@ -16,9 +16,10 @@ export const RAY_STRIDE = 7;
|
|
|
16
16
|
export const HIT_STRIDE = 2;
|
|
17
17
|
// Per-pixel G-buffer (first-hit MRT staging): 1 uvec4/pixel — half-packed normal/depth/albedo
|
|
18
18
|
// (pack2x16, no f32 bitcast); read by FinalWrite:
|
|
19
|
-
// .x=packSnorm2x16(normal.xy) .y=packSnorm2x16(normal.z, depth) .z=packUnorm2x16(albedo.rg)
|
|
19
|
+
// .x=packSnorm2x16(normal.xy) .y=packSnorm2x16(normal.z, depth) .z=packUnorm2x16(albedo.rg)
|
|
20
|
+
// .w=packUnorm2x16(albedo.b, normHitDist)
|
|
21
|
+
// normHitDist is written by Shade; writeGBuffer preserves it so the DDFA albedo commits can't zero it.
|
|
20
22
|
// Separate buffer from RAY (per-pixel, not per-ray×S) — written by Generate/Shade bounce-0.
|
|
21
|
-
// (A second lane reserved for an A-SVGF Tier-1 surface ID was removed — write-only, never read.)
|
|
22
23
|
export const GBUFFER_STRIDE = 1;
|
|
23
24
|
|
|
24
25
|
export const RAY = {
|
|
@@ -162,15 +163,35 @@ export const readRayRadiance = ( buf, id ) =>
|
|
|
162
163
|
// One uvec4 per pixel (stride 1); AoS base = pixelIndex * GBUFFER_STRIDE.
|
|
163
164
|
const gbLane = ( pixelIndex ) => uint( pixelIndex ).mul( GBUFFER_STRIDE );
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
// hitDist = null keeps the lane's current normHitDist.
|
|
167
|
+
export const writeGBuffer = ( buf, pixelIndex, normal, depth, albedo, hitDist = null ) => {
|
|
168
|
+
|
|
169
|
+
const lane = gbLane( pixelIndex );
|
|
170
|
+
const h = hitDist === null ? unpackUnorm2x16( buf.element( lane ).w ).y : float( hitDist );
|
|
171
|
+
return buf.element( lane ).assign( uvec4(
|
|
167
172
|
packSnorm2x16( vec2( normal.x, normal.y ) ),
|
|
168
173
|
packSnorm2x16( vec2( normal.z, depth ) ),
|
|
169
174
|
packUnorm2x16( vec2( albedo.x, albedo.y ) ),
|
|
170
|
-
packUnorm2x16( vec2( albedo.z,
|
|
175
|
+
packUnorm2x16( vec2( albedo.z, h ) ),
|
|
176
|
+
) );
|
|
177
|
+
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const writeGBufferHitDist = ( buf, pixelIndex, normHitDist ) => {
|
|
181
|
+
|
|
182
|
+
const lane = gbLane( pixelIndex );
|
|
183
|
+
const cur = buf.element( lane ).toVar();
|
|
184
|
+
return buf.element( lane ).assign( uvec4(
|
|
185
|
+
cur.x, cur.y, cur.z,
|
|
186
|
+
packUnorm2x16( vec2( unpackUnorm2x16( cur.w ).x, normHitDist ) ),
|
|
171
187
|
) );
|
|
188
|
+
|
|
189
|
+
};
|
|
190
|
+
|
|
172
191
|
export const readGBuffer = ( buf, pixelIndex ) => buf.element( gbLane( pixelIndex ) );
|
|
173
192
|
|
|
193
|
+
export const gbDecodeHitDist = ( packed ) => unpackUnorm2x16( packed.w ).y;
|
|
194
|
+
|
|
174
195
|
// Decode for FinalWrite. normalDepth.xyz matches the prior path (normal*0.5+0.5), .w = raw depth.
|
|
175
196
|
export const gbDecodeNormalDepth = ( packed ) => {
|
|
176
197
|
|
|
@@ -836,7 +836,7 @@ export class SceneProcessor {
|
|
|
836
836
|
// (the prior model-load path omitted this, leaving albedo un-decoded / too bright).
|
|
837
837
|
const buildBucket = ( list, srgb ) => list.length === 0
|
|
838
838
|
? Promise.resolve( null )
|
|
839
|
-
: this.textureCreator.createTexturesToDataTexture( list ).then( tex => {
|
|
839
|
+
: this.textureCreator.createTexturesToDataTexture( list, { srgbPool: srgb } ).then( tex => {
|
|
840
840
|
|
|
841
841
|
if ( tex && srgb ) tex.colorSpace = SRGBColorSpace;
|
|
842
842
|
return tex;
|
|
@@ -19,17 +19,21 @@ import { createLogger, fmt } from '../utils/Logger.js';
|
|
|
19
19
|
|
|
20
20
|
const log = createLogger( 'gpu' );
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// Compute-output StorageTexture at the reserved size, never resized (see above). LinearFilter is
|
|
23
|
+
// load-bearing: textureLoad codegen requires it on a StorageTexture.
|
|
24
|
+
export function createStorageTexture( type = FloatType, filter = LinearFilter ) {
|
|
23
25
|
|
|
24
26
|
const tex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
25
|
-
tex.type =
|
|
27
|
+
tex.type = type;
|
|
26
28
|
tex.format = RGBAFormat;
|
|
27
|
-
tex.minFilter =
|
|
28
|
-
tex.magFilter =
|
|
29
|
+
tex.minFilter = filter;
|
|
30
|
+
tex.magFilter = filter;
|
|
29
31
|
return tex;
|
|
30
32
|
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
const createWriteStorageTex = () => createStorageTexture();
|
|
36
|
+
|
|
33
37
|
export class StorageTexturePool {
|
|
34
38
|
|
|
35
39
|
constructor( width, height ) {
|
|
@@ -1,7 +1,38 @@
|
|
|
1
|
-
import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace, RepeatWrapping } from "three";
|
|
1
|
+
import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace, LinearSRGBColorSpace, RepeatWrapping } from "three";
|
|
2
2
|
import { TEXTURE_CONSTANTS, MEMORY_CONSTANTS, DEFAULT_TEXTURE_MATRIX, MATERIAL_DATA_LAYOUT, normalizeAttenuationDistance } from '../EngineDefaults.js';
|
|
3
3
|
import TexturesWorker from './Workers/TexturesWorker.js?worker&inline';
|
|
4
4
|
import { ISSUE_CODES } from '../EngineIssues.js';
|
|
5
|
+
import { linearToSRGB } from './ToneMapCPU.js';
|
|
6
|
+
import { createLogger } from '../utils/Logger.js';
|
|
7
|
+
|
|
8
|
+
const log = createLogger( 'textures' );
|
|
9
|
+
|
|
10
|
+
// A bucket array carries ONE colorSpace and the shader reads each slot from a fixed pool, so a
|
|
11
|
+
// texture disagreeing with its pool cannot be re-routed — its pixels are re-encoded instead.
|
|
12
|
+
const TRANSFER_LUT = { toSRGB: null, toLinear: null };
|
|
13
|
+
|
|
14
|
+
function transferLUT( direction ) {
|
|
15
|
+
|
|
16
|
+
if ( ! TRANSFER_LUT[ direction ] ) {
|
|
17
|
+
|
|
18
|
+
const lut = new Uint8Array( 256 );
|
|
19
|
+
for ( let i = 0; i < 256; i ++ ) {
|
|
20
|
+
|
|
21
|
+
const c = i / 255;
|
|
22
|
+
const v = direction === 'toSRGB'
|
|
23
|
+
? linearToSRGB( c )
|
|
24
|
+
: ( c <= 0.04045 ? c / 12.92 : Math.pow( ( c + 0.055 ) / 1.055, 2.4 ) );
|
|
25
|
+
lut[ i ] = Math.max( 0, Math.min( 255, Math.round( v * 255 ) ) );
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
TRANSFER_LUT[ direction ] = lut;
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return TRANSFER_LUT[ direction ];
|
|
34
|
+
|
|
35
|
+
}
|
|
5
36
|
|
|
6
37
|
// Canvas pooling for efficient reuse of canvas elements
|
|
7
38
|
class CanvasPool {
|
|
@@ -541,13 +572,76 @@ export class TextureCreator {
|
|
|
541
572
|
|
|
542
573
|
}
|
|
543
574
|
|
|
575
|
+
/**
|
|
576
|
+
* Re-encodes any layer whose declared colour space disagrees with its pool, so the GPU's
|
|
577
|
+
* decode — or absence of one — lands on the values the author stored. An untagged texture
|
|
578
|
+
* keeps its pool's convention, which is what every loader produces for glTF.
|
|
579
|
+
*
|
|
580
|
+
* @param {import('three').DataArrayTexture} arrayTex
|
|
581
|
+
* @param {Array<import('three').Texture>} textures - pre-normalization list, in layer order
|
|
582
|
+
* @param {boolean} srgbPool - true when the array is tagged SRGBColorSpace
|
|
583
|
+
*/
|
|
584
|
+
_harmonizeTransfer( arrayTex, textures, srgbPool ) {
|
|
585
|
+
|
|
586
|
+
const data = arrayTex?.image?.data;
|
|
587
|
+
if ( ! data ) return;
|
|
588
|
+
|
|
589
|
+
// Mirrors _normalizeTexturesForProcessing's skip rule, which is what sets layer order.
|
|
590
|
+
const kept = textures.filter( t => t?.image );
|
|
591
|
+
const want = srgbPool ? SRGBColorSpace : LinearSRGBColorSpace;
|
|
592
|
+
const layerSize = arrayTex.image.width * arrayTex.image.height * 4;
|
|
593
|
+
const converted = [];
|
|
594
|
+
|
|
595
|
+
for ( let layer = 0; layer < kept.length; layer ++ ) {
|
|
596
|
+
|
|
597
|
+
const declared = kept[ layer ].colorSpace;
|
|
598
|
+
// Only an explicit disagreement is acted on; NoColorSpace defers to the pool.
|
|
599
|
+
if ( declared !== SRGBColorSpace && declared !== LinearSRGBColorSpace ) continue;
|
|
600
|
+
if ( declared === want ) continue;
|
|
601
|
+
|
|
602
|
+
const lut = transferLUT( srgbPool ? 'toSRGB' : 'toLinear' );
|
|
603
|
+
const start = layer * layerSize;
|
|
604
|
+
const end = Math.min( start + layerSize, data.length );
|
|
605
|
+
// RGB only — alpha is never transfer-encoded.
|
|
606
|
+
for ( let i = start; i < end; i += 4 ) {
|
|
607
|
+
|
|
608
|
+
data[ i ] = lut[ data[ i ] ];
|
|
609
|
+
data[ i + 1 ] = lut[ data[ i + 1 ] ];
|
|
610
|
+
data[ i + 2 ] = lut[ data[ i + 2 ] ];
|
|
611
|
+
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
converted.push( kept[ layer ].name || kept[ layer ].source?.uuid?.slice( 0, 8 ) || `layer ${layer}` );
|
|
615
|
+
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if ( converted.length === 0 ) return;
|
|
619
|
+
|
|
620
|
+
arrayTex.needsUpdate = true;
|
|
621
|
+
const names = converted.slice( 0, 4 ).join( ', ' ) + ( converted.length > 4 ? `, +${converted.length - 4} more` : '' );
|
|
622
|
+
|
|
623
|
+
if ( srgbPool ) {
|
|
624
|
+
|
|
625
|
+
log.info( `re-encoded ${converted.length}/${kept.length} linear-declared colour map(s) to sRGB so the GPU decode round-trips: ${names}` );
|
|
626
|
+
|
|
627
|
+
} else {
|
|
628
|
+
|
|
629
|
+
// Storing a decoded value in an 8-bit linear array is the lossy direction, and a
|
|
630
|
+
// transfer-encoded data map is almost always an authoring slip rather than intent.
|
|
631
|
+
log.warn( `decoded ${converted.length}/${kept.length} sRGB-declared data map(s) to linear — precision is lost in the darks, check the asset: ${names}` );
|
|
632
|
+
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
}
|
|
636
|
+
|
|
544
637
|
// Unified texture processing with strategy selection
|
|
545
|
-
async createTexturesToDataTexture( textures ) {
|
|
638
|
+
async createTexturesToDataTexture( textures, { srgbPool = false } = {} ) {
|
|
546
639
|
|
|
547
640
|
if ( ! textures || textures.length === 0 ) return null;
|
|
548
641
|
|
|
549
|
-
//
|
|
550
|
-
|
|
642
|
+
// Pool is part of the identity: the same list packed for the colour pool and the data pool
|
|
643
|
+
// gets different pixels (see _harmonizeTransfer), so one key must not serve both.
|
|
644
|
+
const cacheKey = `${srgbPool ? 's' : 'l'}:${this.textureCache.generateHash( textures )}`;
|
|
551
645
|
const cached = this.textureCache.get( cacheKey );
|
|
552
646
|
if ( cached ) return cached;
|
|
553
647
|
|
|
@@ -579,6 +673,7 @@ export class TextureCreator {
|
|
|
579
673
|
// Cache successful result
|
|
580
674
|
if ( result ) {
|
|
581
675
|
|
|
676
|
+
this._harmonizeTransfer( result, textures, srgbPool );
|
|
582
677
|
this.textureCache.set( cacheKey, result );
|
|
583
678
|
|
|
584
679
|
}
|
|
@@ -592,7 +687,9 @@ export class TextureCreator {
|
|
|
592
687
|
'worker texture processing failed — retrying on the main thread',
|
|
593
688
|
{ cause: String( error?.message ?? error ) }
|
|
594
689
|
);
|
|
595
|
-
|
|
690
|
+
const fallback = await this.processOnMainThreadSync( normalized );
|
|
691
|
+
this._harmonizeTransfer( fallback, textures, srgbPool );
|
|
692
|
+
return fallback;
|
|
596
693
|
|
|
597
694
|
} finally {
|
|
598
695
|
|
package/src/RenderSettings.js
CHANGED
|
@@ -182,10 +182,10 @@ export class RenderSettings extends EventDispatcher {
|
|
|
182
182
|
|
|
183
183
|
if ( ! isPanorama ) return;
|
|
184
184
|
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
if ( denoisingManager?.
|
|
185
|
+
// MotionVector unprojects through projectionMatrixInverse, which is meaningless once
|
|
186
|
+
// every pixel is its own direction. Fall back to the spatial-only denoiser rather
|
|
187
|
+
// than leaving no strategy.
|
|
188
|
+
if ( denoisingManager?.requiresMotionVectors ) denoisingManager.setDenoiserStrategy( 'edgeaware' );
|
|
189
189
|
// Auto-focus raycasts via Raycaster.setFromCamera, which only knows the frustum.
|
|
190
190
|
cameraManager?.setAutoFocusMode( 'manual' );
|
|
191
191
|
|
package/src/Stages/Compositor.js
CHANGED
|
@@ -70,6 +70,7 @@ export class Compositor extends RenderStage {
|
|
|
70
70
|
|| context.getTexture( 'edgeFiltering:output' )
|
|
71
71
|
|| context.getTexture( 'bilateralFiltering:output' )
|
|
72
72
|
|| context.getTexture( 'asvgf:output' )
|
|
73
|
+
|| context.getTexture( 'nrd:output' )
|
|
73
74
|
|| context.getTexture( 'pathtracer:color' );
|
|
74
75
|
|
|
75
76
|
}
|