rayzee 6.5.0 → 7.1.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 +7624 -7063
- 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 +26 -9
- package/src/PathTracerApp.js +118 -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 +6 -0
- package/src/Processor/KernelManager.js +277 -0
- package/src/Processor/PackedRayBuffer.js +291 -0
- package/src/Processor/QueueManager.js +173 -0
- package/src/Processor/SceneProcessor.js +1 -0
- package/src/Processor/ShaderBuilder.js +11 -317
- package/src/Processor/StorageTexturePool.js +29 -15
- package/src/Processor/VRAMTracker.js +169 -0
- package/src/Processor/utils.js +11 -110
- package/src/RenderSettings.js +0 -3
- package/src/Stages/ASVGF.js +151 -78
- 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/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 +111 -0
- package/src/TSL/LightsSampling.js +2 -2
- package/src/TSL/PathTracerCore.js +43 -1039
- package/src/TSL/ShadeKernel.js +876 -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/OverlayManager.js +7 -22
- package/src/managers/UniformManager.js +0 -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/Stages/ASVGF.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { Fn, vec3, vec4, float, int, uint, ivec2, uvec2, uniform,
|
|
2
|
-
If, dot, max, min, abs, mix, pow, exp,
|
|
2
|
+
If, dot, max, min, abs, mix, pow, exp, sqrt,
|
|
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
|
-
* ASVGF — SVGF temporal
|
|
11
|
+
* ASVGF — SVGF temporal denoising with albedo demodulation + adaptive-α.
|
|
12
12
|
*
|
|
13
|
-
* Adaptive
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* 1
|
|
13
|
+
* Adaptive temporal gradient (the "A"): the gradient kernel compares this
|
|
14
|
+
* frame's demodulated lighting against the reprojected accumulated history,
|
|
15
|
+
* floored by a per-pixel σ (max(Δ − k·σ, 0)), max-normalised and squared
|
|
16
|
+
* (Q2RTX get_gradient). gradientStrength scales it into effectiveAlpha =
|
|
17
|
+
* mix(baseAlpha, 1, gradient·gradientStrength) — high change ⇒ drop history
|
|
18
|
+
* (anti-lag). gradientStrength=0 (base presets) keeps pure EMA; the
|
|
19
|
+
* *_adaptive presets enable it.
|
|
18
20
|
*
|
|
19
21
|
* Reads: pathtracer:color, pathtracer:albedo, pathtracer:normalDepth,
|
|
20
22
|
* pathtracer:prevNormalDepth, motionVector:screenSpace
|
|
@@ -34,7 +36,10 @@ export class ASVGF extends RenderStage {
|
|
|
34
36
|
|
|
35
37
|
this.temporalAlpha = uniform( options.temporalAlpha ?? 0.0 );
|
|
36
38
|
this.gradientStrength = uniform( options.gradientStrength ?? 0.0 );
|
|
37
|
-
|
|
39
|
+
// σ multiplier for the per-pixel noise floor (NRD luminanceSigmaScale ≈ 2).
|
|
40
|
+
this.gradientSigmaScale = uniform( options.gradientSigmaScale ?? 2.0 );
|
|
41
|
+
// Secondary relative floor on the normalised gradient (0 = rely on σ alone).
|
|
42
|
+
this.gradientNoiseFloor = uniform( options.gradientNoiseFloor ?? 0.0 );
|
|
38
43
|
this.maxAccumFrames = uniform( options.maxAccumFrames ?? 32.0 );
|
|
39
44
|
|
|
40
45
|
this.resW = uniform( options.width || 1 );
|
|
@@ -43,7 +48,6 @@ export class ASVGF extends RenderStage {
|
|
|
43
48
|
this.temporalEnabledU = uniform( 1.0 );
|
|
44
49
|
|
|
45
50
|
this._colorTexNode = new TextureNode();
|
|
46
|
-
this._prevColorTexNode = new TextureNode();
|
|
47
51
|
this._albedoTexNode = new TextureNode();
|
|
48
52
|
this._motionTexNode = new TextureNode();
|
|
49
53
|
this._normalDepthTexNode = new TextureNode();
|
|
@@ -57,41 +61,60 @@ export class ASVGF extends RenderStage {
|
|
|
57
61
|
// FloatType for ping-pong: demodulated lighting on dark materials
|
|
58
62
|
// (lighting ≈ color/0.01) exceeds HalfFloat's 65k cap on HDR.
|
|
59
63
|
// LinearFilter is required for textureLoad codegen on StorageTextures.
|
|
60
|
-
this._temporalTexA = new StorageTexture(
|
|
64
|
+
this._temporalTexA = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
61
65
|
this._temporalTexA.type = FloatType;
|
|
62
66
|
this._temporalTexA.format = RGBAFormat;
|
|
63
67
|
this._temporalTexA.minFilter = LinearFilter;
|
|
64
68
|
this._temporalTexA.magFilter = LinearFilter;
|
|
65
69
|
|
|
66
|
-
this._temporalTexB = new StorageTexture(
|
|
70
|
+
this._temporalTexB = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
67
71
|
this._temporalTexB.type = FloatType;
|
|
68
72
|
this._temporalTexB.format = RGBAFormat;
|
|
69
73
|
this._temporalTexB.minFilter = LinearFilter;
|
|
70
74
|
this._temporalTexB.magFilter = LinearFilter;
|
|
71
75
|
|
|
72
|
-
this._outputModulatedTex = new StorageTexture(
|
|
76
|
+
this._outputModulatedTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
73
77
|
this._outputModulatedTex.type = FloatType;
|
|
74
78
|
this._outputModulatedTex.format = RGBAFormat;
|
|
75
79
|
this._outputModulatedTex.minFilter = LinearFilter;
|
|
76
80
|
this._outputModulatedTex.magFilter = LinearFilter;
|
|
77
81
|
|
|
78
|
-
this._gradientStorageTex = new StorageTexture(
|
|
82
|
+
this._gradientStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
79
83
|
this._gradientStorageTex.type = HalfFloatType;
|
|
80
84
|
this._gradientStorageTex.format = RGBAFormat;
|
|
81
85
|
this._gradientStorageTex.minFilter = LinearFilter;
|
|
82
86
|
this._gradientStorageTex.magFilter = LinearFilter;
|
|
83
87
|
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
this.
|
|
88
|
+
// Over-allocated StorageTextures are sampled by UV downstream; copy the
|
|
89
|
+
// active region into right-sized RTs and publish those instead.
|
|
90
|
+
this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
|
|
91
|
+
|
|
92
|
+
this._demodulatedRT = new RenderTarget( w, h, {
|
|
87
93
|
type: FloatType,
|
|
88
94
|
format: RGBAFormat,
|
|
89
|
-
minFilter:
|
|
90
|
-
magFilter:
|
|
95
|
+
minFilter: LinearFilter,
|
|
96
|
+
magFilter: LinearFilter,
|
|
97
|
+
depthBuffer: false,
|
|
98
|
+
stencilBuffer: false
|
|
99
|
+
} );
|
|
100
|
+
|
|
101
|
+
this._outputRT = new RenderTarget( w, h, {
|
|
102
|
+
type: FloatType,
|
|
103
|
+
format: RGBAFormat,
|
|
104
|
+
minFilter: LinearFilter,
|
|
105
|
+
magFilter: LinearFilter,
|
|
106
|
+
depthBuffer: false,
|
|
107
|
+
stencilBuffer: false
|
|
108
|
+
} );
|
|
109
|
+
|
|
110
|
+
this._gradientRT = new RenderTarget( w, h, {
|
|
111
|
+
type: HalfFloatType,
|
|
112
|
+
format: RGBAFormat,
|
|
113
|
+
minFilter: LinearFilter,
|
|
114
|
+
magFilter: LinearFilter,
|
|
91
115
|
depthBuffer: false,
|
|
92
116
|
stencilBuffer: false
|
|
93
117
|
} );
|
|
94
|
-
this._prevColorReady = false;
|
|
95
118
|
|
|
96
119
|
this.currentMoments = 0; // 0 = write A, read B; 1 = write B, read A
|
|
97
120
|
this._compiled = false;
|
|
@@ -105,7 +128,7 @@ export class ASVGF extends RenderStage {
|
|
|
105
128
|
this.showHeatmap = false;
|
|
106
129
|
this.debugMode = uniform( 0, 'int' );
|
|
107
130
|
|
|
108
|
-
this._heatmapStorageTex = new StorageTexture(
|
|
131
|
+
this._heatmapStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
109
132
|
this._heatmapStorageTex.type = FloatType;
|
|
110
133
|
this._heatmapStorageTex.format = RGBAFormat;
|
|
111
134
|
this._heatmapStorageTex.minFilter = NearestFilter;
|
|
@@ -131,17 +154,24 @@ export class ASVGF extends RenderStage {
|
|
|
131
154
|
|
|
132
155
|
}
|
|
133
156
|
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
157
|
+
// Adaptive temporal gradient: per pixel, compare this frame's DEMODULATED
|
|
158
|
+
// lighting against the motion-reprojected accumulated history (low-noise),
|
|
159
|
+
// floored by a per-pixel σ from the 5×5 spatial neighbourhood. The σ floor
|
|
160
|
+
// is what the old fixed 0.15 constant couldn't give — on a static scene the
|
|
161
|
+
// 1-SPP sample sits within ±k·σ of the converged estimate so the gradient
|
|
162
|
+
// reads ~0 (no convergence penalty); only real change (moving light, anim,
|
|
163
|
+
// disocclusion) exceeds it. Demodulation (vs raw color) keeps albedo/texture
|
|
164
|
+
// edges from false-firing. Output .x feeds effectiveAlpha in the temporal pass.
|
|
139
165
|
_buildGradientCompute() {
|
|
140
166
|
|
|
141
167
|
const colorTex = this._colorTexNode;
|
|
142
|
-
const
|
|
168
|
+
const albedoTex = this._albedoTexNode;
|
|
143
169
|
const motionTex = this._motionTexNode;
|
|
170
|
+
// Accumulated history (demodulated lighting in .xyz, history count in .w).
|
|
171
|
+
// Same node the temporal pass reads; set to readTemporal before dispatch.
|
|
172
|
+
const histTex = this._readTemporalTexNode;
|
|
144
173
|
const noiseFloor = this.gradientNoiseFloor;
|
|
174
|
+
const sigmaScale = this.gradientSigmaScale;
|
|
145
175
|
const gradientStorageTex = this._gradientStorageTex;
|
|
146
176
|
const resW = this.resW;
|
|
147
177
|
const resH = this.resH;
|
|
@@ -154,7 +184,7 @@ export class ASVGF extends RenderStage {
|
|
|
154
184
|
const WG_THREADS = WG_SIZE * WG_SIZE; // 64
|
|
155
185
|
|
|
156
186
|
const sharedCurLum = workgroupArray( 'float', TILE_TOTAL );
|
|
157
|
-
const
|
|
187
|
+
const sharedHistLum = workgroupArray( 'float', TILE_TOTAL );
|
|
158
188
|
|
|
159
189
|
const computeFn = Fn( () => {
|
|
160
190
|
|
|
@@ -173,20 +203,26 @@ export class ASVGF extends RenderStage {
|
|
|
173
203
|
const gxL = tileOriginX.add( int( sx ) ).clamp( int( 0 ), int( resW ).sub( 1 ) );
|
|
174
204
|
const gyL = tileOriginY.add( int( sy ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
175
205
|
|
|
206
|
+
// Demodulated current lighting luminance (matches the temporal pass:
|
|
207
|
+
// safeAlbedo = max(albedo, ALBEDO_EPS) keeps sky/dark-material round-trip).
|
|
176
208
|
const curColor = textureLoad( colorTex, ivec2( gxL, gyL ) ).xyz;
|
|
177
|
-
|
|
178
|
-
|
|
209
|
+
const curAlbedo = textureLoad( albedoTex, ivec2( gxL, gyL ) ).xyz;
|
|
210
|
+
const curLighting = curColor.div( max( curAlbedo, vec3( ALBEDO_EPS ) ) );
|
|
211
|
+
const curLum = luminance( curLighting ).toVar();
|
|
212
|
+
sharedCurLum.element( k ).assign( curLum );
|
|
213
|
+
|
|
214
|
+
// Reproject the accumulated history to this pixel; read its lighting
|
|
215
|
+
// luminance. Invalid motion → mirror current so the delta is 0
|
|
216
|
+
// (disocclusion handled by the temporal pass's geometric gate).
|
|
179
217
|
const motion = textureLoad( motionTex, ivec2( gxL, gyL ) );
|
|
180
218
|
const prevXf = float( gxL ).sub( motion.x.mul( resW ) );
|
|
181
219
|
const prevYf = float( gyL ).sub( motion.y.mul( resH ) );
|
|
182
220
|
const prevX = int( prevXf ).clamp( int( 0 ), int( resW ).sub( 1 ) );
|
|
183
221
|
const prevY = int( prevYf ).clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
184
|
-
// Invalid prev → mirror current so the diff contributes 0;
|
|
185
|
-
// disocclusion is handled by the geometric gate downstream.
|
|
186
222
|
const motionValid = motion.w.greaterThan( 0.5 );
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
223
|
+
const histLighting = textureLoad( histTex, ivec2( prevX, prevY ) ).xyz;
|
|
224
|
+
const histLum = motionValid.select( luminance( histLighting ), curLum );
|
|
225
|
+
sharedHistLum.element( k ).assign( histLum );
|
|
190
226
|
|
|
191
227
|
};
|
|
192
228
|
|
|
@@ -214,8 +250,33 @@ export class ASVGF extends RenderStage {
|
|
|
214
250
|
|
|
215
251
|
If( gx.lessThan( int( resW ) ).and( gy.lessThan( int( resH ) ) ), () => {
|
|
216
252
|
|
|
217
|
-
|
|
218
|
-
const
|
|
253
|
+
// Pass 1 — per-pixel noise σ from the 5×5 (demodulated) neighbourhood.
|
|
254
|
+
const sumLum = float( 0.0 ).toVar();
|
|
255
|
+
const sumLumSq = float( 0.0 ).toVar();
|
|
256
|
+
|
|
257
|
+
for ( let dy = - TILE_BORDER; dy <= TILE_BORDER; dy ++ ) {
|
|
258
|
+
|
|
259
|
+
for ( let dx = - TILE_BORDER; dx <= TILE_BORDER; dx ++ ) {
|
|
260
|
+
|
|
261
|
+
const idx = ly.add( uint( TILE_BORDER + dy ) )
|
|
262
|
+
.mul( uint( TILE_W ) )
|
|
263
|
+
.add( lx.add( uint( TILE_BORDER + dx ) ) );
|
|
264
|
+
const cL = sharedCurLum.element( idx );
|
|
265
|
+
sumLum.addAssign( cL );
|
|
266
|
+
sumLumSq.addAssign( cL.mul( cL ) );
|
|
267
|
+
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const meanLum = sumLum.div( 25.0 );
|
|
273
|
+
const variance = max( sumLumSq.div( 25.0 ).sub( meanLum.mul( meanLum ) ), float( 0.0 ) );
|
|
274
|
+
const sigmaFloor = sqrt( variance ).mul( sigmaScale ).toVar();
|
|
275
|
+
|
|
276
|
+
// Pass 2 — 5×5 average of the squared, σ-floored, max-normalised
|
|
277
|
+
// temporal change. Squaring (Q2RTX get_gradient) suppresses residual
|
|
278
|
+
// MC noise while staying reactive in high contrast.
|
|
279
|
+
const sumG = float( 0.0 ).toVar();
|
|
219
280
|
|
|
220
281
|
for ( let dy = - TILE_BORDER; dy <= TILE_BORDER; dy ++ ) {
|
|
221
282
|
|
|
@@ -225,26 +286,35 @@ export class ASVGF extends RenderStage {
|
|
|
225
286
|
.mul( uint( TILE_W ) )
|
|
226
287
|
.add( lx.add( uint( TILE_BORDER + dx ) ) );
|
|
227
288
|
const cL = sharedCurLum.element( idx );
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
289
|
+
const hL = sharedHistLum.element( idx );
|
|
290
|
+
const floored = max( abs( cL.sub( hL ) ).sub( sigmaFloor ), float( 0.0 ) );
|
|
291
|
+
const g = floored.div( max( max( cL, hL ), float( 0.001 ) ) );
|
|
292
|
+
// Optional secondary relative floor (noiseFloor=0 → no-op).
|
|
293
|
+
const gf = max( g.sub( noiseFloor ), float( 0.0 ) )
|
|
294
|
+
.div( max( float( 1.0 ).sub( noiseFloor ), float( 0.0001 ) ) );
|
|
295
|
+
sumG.addAssign( gf.mul( gf ) );
|
|
231
296
|
|
|
232
297
|
}
|
|
233
298
|
|
|
234
299
|
}
|
|
235
300
|
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
301
|
+
const gradientRaw = sumG.div( 25.0 ).clamp( 0.0, 1.0 ).toVar();
|
|
302
|
+
|
|
303
|
+
// Trust the gradient only once enough history has accumulated at the
|
|
304
|
+
// reprojected centre — early frames have noisy history → false fires.
|
|
305
|
+
const cMotion = textureLoad( motionTex, ivec2( gx, gy ) );
|
|
306
|
+
const cPrevX = int( float( gx ).sub( cMotion.x.mul( resW ) ) ).clamp( int( 0 ), int( resW ).sub( 1 ) );
|
|
307
|
+
const cPrevY = int( float( gy ).sub( cMotion.y.mul( resH ) ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
308
|
+
const histLen = cMotion.w.greaterThan( 0.5 )
|
|
309
|
+
.select( textureLoad( histTex, ivec2( cPrevX, cPrevY ) ).w, float( 0.0 ) );
|
|
310
|
+
const confidence = histLen.div( 4.0 ).clamp( 0.0, 1.0 );
|
|
311
|
+
|
|
312
|
+
const gradient = gradientRaw.mul( confidence );
|
|
243
313
|
|
|
244
314
|
textureStore(
|
|
245
315
|
gradientStorageTex,
|
|
246
316
|
uvec2( uint( gx ), uint( gy ) ),
|
|
247
|
-
vec4( gradient,
|
|
317
|
+
vec4( gradient, gradientRaw, sigmaFloor, 1.0 )
|
|
248
318
|
).toWriteOnly();
|
|
249
319
|
|
|
250
320
|
} );
|
|
@@ -581,8 +651,9 @@ export class ASVGF extends RenderStage {
|
|
|
581
651
|
const img = colorTex.image;
|
|
582
652
|
if ( img && img.width > 0 && img.height > 0 ) {
|
|
583
653
|
|
|
584
|
-
|
|
585
|
-
|
|
654
|
+
// Compare against an active-size RT, not the fixed-2048 StorageTexture.
|
|
655
|
+
if ( img.width !== this._outputRT.width ||
|
|
656
|
+
img.height !== this._outputRT.height ) {
|
|
586
657
|
|
|
587
658
|
this.setSize( img.width, img.height );
|
|
588
659
|
|
|
@@ -603,12 +674,6 @@ export class ASVGF extends RenderStage {
|
|
|
603
674
|
const writeTemporal = this.currentMoments === 0
|
|
604
675
|
? this._temporalTexA : this._temporalTexB;
|
|
605
676
|
|
|
606
|
-
// Before first copy seeds the cache, alias current so the gradient
|
|
607
|
-
// sees zero diff (no false boost).
|
|
608
|
-
this._prevColorTexNode.value = this._prevColorReady
|
|
609
|
-
? this._prevColorRT.texture
|
|
610
|
-
: colorTex;
|
|
611
|
-
|
|
612
677
|
// First-frame compile while StorageTexture-typed nodes still hold
|
|
613
678
|
// EmptyTexture, so textureLoad codegen emits the required `level`
|
|
614
679
|
// parameter. Binding StorageTextures only AFTER compile keeps the
|
|
@@ -638,18 +703,21 @@ export class ASVGF extends RenderStage {
|
|
|
638
703
|
|
|
639
704
|
this.renderer.compute( writeNode );
|
|
640
705
|
|
|
641
|
-
//
|
|
642
|
-
//
|
|
706
|
+
// Copy active region out of the over-allocated StorageTextures into
|
|
707
|
+
// right-sized RTs; downstream stages UV-sample these.
|
|
708
|
+
this._srcRegion.max.set( this.resW.value, this.resH.value );
|
|
709
|
+
|
|
710
|
+
this.renderer.copyTextureToTexture( writeTemporal, this._demodulatedRT.texture, this._srcRegion );
|
|
711
|
+
this.renderer.copyTextureToTexture( this._outputModulatedTex, this._outputRT.texture, this._srcRegion );
|
|
643
712
|
if ( needsGradient ) {
|
|
644
713
|
|
|
645
|
-
this.renderer.copyTextureToTexture(
|
|
646
|
-
this._prevColorReady = true;
|
|
714
|
+
this.renderer.copyTextureToTexture( this._gradientStorageTex, this._gradientRT.texture, this._srcRegion );
|
|
647
715
|
|
|
648
716
|
}
|
|
649
717
|
|
|
650
|
-
context.setTexture( 'asvgf:demodulated',
|
|
651
|
-
context.setTexture( 'asvgf:output', this.
|
|
652
|
-
context.setTexture( 'asvgf:gradient', this.
|
|
718
|
+
context.setTexture( 'asvgf:demodulated', this._demodulatedRT.texture );
|
|
719
|
+
context.setTexture( 'asvgf:output', this._outputRT.texture );
|
|
720
|
+
context.setTexture( 'asvgf:gradient', this._gradientRT.texture );
|
|
653
721
|
|
|
654
722
|
this.currentMoments = 1 - this.currentMoments;
|
|
655
723
|
|
|
@@ -665,7 +733,8 @@ export class ASVGF extends RenderStage {
|
|
|
665
733
|
this._heatmapGradientTexNode.value = this._gradientStorageTex;
|
|
666
734
|
|
|
667
735
|
this.renderer.compute( this._heatmapComputeNode );
|
|
668
|
-
this.
|
|
736
|
+
this._srcRegion.max.set( this.heatmapTarget.width, this.heatmapTarget.height );
|
|
737
|
+
this.renderer.copyTextureToTexture( this._heatmapStorageTex, this.heatmapTarget.texture, this._srcRegion );
|
|
669
738
|
|
|
670
739
|
}
|
|
671
740
|
|
|
@@ -688,6 +757,7 @@ export class ASVGF extends RenderStage {
|
|
|
688
757
|
if ( ! params ) return;
|
|
689
758
|
if ( params.temporalAlpha !== undefined ) this.temporalAlpha.value = params.temporalAlpha;
|
|
690
759
|
if ( params.gradientStrength !== undefined ) this.gradientStrength.value = params.gradientStrength;
|
|
760
|
+
if ( params.gradientSigmaScale !== undefined ) this.gradientSigmaScale.value = params.gradientSigmaScale;
|
|
691
761
|
if ( params.gradientNoiseFloor !== undefined ) this.gradientNoiseFloor.value = params.gradientNoiseFloor;
|
|
692
762
|
if ( params.maxAccumFrames !== undefined ) this.maxAccumFrames.value = params.maxAccumFrames;
|
|
693
763
|
if ( params.debugMode !== undefined ) this.debugMode.value = params.debugMode;
|
|
@@ -697,20 +767,20 @@ export class ASVGF extends RenderStage {
|
|
|
697
767
|
resetTemporalData() {
|
|
698
768
|
|
|
699
769
|
this.currentMoments = 0;
|
|
700
|
-
// Drop cache so post-reset frames don't see pre-reset prev color.
|
|
701
|
-
this._prevColorReady = false;
|
|
702
770
|
|
|
703
771
|
}
|
|
704
772
|
|
|
705
773
|
setSize( width, height ) {
|
|
706
774
|
|
|
707
|
-
|
|
708
|
-
this.
|
|
709
|
-
this.
|
|
710
|
-
this.
|
|
711
|
-
this.
|
|
712
|
-
this.
|
|
775
|
+
// StorageTextures stay at max alloc — see resize crash fix (three.js #33061).
|
|
776
|
+
this._demodulatedRT.setSize( width, height );
|
|
777
|
+
this._demodulatedRT.texture.needsUpdate = true;
|
|
778
|
+
this._outputRT.setSize( width, height );
|
|
779
|
+
this._outputRT.texture.needsUpdate = true;
|
|
780
|
+
this._gradientRT.setSize( width, height );
|
|
781
|
+
this._gradientRT.texture.needsUpdate = true;
|
|
713
782
|
this.heatmapTarget.setSize( width, height );
|
|
783
|
+
this.heatmapTarget.texture.needsUpdate = true;
|
|
714
784
|
this.resW.value = width;
|
|
715
785
|
this.resH.value = height;
|
|
716
786
|
|
|
@@ -721,9 +791,11 @@ export class ASVGF extends RenderStage {
|
|
|
721
791
|
this._temporalNodeB.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
|
|
722
792
|
this._heatmapComputeNode.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
|
|
723
793
|
|
|
724
|
-
//
|
|
725
|
-
|
|
726
|
-
|
|
794
|
+
// StorageTextures are over-allocated (never reallocated on resize), so the
|
|
795
|
+
// compute kernels stay valid — do NOT reset _compiled. Re-running the warmup
|
|
796
|
+
// would dispatch both temporal ping-pong nodes while _readTemporalTexNode still
|
|
797
|
+
// aliases one node's write target, producing a "write-only storage +
|
|
798
|
+
// TextureBinding in same synchronization scope" validation error.
|
|
727
799
|
|
|
728
800
|
}
|
|
729
801
|
|
|
@@ -743,13 +815,14 @@ export class ASVGF extends RenderStage {
|
|
|
743
815
|
this._temporalTexB?.dispose();
|
|
744
816
|
this._outputModulatedTex?.dispose();
|
|
745
817
|
this._gradientStorageTex?.dispose();
|
|
746
|
-
this.
|
|
818
|
+
this._demodulatedRT?.dispose();
|
|
819
|
+
this._outputRT?.dispose();
|
|
820
|
+
this._gradientRT?.dispose();
|
|
747
821
|
this._heatmapComputeNode?.dispose();
|
|
748
822
|
this._heatmapStorageTex?.dispose();
|
|
749
823
|
this.heatmapTarget?.dispose();
|
|
750
824
|
|
|
751
825
|
this._colorTexNode?.dispose();
|
|
752
|
-
this._prevColorTexNode?.dispose();
|
|
753
826
|
this._albedoTexNode?.dispose();
|
|
754
827
|
this._motionTexNode?.dispose();
|
|
755
828
|
this._normalDepthTexNode?.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 );
|