rayzee 6.4.0 → 7.0.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 +24 -5
- package/dist/rayzee.es.js +4953 -4225
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +157 -236
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +29 -13
- package/src/PathTracerApp.js +119 -26
- package/src/Pipeline/PipelineContext.js +1 -2
- package/src/Pipeline/RenderPipeline.js +1 -1
- package/src/Pipeline/RenderStage.js +1 -1
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/GeometryExtractor.js +22 -1
- package/src/Processor/KernelManager.js +277 -0
- package/src/Processor/PackedRayBuffer.js +265 -0
- package/src/Processor/QueueManager.js +173 -0
- package/src/Processor/SceneProcessor.js +1 -0
- package/src/Processor/ShaderBuilder.js +11 -316
- package/src/Processor/StorageTexturePool.js +29 -15
- package/src/Processor/TextureCreator.js +6 -0
- package/src/Processor/VRAMTracker.js +169 -0
- package/src/Processor/utils.js +11 -110
- package/src/RenderSettings.js +1 -3
- package/src/Stages/ASVGF.js +76 -20
- package/src/Stages/BilateralFilter.js +34 -10
- package/src/Stages/EdgeFilter.js +2 -3
- package/src/Stages/MotionVector.js +16 -9
- package/src/Stages/NormalDepth.js +17 -5
- package/src/Stages/PathTracer.js +671 -1456
- package/src/Stages/PathTracerStage.js +1451 -0
- package/src/Stages/SSRC.js +32 -15
- package/src/Stages/Variance.js +35 -12
- package/src/TSL/BVHTraversal.js +7 -1
- package/src/TSL/Common.js +12 -2
- package/src/TSL/CompactKernel.js +110 -0
- package/src/TSL/DebugKernel.js +98 -0
- package/src/TSL/Environment.js +13 -11
- package/src/TSL/ExtendKernel.js +75 -0
- package/src/TSL/FinalWriteKernel.js +121 -0
- package/src/TSL/GenerateKernel.js +109 -0
- package/src/TSL/LightsSampling.js +2 -2
- package/src/TSL/MaterialTransmission.js +32 -2
- package/src/TSL/PathTracerCore.js +43 -912
- package/src/TSL/ShadeKernel.js +873 -0
- package/src/TSL/Struct.js +5 -0
- package/src/TSL/Subsurface.js +232 -0
- package/src/TSL/patches.js +81 -4
- package/src/index.js +3 -0
- package/src/managers/CameraManager.js +1 -1
- package/src/managers/DenoisingManager.js +40 -75
- package/src/managers/EnvironmentManager.js +30 -39
- package/src/managers/MaterialDataManager.js +60 -1
- package/src/managers/OverlayManager.js +7 -22
- package/src/managers/UniformManager.js +1 -3
- package/src/managers/helpers/TileHelper.js +2 -2
- package/src/Stages/AdaptiveSampling.js +0 -483
- package/src/TSL/PathTracer.js +0 -384
- package/src/managers/TileManager.js +0 -298
package/src/Processor/utils.js
CHANGED
|
@@ -27,21 +27,10 @@ export const updateStats = ( statsUpdate ) => {
|
|
|
27
27
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
* Convert raw frameCount to user-facing sample count.
|
|
32
|
-
* In tiled mode, one "sample pass" spans all tiles.
|
|
33
|
-
*/
|
|
30
|
+
// Raw frameCount is the user-facing sample count (one full-frame pass per frame).
|
|
34
31
|
export function getDisplaySamples( pathTracerStage ) {
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
if ( pathTracerStage.renderMode?.value === 1 && frameCount > 0 ) {
|
|
38
|
-
|
|
39
|
-
const totalTiles = pathTracerStage.tileManager?.totalTilesCache || 1;
|
|
40
|
-
return 1 + Math.floor( ( frameCount - 1 ) / totalTiles );
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return frameCount;
|
|
33
|
+
return pathTracerStage.frameCount || 0;
|
|
45
34
|
|
|
46
35
|
}
|
|
47
36
|
|
|
@@ -214,9 +203,9 @@ export function generateMaterialSpheres( rows = 5, columns = 5, spacing = 1.2 )
|
|
|
214
203
|
|
|
215
204
|
// ── Path Tracer Utilities (formerly PathTracerUtils static class) ──
|
|
216
205
|
|
|
217
|
-
export function updateCompletionThreshold( renderMode, maxFrames
|
|
206
|
+
export function updateCompletionThreshold( renderMode, maxFrames ) {
|
|
218
207
|
|
|
219
|
-
return
|
|
208
|
+
return maxFrames;
|
|
220
209
|
|
|
221
210
|
}
|
|
222
211
|
|
|
@@ -335,33 +324,10 @@ export function areValuesEqual( a, b ) {
|
|
|
335
324
|
|
|
336
325
|
}
|
|
337
326
|
|
|
338
|
-
export function calculateAccumulationAlpha( frameValue,
|
|
339
|
-
|
|
340
|
-
if ( isInteractionMode ) {
|
|
341
|
-
|
|
342
|
-
return 1.0;
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if ( renderMode === 0 ) {
|
|
347
|
-
|
|
348
|
-
return 1.0 / ( frameValue + 1 );
|
|
349
|
-
|
|
350
|
-
} else {
|
|
351
|
-
|
|
352
|
-
if ( frameValue === 0 ) {
|
|
353
|
-
|
|
354
|
-
return 1.0;
|
|
355
|
-
|
|
356
|
-
} else {
|
|
357
|
-
|
|
358
|
-
const completedTileCycles = Math.floor( ( frameValue - 1 ) / totalTiles );
|
|
359
|
-
const totalSamples = 1 + completedTileCycles;
|
|
360
|
-
return 1.0 / ( totalSamples + 1 );
|
|
327
|
+
export function calculateAccumulationAlpha( frameValue, isInteractionMode = false ) {
|
|
361
328
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
329
|
+
// Full-frame progressive accumulation: each frame is one complete pass.
|
|
330
|
+
return isInteractionMode ? 1.0 : 1.0 / ( frameValue + 1 );
|
|
365
331
|
|
|
366
332
|
}
|
|
367
333
|
|
|
@@ -416,12 +382,6 @@ export function optimizeShaderDefines( defines, state ) {
|
|
|
416
382
|
|
|
417
383
|
const optimized = { ...defines };
|
|
418
384
|
|
|
419
|
-
if ( ! state.useAdaptiveSampling ) {
|
|
420
|
-
|
|
421
|
-
delete optimized.ENABLE_ADAPTIVE_SAMPLING;
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
|
|
425
385
|
if ( ! state.enableAccumulation ) {
|
|
426
386
|
|
|
427
387
|
delete optimized.ENABLE_ACCUMULATION;
|
|
@@ -438,49 +398,6 @@ export function optimizeShaderDefines( defines, state ) {
|
|
|
438
398
|
|
|
439
399
|
}
|
|
440
400
|
|
|
441
|
-
export function calculateSpiralOrder( tiles, center = null ) {
|
|
442
|
-
|
|
443
|
-
const totalTiles = tiles * tiles;
|
|
444
|
-
const centerPoint = center || new Vector2( ( tiles - 1 ) / 2, ( tiles - 1 ) / 2 );
|
|
445
|
-
const tilePositions = [];
|
|
446
|
-
|
|
447
|
-
for ( let i = 0; i < totalTiles; i ++ ) {
|
|
448
|
-
|
|
449
|
-
const x = i % tiles;
|
|
450
|
-
const y = Math.floor( i / tiles );
|
|
451
|
-
const distance = Math.sqrt(
|
|
452
|
-
Math.pow( x - centerPoint.x, 2 ) +
|
|
453
|
-
Math.pow( y - centerPoint.y, 2 )
|
|
454
|
-
);
|
|
455
|
-
const angle = Math.atan( y - centerPoint.y, x - centerPoint.x );
|
|
456
|
-
|
|
457
|
-
tilePositions.push( {
|
|
458
|
-
index: i,
|
|
459
|
-
x,
|
|
460
|
-
y,
|
|
461
|
-
distance,
|
|
462
|
-
angle
|
|
463
|
-
} );
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
tilePositions.sort( ( a, b ) => {
|
|
468
|
-
|
|
469
|
-
const distanceDiff = a.distance - b.distance;
|
|
470
|
-
if ( Math.abs( distanceDiff ) < 0.01 ) {
|
|
471
|
-
|
|
472
|
-
return a.angle - b.angle;
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return distanceDiff;
|
|
477
|
-
|
|
478
|
-
} );
|
|
479
|
-
|
|
480
|
-
return tilePositions.map( pos => pos.index );
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
401
|
export function clamp( value, min, max ) {
|
|
485
402
|
|
|
486
403
|
return Math.min( Math.max( value, min ), max );
|
|
@@ -493,31 +410,15 @@ export function lerp( a, b, t ) {
|
|
|
493
410
|
|
|
494
411
|
}
|
|
495
412
|
|
|
496
|
-
export function isRenderComplete( frameValue, renderMode, maxFrames
|
|
497
|
-
|
|
498
|
-
if ( renderMode === 0 ) {
|
|
499
|
-
|
|
500
|
-
return frameValue >= maxFrames;
|
|
501
|
-
|
|
502
|
-
} else {
|
|
503
|
-
|
|
504
|
-
return frameValue >= maxFrames * totalTiles;
|
|
413
|
+
export function isRenderComplete( frameValue, renderMode, maxFrames ) {
|
|
505
414
|
|
|
506
|
-
|
|
415
|
+
return frameValue >= maxFrames;
|
|
507
416
|
|
|
508
417
|
}
|
|
509
418
|
|
|
510
|
-
export function getCurrentSampleCount( frameValue
|
|
511
|
-
|
|
512
|
-
if ( renderMode === 0 ) {
|
|
419
|
+
export function getCurrentSampleCount( frameValue ) {
|
|
513
420
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
} else {
|
|
517
|
-
|
|
518
|
-
return Math.floor( frameValue / totalTiles );
|
|
519
|
-
|
|
520
|
-
}
|
|
421
|
+
return frameValue;
|
|
521
422
|
|
|
522
423
|
}
|
|
523
424
|
|
package/src/RenderSettings.js
CHANGED
|
@@ -18,6 +18,7 @@ const SETTING_ROUTES = {
|
|
|
18
18
|
maxBounces: { uniform: 'maxBounces', reset: true },
|
|
19
19
|
samplesPerPixel: { uniform: 'samplesPerPixel', reset: true },
|
|
20
20
|
transmissiveBounces: { uniform: 'transmissiveBounces', reset: true },
|
|
21
|
+
maxSubsurfaceSteps: { uniform: 'maxSubsurfaceSteps', reset: true },
|
|
21
22
|
environmentIntensity: { uniform: 'environmentIntensity', reset: true },
|
|
22
23
|
backgroundIntensity: { uniform: 'backgroundIntensity', reset: true },
|
|
23
24
|
showBackground: { uniform: 'showBackground', reset: true },
|
|
@@ -39,8 +40,6 @@ const SETTING_ROUTES = {
|
|
|
39
40
|
emissiveBoost: { uniform: 'emissiveBoost', reset: true },
|
|
40
41
|
visMode: { uniform: 'visMode', reset: true },
|
|
41
42
|
debugVisScale: { uniform: 'debugVisScale', reset: true },
|
|
42
|
-
useAdaptiveSampling: { uniform: 'useAdaptiveSampling', reset: true },
|
|
43
|
-
adaptiveSamplingMax: { uniform: 'adaptiveSamplingMax', reset: true },
|
|
44
43
|
|
|
45
44
|
// ── Multi-stage / special handling ────────────────────────────
|
|
46
45
|
|
|
@@ -62,7 +61,6 @@ const SETTING_ROUTES = {
|
|
|
62
61
|
*/
|
|
63
62
|
const DEFAULTS_KEY_MAP = {
|
|
64
63
|
bounces: 'maxBounces',
|
|
65
|
-
adaptiveSampling: 'useAdaptiveSampling',
|
|
66
64
|
debugMode: 'visMode',
|
|
67
65
|
};
|
|
68
66
|
|
package/src/Stages/ASVGF.js
CHANGED
|
@@ -2,10 +2,10 @@ import { Fn, vec3, vec4, float, int, uint, ivec2, uvec2, uniform,
|
|
|
2
2
|
If, dot, max, min, abs, mix, pow, exp,
|
|
3
3
|
textureLoad, textureStore, workgroupArray, workgroupBarrier, localId, workgroupId } from 'three/tsl';
|
|
4
4
|
import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
|
|
5
|
-
import { HalfFloatType, FloatType, RGBAFormat, NearestFilter, LinearFilter } from 'three';
|
|
5
|
+
import { HalfFloatType, FloatType, RGBAFormat, NearestFilter, LinearFilter, Box2, Vector2 } from 'three';
|
|
6
6
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
7
7
|
import { luminance } from '../TSL/Common.js';
|
|
8
|
-
import { ALBEDO_EPS } from '../EngineDefaults.js';
|
|
8
|
+
import { ALBEDO_EPS, MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* ASVGF — SVGF temporal + spatial denoising with albedo demodulation.
|
|
@@ -57,30 +57,61 @@ export class ASVGF extends RenderStage {
|
|
|
57
57
|
// FloatType for ping-pong: demodulated lighting on dark materials
|
|
58
58
|
// (lighting ≈ color/0.01) exceeds HalfFloat's 65k cap on HDR.
|
|
59
59
|
// LinearFilter is required for textureLoad codegen on StorageTextures.
|
|
60
|
-
this._temporalTexA = new StorageTexture(
|
|
60
|
+
this._temporalTexA = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
61
61
|
this._temporalTexA.type = FloatType;
|
|
62
62
|
this._temporalTexA.format = RGBAFormat;
|
|
63
63
|
this._temporalTexA.minFilter = LinearFilter;
|
|
64
64
|
this._temporalTexA.magFilter = LinearFilter;
|
|
65
65
|
|
|
66
|
-
this._temporalTexB = new StorageTexture(
|
|
66
|
+
this._temporalTexB = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
67
67
|
this._temporalTexB.type = FloatType;
|
|
68
68
|
this._temporalTexB.format = RGBAFormat;
|
|
69
69
|
this._temporalTexB.minFilter = LinearFilter;
|
|
70
70
|
this._temporalTexB.magFilter = LinearFilter;
|
|
71
71
|
|
|
72
|
-
this._outputModulatedTex = new StorageTexture(
|
|
72
|
+
this._outputModulatedTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
73
73
|
this._outputModulatedTex.type = FloatType;
|
|
74
74
|
this._outputModulatedTex.format = RGBAFormat;
|
|
75
75
|
this._outputModulatedTex.minFilter = LinearFilter;
|
|
76
76
|
this._outputModulatedTex.magFilter = LinearFilter;
|
|
77
77
|
|
|
78
|
-
this._gradientStorageTex = new StorageTexture(
|
|
78
|
+
this._gradientStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
79
79
|
this._gradientStorageTex.type = HalfFloatType;
|
|
80
80
|
this._gradientStorageTex.format = RGBAFormat;
|
|
81
81
|
this._gradientStorageTex.minFilter = LinearFilter;
|
|
82
82
|
this._gradientStorageTex.magFilter = LinearFilter;
|
|
83
83
|
|
|
84
|
+
// Over-allocated StorageTextures are sampled by UV downstream; copy the
|
|
85
|
+
// active region into right-sized RTs and publish those instead.
|
|
86
|
+
this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
|
|
87
|
+
|
|
88
|
+
this._demodulatedRT = new RenderTarget( w, h, {
|
|
89
|
+
type: FloatType,
|
|
90
|
+
format: RGBAFormat,
|
|
91
|
+
minFilter: LinearFilter,
|
|
92
|
+
magFilter: LinearFilter,
|
|
93
|
+
depthBuffer: false,
|
|
94
|
+
stencilBuffer: false
|
|
95
|
+
} );
|
|
96
|
+
|
|
97
|
+
this._outputRT = new RenderTarget( w, h, {
|
|
98
|
+
type: FloatType,
|
|
99
|
+
format: RGBAFormat,
|
|
100
|
+
minFilter: LinearFilter,
|
|
101
|
+
magFilter: LinearFilter,
|
|
102
|
+
depthBuffer: false,
|
|
103
|
+
stencilBuffer: false
|
|
104
|
+
} );
|
|
105
|
+
|
|
106
|
+
this._gradientRT = new RenderTarget( w, h, {
|
|
107
|
+
type: HalfFloatType,
|
|
108
|
+
format: RGBAFormat,
|
|
109
|
+
minFilter: LinearFilter,
|
|
110
|
+
magFilter: LinearFilter,
|
|
111
|
+
depthBuffer: false,
|
|
112
|
+
stencilBuffer: false
|
|
113
|
+
} );
|
|
114
|
+
|
|
84
115
|
// FloatType to match pathtracer:color (PT MRT). copyTextureToTexture
|
|
85
116
|
// requires identical formats.
|
|
86
117
|
this._prevColorRT = new RenderTarget( w, h, {
|
|
@@ -105,7 +136,7 @@ export class ASVGF extends RenderStage {
|
|
|
105
136
|
this.showHeatmap = false;
|
|
106
137
|
this.debugMode = uniform( 0, 'int' );
|
|
107
138
|
|
|
108
|
-
this._heatmapStorageTex = new StorageTexture(
|
|
139
|
+
this._heatmapStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
109
140
|
this._heatmapStorageTex.type = FloatType;
|
|
110
141
|
this._heatmapStorageTex.format = RGBAFormat;
|
|
111
142
|
this._heatmapStorageTex.minFilter = NearestFilter;
|
|
@@ -581,8 +612,9 @@ export class ASVGF extends RenderStage {
|
|
|
581
612
|
const img = colorTex.image;
|
|
582
613
|
if ( img && img.width > 0 && img.height > 0 ) {
|
|
583
614
|
|
|
584
|
-
|
|
585
|
-
|
|
615
|
+
// Compare against an active-size RT, not the fixed-2048 StorageTexture.
|
|
616
|
+
if ( img.width !== this._outputRT.width ||
|
|
617
|
+
img.height !== this._outputRT.height ) {
|
|
586
618
|
|
|
587
619
|
this.setSize( img.width, img.height );
|
|
588
620
|
|
|
@@ -647,9 +679,21 @@ export class ASVGF extends RenderStage {
|
|
|
647
679
|
|
|
648
680
|
}
|
|
649
681
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
682
|
+
// Copy active region out of the over-allocated StorageTextures into
|
|
683
|
+
// right-sized RTs; downstream stages UV-sample these.
|
|
684
|
+
this._srcRegion.max.set( this.resW.value, this.resH.value );
|
|
685
|
+
|
|
686
|
+
this.renderer.copyTextureToTexture( writeTemporal, this._demodulatedRT.texture, this._srcRegion );
|
|
687
|
+
this.renderer.copyTextureToTexture( this._outputModulatedTex, this._outputRT.texture, this._srcRegion );
|
|
688
|
+
if ( needsGradient ) {
|
|
689
|
+
|
|
690
|
+
this.renderer.copyTextureToTexture( this._gradientStorageTex, this._gradientRT.texture, this._srcRegion );
|
|
691
|
+
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
context.setTexture( 'asvgf:demodulated', this._demodulatedRT.texture );
|
|
695
|
+
context.setTexture( 'asvgf:output', this._outputRT.texture );
|
|
696
|
+
context.setTexture( 'asvgf:gradient', this._gradientRT.texture );
|
|
653
697
|
|
|
654
698
|
this.currentMoments = 1 - this.currentMoments;
|
|
655
699
|
|
|
@@ -665,7 +709,8 @@ export class ASVGF extends RenderStage {
|
|
|
665
709
|
this._heatmapGradientTexNode.value = this._gradientStorageTex;
|
|
666
710
|
|
|
667
711
|
this.renderer.compute( this._heatmapComputeNode );
|
|
668
|
-
this.
|
|
712
|
+
this._srcRegion.max.set( this.heatmapTarget.width, this.heatmapTarget.height );
|
|
713
|
+
this.renderer.copyTextureToTexture( this._heatmapStorageTex, this.heatmapTarget.texture, this._srcRegion );
|
|
669
714
|
|
|
670
715
|
}
|
|
671
716
|
|
|
@@ -704,13 +749,17 @@ export class ASVGF extends RenderStage {
|
|
|
704
749
|
|
|
705
750
|
setSize( width, height ) {
|
|
706
751
|
|
|
707
|
-
|
|
708
|
-
this.
|
|
709
|
-
this.
|
|
710
|
-
this.
|
|
752
|
+
// StorageTextures stay at max alloc — see resize crash fix (three.js #33061).
|
|
753
|
+
this._demodulatedRT.setSize( width, height );
|
|
754
|
+
this._demodulatedRT.texture.needsUpdate = true;
|
|
755
|
+
this._outputRT.setSize( width, height );
|
|
756
|
+
this._outputRT.texture.needsUpdate = true;
|
|
757
|
+
this._gradientRT.setSize( width, height );
|
|
758
|
+
this._gradientRT.texture.needsUpdate = true;
|
|
711
759
|
this._prevColorRT.setSize( width, height );
|
|
712
|
-
this.
|
|
760
|
+
this._prevColorRT.texture.needsUpdate = true;
|
|
713
761
|
this.heatmapTarget.setSize( width, height );
|
|
762
|
+
this.heatmapTarget.texture.needsUpdate = true;
|
|
714
763
|
this.resW.value = width;
|
|
715
764
|
this.resH.value = height;
|
|
716
765
|
|
|
@@ -721,8 +770,12 @@ export class ASVGF extends RenderStage {
|
|
|
721
770
|
this._temporalNodeB.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
|
|
722
771
|
this._heatmapComputeNode.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
|
|
723
772
|
|
|
724
|
-
//
|
|
725
|
-
|
|
773
|
+
// StorageTextures are over-allocated (never reallocated on resize), so the
|
|
774
|
+
// compute kernels stay valid — do NOT reset _compiled. Re-running the warmup
|
|
775
|
+
// would dispatch both temporal ping-pong nodes while _readTemporalTexNode still
|
|
776
|
+
// aliases one node's write target, producing a "write-only storage +
|
|
777
|
+
// TextureBinding in same synchronization scope" validation error.
|
|
778
|
+
// Only the size-dependent prev-color cache needs re-seeding.
|
|
726
779
|
this._prevColorReady = false;
|
|
727
780
|
|
|
728
781
|
}
|
|
@@ -743,6 +796,9 @@ export class ASVGF extends RenderStage {
|
|
|
743
796
|
this._temporalTexB?.dispose();
|
|
744
797
|
this._outputModulatedTex?.dispose();
|
|
745
798
|
this._gradientStorageTex?.dispose();
|
|
799
|
+
this._demodulatedRT?.dispose();
|
|
800
|
+
this._outputRT?.dispose();
|
|
801
|
+
this._gradientRT?.dispose();
|
|
746
802
|
this._prevColorRT?.dispose();
|
|
747
803
|
this._heatmapComputeNode?.dispose();
|
|
748
804
|
this._heatmapStorageTex?.dispose();
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Fn, wgslFn, vec3, vec4, float, int, uint, ivec2, uvec2, uniform, If, max, sqrt,
|
|
2
2
|
textureLoad, textureStore, localId, workgroupId } from 'three/tsl';
|
|
3
|
-
import { TextureNode, StorageTexture } from 'three/webgpu';
|
|
4
|
-
import { HalfFloatType, RGBAFormat, LinearFilter } from 'three';
|
|
3
|
+
import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
|
|
4
|
+
import { HalfFloatType, RGBAFormat, LinearFilter, Box2, Vector2 } from 'three';
|
|
5
5
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
6
6
|
import { luminance } from '../TSL/Common.js';
|
|
7
|
-
import { ALBEDO_EPS } from '../EngineDefaults.js';
|
|
7
|
+
import { ALBEDO_EPS, MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
8
8
|
|
|
9
9
|
// SVGF bilateral edge-stopping weight. All three φ params are relative
|
|
10
10
|
// tolerances (unitless fractions) so the filter is scale-invariant across
|
|
@@ -83,19 +83,35 @@ export class BilateralFilter extends RenderStage {
|
|
|
83
83
|
const w = options.width || 1;
|
|
84
84
|
const h = options.height || 1;
|
|
85
85
|
|
|
86
|
+
// Pre-allocate StorageTextures at max — defensive against three.js #33061
|
|
87
|
+
// (TSL compute pipeline keeps a stale GPUTextureView after setSize()).
|
|
88
|
+
|
|
86
89
|
// LinearFilter required for textureLoad codegen on StorageTextures.
|
|
87
|
-
this._storageTexA = new StorageTexture(
|
|
90
|
+
this._storageTexA = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
88
91
|
this._storageTexA.type = HalfFloatType;
|
|
89
92
|
this._storageTexA.format = RGBAFormat;
|
|
90
93
|
this._storageTexA.minFilter = LinearFilter;
|
|
91
94
|
this._storageTexA.magFilter = LinearFilter;
|
|
92
95
|
|
|
93
|
-
this._storageTexB = new StorageTexture(
|
|
96
|
+
this._storageTexB = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
94
97
|
this._storageTexB.type = HalfFloatType;
|
|
95
98
|
this._storageTexB.format = RGBAFormat;
|
|
96
99
|
this._storageTexB.minFilter = LinearFilter;
|
|
97
100
|
this._storageTexB.magFilter = LinearFilter;
|
|
98
101
|
|
|
102
|
+
this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
|
|
103
|
+
|
|
104
|
+
// Active-size RT published downstream; over-allocated storage tex sampled
|
|
105
|
+
// by UV would read the wrong region.
|
|
106
|
+
this._outputTarget = new RenderTarget( w, h, {
|
|
107
|
+
type: HalfFloatType,
|
|
108
|
+
format: RGBAFormat,
|
|
109
|
+
minFilter: LinearFilter,
|
|
110
|
+
magFilter: LinearFilter,
|
|
111
|
+
depthBuffer: false,
|
|
112
|
+
stencilBuffer: false
|
|
113
|
+
} );
|
|
114
|
+
|
|
99
115
|
this._compiled = false;
|
|
100
116
|
|
|
101
117
|
this._dispatchX = Math.ceil( w / 8 );
|
|
@@ -245,8 +261,9 @@ export class BilateralFilter extends RenderStage {
|
|
|
245
261
|
const img = inputTex.image;
|
|
246
262
|
if ( img && img.width > 0 && img.height > 0 ) {
|
|
247
263
|
|
|
248
|
-
|
|
249
|
-
|
|
264
|
+
// Compare against an active-size RT, not the fixed-2048 StorageTexture.
|
|
265
|
+
if ( img.width !== this._outputTarget.width ||
|
|
266
|
+
img.height !== this._outputTarget.height ) {
|
|
250
267
|
|
|
251
268
|
this.setSize( img.width, img.height );
|
|
252
269
|
|
|
@@ -295,7 +312,12 @@ export class BilateralFilter extends RenderStage {
|
|
|
295
312
|
|
|
296
313
|
}
|
|
297
314
|
|
|
298
|
-
|
|
315
|
+
// Copy the final result out of the over-allocated StorageTexture into
|
|
316
|
+
// the active-size RenderTarget; downstream stages UV-sample the latter.
|
|
317
|
+
this._srcRegion.max.set( this._outputTarget.width, this._outputTarget.height );
|
|
318
|
+
this.renderer.copyTextureToTexture( readTex, this._outputTarget.texture, this._srcRegion );
|
|
319
|
+
|
|
320
|
+
context.setTexture( 'bilateralFiltering:output', this._outputTarget.texture );
|
|
299
321
|
|
|
300
322
|
}
|
|
301
323
|
|
|
@@ -313,8 +335,9 @@ export class BilateralFilter extends RenderStage {
|
|
|
313
335
|
|
|
314
336
|
setSize( width, height ) {
|
|
315
337
|
|
|
316
|
-
|
|
317
|
-
this.
|
|
338
|
+
// StorageTextures stay at their max allocation (see constructor).
|
|
339
|
+
this._outputTarget.setSize( width, height );
|
|
340
|
+
this._outputTarget.texture.needsUpdate = true;
|
|
318
341
|
this.resW.value = width;
|
|
319
342
|
this.resH.value = height;
|
|
320
343
|
|
|
@@ -338,6 +361,7 @@ export class BilateralFilter extends RenderStage {
|
|
|
338
361
|
this._computeNodeB?.dispose();
|
|
339
362
|
this._storageTexA?.dispose();
|
|
340
363
|
this._storageTexB?.dispose();
|
|
364
|
+
this._outputTarget?.dispose();
|
|
341
365
|
this._readTexNode?.dispose();
|
|
342
366
|
this._normalDepthTexNode?.dispose();
|
|
343
367
|
this._albedoTexNode?.dispose();
|
package/src/Stages/EdgeFilter.js
CHANGED
|
@@ -5,6 +5,7 @@ import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
|
|
|
5
5
|
import { HalfFloatType, RGBAFormat, NearestFilter, Box2, Vector2 } from 'three';
|
|
6
6
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
7
7
|
import { REC709_LUMINANCE_COEFFICIENTS } from '../TSL/Common.js';
|
|
8
|
+
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* WebGPU Edge-Aware Filtering Stage (Compute Shader).
|
|
@@ -50,11 +51,10 @@ export class EdgeFilter extends RenderStage {
|
|
|
50
51
|
|
|
51
52
|
// Pre-allocate StorageTexture at max — defensive against three.js #33061
|
|
52
53
|
// (TSL compute pipeline re-compile returns zeros after resize).
|
|
53
|
-
const MAX_STORAGE_SIZE = 2048;
|
|
54
54
|
const w = options.width || 1;
|
|
55
55
|
const h = options.height || 1;
|
|
56
56
|
|
|
57
|
-
this._outputStorageTex = new StorageTexture(
|
|
57
|
+
this._outputStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
58
58
|
this._outputStorageTex.type = HalfFloatType;
|
|
59
59
|
this._outputStorageTex.format = RGBAFormat;
|
|
60
60
|
this._outputStorageTex.minFilter = NearestFilter;
|
|
@@ -243,7 +243,6 @@ export class EdgeFilter extends RenderStage {
|
|
|
243
243
|
|
|
244
244
|
// Copy out of the over-allocated StorageTexture into the right-sized
|
|
245
245
|
// RenderTarget; downstream stages can sample the latter.
|
|
246
|
-
this._srcRegion.min.set( 0, 0 );
|
|
247
246
|
this._srcRegion.max.set( this.outputTarget.width, this.outputTarget.height );
|
|
248
247
|
this.renderer.copyTextureToTexture( this._outputStorageTex, this.outputTarget.texture, this._srcRegion );
|
|
249
248
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Fn, vec2, vec3, vec4, float, int, uint, ivec2, uvec2, uniform, If, normalize, mat3,
|
|
2
2
|
textureLoad, textureStore, workgroupId, localId } from 'three/tsl';
|
|
3
3
|
import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
|
|
4
|
-
import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4 } from 'three';
|
|
4
|
+
import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4, Box2, Vector2 } from 'three';
|
|
5
5
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
6
|
+
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* WebGPU Motion Vector Stage (Compute Shader)
|
|
@@ -89,19 +90,24 @@ export class MotionVector extends RenderStage {
|
|
|
89
90
|
// Input texture node (swappable — no shader recompile)
|
|
90
91
|
this._normalDepthTexNode = new TextureNode();
|
|
91
92
|
|
|
92
|
-
// Write-only StorageTextures (compute output)
|
|
93
|
-
|
|
93
|
+
// Write-only StorageTextures (compute output).
|
|
94
|
+
// Pre-allocate at max — StorageTexture.setSize() destroys the GPU texture
|
|
95
|
+
// while the compute bind group keeps the stale view (three.js #33061).
|
|
96
|
+
this._screenSpaceStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
94
97
|
this._screenSpaceStorageTex.type = HalfFloatType;
|
|
95
98
|
this._screenSpaceStorageTex.format = RGBAFormat;
|
|
96
99
|
this._screenSpaceStorageTex.minFilter = NearestFilter;
|
|
97
100
|
this._screenSpaceStorageTex.magFilter = NearestFilter;
|
|
98
101
|
|
|
99
|
-
this._worldSpaceStorageTex = new StorageTexture(
|
|
102
|
+
this._worldSpaceStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
100
103
|
this._worldSpaceStorageTex.type = HalfFloatType;
|
|
101
104
|
this._worldSpaceStorageTex.format = RGBAFormat;
|
|
102
105
|
this._worldSpaceStorageTex.minFilter = NearestFilter;
|
|
103
106
|
this._worldSpaceStorageTex.magFilter = NearestFilter;
|
|
104
107
|
|
|
108
|
+
// Reused per-copy: copy only the active region out of the over-alloc texs.
|
|
109
|
+
this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
|
|
110
|
+
|
|
105
111
|
// Readable RenderTargets (copy destinations — published to context)
|
|
106
112
|
const rtOpts = {
|
|
107
113
|
type: HalfFloatType,
|
|
@@ -464,9 +470,11 @@ export class MotionVector extends RenderStage {
|
|
|
464
470
|
this.renderer.compute( this._worldSpaceComputeNode );
|
|
465
471
|
|
|
466
472
|
// Copy StorageTextures → RenderTargets (cross-dispatch reads from
|
|
467
|
-
// StorageTexture return zeros — must use RenderTarget for downstream stages)
|
|
468
|
-
|
|
469
|
-
this.
|
|
473
|
+
// StorageTexture return zeros — must use RenderTarget for downstream stages).
|
|
474
|
+
// srcRegion = active size; StorageTextures are over-allocated at 2048.
|
|
475
|
+
this._srcRegion.max.set( this.screenSpaceTarget.width, this.screenSpaceTarget.height );
|
|
476
|
+
this.renderer.copyTextureToTexture( this._screenSpaceStorageTex, this.screenSpaceTarget.texture, this._srcRegion );
|
|
477
|
+
this.renderer.copyTextureToTexture( this._worldSpaceStorageTex, this.worldSpaceTarget.texture, this._srcRegion );
|
|
470
478
|
|
|
471
479
|
// Publish RenderTarget textures to context
|
|
472
480
|
context.setTexture( 'motionVector:screenSpace', this.screenSpaceTarget.texture );
|
|
@@ -501,8 +509,7 @@ export class MotionVector extends RenderStage {
|
|
|
501
509
|
|
|
502
510
|
setSize( width, height ) {
|
|
503
511
|
|
|
504
|
-
|
|
505
|
-
this._worldSpaceStorageTex.setSize( width, height );
|
|
512
|
+
// StorageTextures stay at their max allocation (see constructor).
|
|
506
513
|
this.screenSpaceTarget.setSize( width, height );
|
|
507
514
|
this.screenSpaceTarget.texture.needsUpdate = true;
|
|
508
515
|
this.worldSpaceTarget.setSize( width, height );
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Fn, vec3, vec4, float, int, uint, uvec2, uniform, normalize, mat3, storage, If,
|
|
2
2
|
textureStore, workgroupId, localId } from 'three/tsl';
|
|
3
3
|
import { RenderTarget, StorageTexture } from 'three/webgpu';
|
|
4
|
-
import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4 } from 'three';
|
|
4
|
+
import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4, Box2, Vector2 } from 'three';
|
|
5
5
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
6
|
+
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
6
7
|
import { Ray, HitInfo } from '../TSL/Struct.js';
|
|
7
8
|
import { traverseBVH } from '../TSL/BVHTraversal.js';
|
|
8
9
|
|
|
@@ -44,12 +45,15 @@ export class NormalDepth extends RenderStage {
|
|
|
44
45
|
const w = options.width || 1;
|
|
45
46
|
const h = options.height || 1;
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
// StorageTexture stays at max alloc — see resize crash fix (three.js #33061).
|
|
49
|
+
this._outputStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
48
50
|
this._outputStorageTex.type = HalfFloatType;
|
|
49
51
|
this._outputStorageTex.format = RGBAFormat;
|
|
50
52
|
this._outputStorageTex.minFilter = NearestFilter;
|
|
51
53
|
this._outputStorageTex.magFilter = NearestFilter;
|
|
52
54
|
|
|
55
|
+
this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
|
|
56
|
+
|
|
53
57
|
// Ping-pong RTs share format with the StorageTexture so copyTextureToTexture works.
|
|
54
58
|
const rtOpts = {
|
|
55
59
|
type: HalfFloatType,
|
|
@@ -252,13 +256,16 @@ export class NormalDepth extends RenderStage {
|
|
|
252
256
|
const prevRT = this._currentIdx === 0 ? this._rtB : this._rtA;
|
|
253
257
|
|
|
254
258
|
this.renderer.compute( this._computeNode );
|
|
255
|
-
|
|
259
|
+
|
|
260
|
+
// Copy only the active region out of the over-allocated StorageTexture.
|
|
261
|
+
this._srcRegion.max.set( writeRT.width, writeRT.height );
|
|
262
|
+
this.renderer.copyTextureToTexture( this._outputStorageTex, writeRT.texture, this._srcRegion );
|
|
256
263
|
|
|
257
264
|
// First dispatch: seed prev from current so ASVGF doesn't see false
|
|
258
265
|
// disocclusion on frame 1.
|
|
259
266
|
if ( ! this._hasHistory ) {
|
|
260
267
|
|
|
261
|
-
this.renderer.copyTextureToTexture( this._outputStorageTex, prevRT.texture );
|
|
268
|
+
this.renderer.copyTextureToTexture( this._outputStorageTex, prevRT.texture, this._srcRegion );
|
|
262
269
|
this._hasHistory = true;
|
|
263
270
|
|
|
264
271
|
}
|
|
@@ -279,9 +286,14 @@ export class NormalDepth extends RenderStage {
|
|
|
279
286
|
|
|
280
287
|
setSize( width, height ) {
|
|
281
288
|
|
|
282
|
-
|
|
289
|
+
// StorageTexture stays at its max allocation (see constructor).
|
|
290
|
+
// RenderTarget.setSize() updates width/height but does NOT bump
|
|
291
|
+
// texture.version, so copyTextureToTexture's GPU texture would stay at
|
|
292
|
+
// the old size — needsUpdate forces the resize to take effect.
|
|
283
293
|
this._rtA.setSize( width, height );
|
|
294
|
+
this._rtA.texture.needsUpdate = true;
|
|
284
295
|
this._rtB.setSize( width, height );
|
|
296
|
+
this._rtB.texture.needsUpdate = true;
|
|
285
297
|
this._hasHistory = false;
|
|
286
298
|
this.resolutionWidth.value = width;
|
|
287
299
|
this.resolutionHeight.value = height;
|