rayzee 6.1.0 → 6.3.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 +1 -1
- package/dist/rayzee.es.js +1060 -1040
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +131 -76
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +27 -16
- package/src/Stages/ASVGF.js +304 -185
- package/src/Stages/AdaptiveSampling.js +6 -6
- package/src/Stages/BilateralFilter.js +85 -69
- package/src/Stages/Compositor.js +1 -0
- package/src/Stages/NormalDepth.js +56 -118
- package/src/TSL/BVHTraversal.js +114 -49
- package/src/TSL/Common.js +3 -4
- package/src/TSL/Debugger.js +6 -6
- package/src/TSL/Displacement.js +13 -13
- package/src/TSL/Environment.js +12 -12
- package/src/TSL/LightsCore.js +14 -15
- package/src/TSL/LightsDirect.js +2 -5
- package/src/TSL/LightsIndirect.js +20 -21
- package/src/TSL/LightsSampling.js +19 -20
- package/src/TSL/MaterialEvaluation.js +2 -2
- package/src/TSL/MaterialProperties.js +5 -5
- package/src/TSL/MaterialTransmission.js +4 -6
- package/src/TSL/PathTracer.js +24 -9
- package/src/TSL/PathTracerCore.js +13 -29
- package/src/TSL/TextureSampling.js +3 -3
- package/src/managers/DenoisingManager.js +12 -2
package/src/Stages/ASVGF.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { Fn, vec3, vec4, float, int, uint, ivec2, uvec2, uniform,
|
|
2
|
-
If, dot, max, min, abs, mix,
|
|
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
5
|
import { HalfFloatType, FloatType, RGBAFormat, NearestFilter, LinearFilter } 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
9
|
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
-
* + 3×3 colour-distance disocclusion rejection. Ping-pong StorageTextures.
|
|
11
|
+
* ASVGF — SVGF temporal + spatial denoising with albedo demodulation.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Adaptive-α infrastructure (gradient compute, prev-color cache,
|
|
14
|
+
* gradientStrength uniform) is in place but disabled by default —
|
|
15
|
+
* gradientStrength=0 makes adaptiveBoost=0 so effectiveAlpha is the pure
|
|
16
|
+
* EMA 1/(history+1). The fixed-noise-floor implementation misfires on
|
|
17
|
+
* 1-SPP raw input; a per-pixel variance-aware floor is the proper fix.
|
|
18
|
+
*
|
|
19
|
+
* Reads: pathtracer:color, pathtracer:albedo, pathtracer:normalDepth,
|
|
20
|
+
* pathtracer:prevNormalDepth, motionVector:screenSpace
|
|
21
|
+
* Publishes: asvgf:output (modulated), asvgf:demodulated (lighting + history),
|
|
22
|
+
* asvgf:gradient
|
|
16
23
|
*/
|
|
17
24
|
export class ASVGF extends RenderStage {
|
|
18
25
|
|
|
@@ -25,43 +32,67 @@ export class ASVGF extends RenderStage {
|
|
|
25
32
|
|
|
26
33
|
this.renderer = renderer;
|
|
27
34
|
|
|
28
|
-
this.temporalAlpha = uniform( options.temporalAlpha ?? 0.
|
|
29
|
-
this.
|
|
35
|
+
this.temporalAlpha = uniform( options.temporalAlpha ?? 0.0 );
|
|
36
|
+
this.gradientStrength = uniform( options.gradientStrength ?? 0.0 );
|
|
37
|
+
this.gradientNoiseFloor = uniform( options.gradientNoiseFloor ?? 0.15 );
|
|
30
38
|
this.maxAccumFrames = uniform( options.maxAccumFrames ?? 32.0 );
|
|
31
|
-
this.varianceClip = uniform( options.varianceClip ?? 1.0 );
|
|
32
39
|
|
|
33
40
|
this.resW = uniform( options.width || 1 );
|
|
34
41
|
this.resH = uniform( options.height || 1 );
|
|
35
42
|
|
|
36
|
-
this.temporalEnabled = true;
|
|
37
43
|
this.temporalEnabledU = uniform( 1.0 );
|
|
38
44
|
|
|
39
45
|
this._colorTexNode = new TextureNode();
|
|
46
|
+
this._prevColorTexNode = new TextureNode();
|
|
47
|
+
this._albedoTexNode = new TextureNode();
|
|
40
48
|
this._motionTexNode = new TextureNode();
|
|
49
|
+
this._normalDepthTexNode = new TextureNode();
|
|
50
|
+
this._prevNormalDepthTexNode = new TextureNode();
|
|
41
51
|
this._readTemporalTexNode = new TextureNode();
|
|
52
|
+
this._gradientReadTexNode = new TextureNode();
|
|
42
53
|
|
|
43
54
|
const w = options.width || 1;
|
|
44
55
|
const h = options.height || 1;
|
|
45
56
|
|
|
46
|
-
//
|
|
57
|
+
// FloatType for ping-pong: demodulated lighting on dark materials
|
|
58
|
+
// (lighting ≈ color/0.01) exceeds HalfFloat's 65k cap on HDR.
|
|
59
|
+
// LinearFilter is required for textureLoad codegen on StorageTextures.
|
|
47
60
|
this._temporalTexA = new StorageTexture( w, h );
|
|
48
|
-
this._temporalTexA.type =
|
|
61
|
+
this._temporalTexA.type = FloatType;
|
|
49
62
|
this._temporalTexA.format = RGBAFormat;
|
|
50
63
|
this._temporalTexA.minFilter = LinearFilter;
|
|
51
64
|
this._temporalTexA.magFilter = LinearFilter;
|
|
52
65
|
|
|
53
66
|
this._temporalTexB = new StorageTexture( w, h );
|
|
54
|
-
this._temporalTexB.type =
|
|
67
|
+
this._temporalTexB.type = FloatType;
|
|
55
68
|
this._temporalTexB.format = RGBAFormat;
|
|
56
69
|
this._temporalTexB.minFilter = LinearFilter;
|
|
57
70
|
this._temporalTexB.magFilter = LinearFilter;
|
|
58
71
|
|
|
72
|
+
this._outputModulatedTex = new StorageTexture( w, h );
|
|
73
|
+
this._outputModulatedTex.type = FloatType;
|
|
74
|
+
this._outputModulatedTex.format = RGBAFormat;
|
|
75
|
+
this._outputModulatedTex.minFilter = LinearFilter;
|
|
76
|
+
this._outputModulatedTex.magFilter = LinearFilter;
|
|
77
|
+
|
|
59
78
|
this._gradientStorageTex = new StorageTexture( w, h );
|
|
60
79
|
this._gradientStorageTex.type = HalfFloatType;
|
|
61
80
|
this._gradientStorageTex.format = RGBAFormat;
|
|
62
81
|
this._gradientStorageTex.minFilter = LinearFilter;
|
|
63
82
|
this._gradientStorageTex.magFilter = LinearFilter;
|
|
64
83
|
|
|
84
|
+
// FloatType to match pathtracer:color (PT MRT). copyTextureToTexture
|
|
85
|
+
// requires identical formats.
|
|
86
|
+
this._prevColorRT = new RenderTarget( w, h, {
|
|
87
|
+
type: FloatType,
|
|
88
|
+
format: RGBAFormat,
|
|
89
|
+
minFilter: NearestFilter,
|
|
90
|
+
magFilter: NearestFilter,
|
|
91
|
+
depthBuffer: false,
|
|
92
|
+
stencilBuffer: false
|
|
93
|
+
} );
|
|
94
|
+
this._prevColorReady = false;
|
|
95
|
+
|
|
65
96
|
this.currentMoments = 0; // 0 = write A, read B; 1 = write B, read A
|
|
66
97
|
this._compiled = false;
|
|
67
98
|
|
|
@@ -89,7 +120,6 @@ export class ASVGF extends RenderStage {
|
|
|
89
120
|
stencilBuffer: false
|
|
90
121
|
} );
|
|
91
122
|
|
|
92
|
-
// Separate from temporal-pass nodes to avoid binding interference.
|
|
93
123
|
this._heatmapRawColorTexNode = new TextureNode();
|
|
94
124
|
this._heatmapColorTexNode = new TextureNode();
|
|
95
125
|
this._heatmapTemporalTexNode = new TextureNode();
|
|
@@ -99,29 +129,32 @@ export class ASVGF extends RenderStage {
|
|
|
99
129
|
|
|
100
130
|
this._buildHeatmapCompute();
|
|
101
131
|
|
|
102
|
-
this.frameCount = 0;
|
|
103
|
-
|
|
104
132
|
}
|
|
105
133
|
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
134
|
+
// Per-pixel adaptive-α signal: 5×5 spatial average of |currentLum − prevLum|
|
|
135
|
+
// / meanLum, both raw single-SPP (noise-comparable), with noise-floor
|
|
136
|
+
// subtraction. Currently gated off by gradientStrength=0 — kept compiled
|
|
137
|
+
// to drive heatmap mode 5 and as scaffolding for a proper variance-aware
|
|
138
|
+
// implementation.
|
|
109
139
|
_buildGradientCompute() {
|
|
110
140
|
|
|
111
141
|
const colorTex = this._colorTexNode;
|
|
142
|
+
const prevColorTex = this._prevColorTexNode;
|
|
112
143
|
const motionTex = this._motionTexNode;
|
|
113
|
-
const
|
|
144
|
+
const noiseFloor = this.gradientNoiseFloor;
|
|
114
145
|
const gradientStorageTex = this._gradientStorageTex;
|
|
115
146
|
const resW = this.resW;
|
|
116
147
|
const resH = this.resH;
|
|
117
148
|
|
|
118
|
-
|
|
119
|
-
const
|
|
149
|
+
// 12×12 tile = 8×8 workgroup + 2px border for the 5×5 stencil.
|
|
150
|
+
const TILE_W = 12;
|
|
151
|
+
const TILE_BORDER = 2;
|
|
152
|
+
const TILE_TOTAL = TILE_W * TILE_W; // 144
|
|
120
153
|
const WG_SIZE = 8;
|
|
121
154
|
const WG_THREADS = WG_SIZE * WG_SIZE; // 64
|
|
122
|
-
const EXTRA_LOAD = TILE_TOTAL - WG_THREADS; // 36
|
|
123
155
|
|
|
124
|
-
const
|
|
156
|
+
const sharedCurLum = workgroupArray( 'float', TILE_TOTAL );
|
|
157
|
+
const sharedPrevLum = workgroupArray( 'float', TILE_TOTAL );
|
|
125
158
|
|
|
126
159
|
const computeFn = Fn( () => {
|
|
127
160
|
|
|
@@ -129,26 +162,48 @@ export class ASVGF extends RenderStage {
|
|
|
129
162
|
const ly = localId.y;
|
|
130
163
|
const linearIdx = ly.mul( WG_SIZE ).add( lx );
|
|
131
164
|
|
|
132
|
-
|
|
133
|
-
const
|
|
165
|
+
// Hoisted outside loadEntry so all 3 load rounds reuse the same nodes.
|
|
166
|
+
const tileOriginX = int( workgroupId.x ).mul( WG_SIZE ).sub( TILE_BORDER ).toVar();
|
|
167
|
+
const tileOriginY = int( workgroupId.y ).mul( WG_SIZE ).sub( TILE_BORDER ).toVar();
|
|
134
168
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
169
|
+
const loadEntry = ( k ) => {
|
|
170
|
+
|
|
171
|
+
const sx = k.mod( uint( TILE_W ) );
|
|
172
|
+
const sy = k.div( uint( TILE_W ) );
|
|
173
|
+
const gxL = tileOriginX.add( int( sx ) ).clamp( int( 0 ), int( resW ).sub( 1 ) );
|
|
174
|
+
const gyL = tileOriginY.add( int( sy ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
175
|
+
|
|
176
|
+
const curColor = textureLoad( colorTex, ivec2( gxL, gyL ) ).xyz;
|
|
177
|
+
sharedCurLum.element( k ).assign( luminance( curColor ) );
|
|
178
|
+
|
|
179
|
+
const motion = textureLoad( motionTex, ivec2( gxL, gyL ) );
|
|
180
|
+
const prevXf = float( gxL ).sub( motion.x.mul( resW ) );
|
|
181
|
+
const prevYf = float( gyL ).sub( motion.y.mul( resH ) );
|
|
182
|
+
const prevX = int( prevXf ).clamp( int( 0 ), int( resW ).sub( 1 ) );
|
|
183
|
+
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
|
+
const motionValid = motion.w.greaterThan( 0.5 );
|
|
187
|
+
const prevColor = textureLoad( prevColorTex, ivec2( prevX, prevY ) ).xyz;
|
|
188
|
+
const prevLum = motionValid.select( luminance( prevColor ), luminance( curColor ) );
|
|
189
|
+
sharedPrevLum.element( k ).assign( prevLum );
|
|
190
|
+
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// 144 entries / 64 threads → 3 rounds, last partially populated.
|
|
194
|
+
loadEntry( linearIdx );
|
|
195
|
+
|
|
196
|
+
const idx2 = linearIdx.add( uint( WG_THREADS ) );
|
|
197
|
+
If( idx2.lessThan( uint( TILE_TOTAL ) ), () => {
|
|
198
|
+
|
|
199
|
+
loadEntry( idx2 );
|
|
200
|
+
|
|
201
|
+
} );
|
|
142
202
|
|
|
143
|
-
|
|
203
|
+
const idx3 = linearIdx.add( uint( WG_THREADS * 2 ) );
|
|
204
|
+
If( idx3.lessThan( uint( TILE_TOTAL ) ), () => {
|
|
144
205
|
|
|
145
|
-
|
|
146
|
-
const sx2 = idx2.mod( TILE_W );
|
|
147
|
-
const sy2 = idx2.div( TILE_W );
|
|
148
|
-
const gx2 = tileOriginX.add( int( sx2 ) ).clamp( int( 0 ), int( resW ).sub( 1 ) );
|
|
149
|
-
const gy2 = tileOriginY.add( int( sy2 ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
150
|
-
const sColor2 = textureLoad( colorTex, ivec2( gx2, gy2 ) ).xyz;
|
|
151
|
-
sharedLum.element( idx2 ).assign( luminance( sColor2 ) );
|
|
206
|
+
loadEntry( idx3 );
|
|
152
207
|
|
|
153
208
|
} );
|
|
154
209
|
|
|
@@ -159,51 +214,37 @@ export class ASVGF extends RenderStage {
|
|
|
159
214
|
|
|
160
215
|
If( gx.lessThan( int( resW ) ).and( gy.lessThan( int( resH ) ) ), () => {
|
|
161
216
|
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const bestDy = int( 0 ).toVar();
|
|
217
|
+
const sumDiff = float( 0.0 ).toVar();
|
|
218
|
+
const sumMean = float( 0.0 ).toVar();
|
|
165
219
|
|
|
166
|
-
for ( let dy = -
|
|
220
|
+
for ( let dy = - TILE_BORDER; dy <= TILE_BORDER; dy ++ ) {
|
|
167
221
|
|
|
168
|
-
for ( let dx = -
|
|
222
|
+
for ( let dx = - TILE_BORDER; dx <= TILE_BORDER; dx ++ ) {
|
|
169
223
|
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
bestDx.assign( int( dx ) );
|
|
178
|
-
bestDy.assign( int( dy ) );
|
|
179
|
-
|
|
180
|
-
} );
|
|
224
|
+
const idx = ly.add( uint( TILE_BORDER + dy ) )
|
|
225
|
+
.mul( uint( TILE_W ) )
|
|
226
|
+
.add( lx.add( uint( TILE_BORDER + dx ) ) );
|
|
227
|
+
const cL = sharedCurLum.element( idx );
|
|
228
|
+
const pL = sharedPrevLum.element( idx );
|
|
229
|
+
sumDiff.addAssign( abs( cL.sub( pL ) ) );
|
|
230
|
+
sumMean.addAssign( cL.add( pL ).mul( 0.5 ) );
|
|
181
231
|
|
|
182
232
|
}
|
|
183
233
|
|
|
184
234
|
}
|
|
185
235
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const prevYf = float( bestGy ).sub( motion.y.mul( resH ) );
|
|
193
|
-
const prevX = int( prevXf ).clamp( int( 0 ), int( resW ).sub( 1 ) );
|
|
194
|
-
const prevY = int( prevYf ).clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
195
|
-
|
|
196
|
-
const prevColor = textureLoad( prevTemporalTex, ivec2( prevX, prevY ) ).xyz;
|
|
197
|
-
const prevLum = luminance( prevColor );
|
|
198
|
-
|
|
199
|
-
const gradient = abs( bestLum.sub( prevLum ) )
|
|
200
|
-
.div( max( bestLum, float( 0.001 ) ) )
|
|
236
|
+
const rawGradient = sumDiff
|
|
237
|
+
.div( max( sumMean, float( 0.001 ) ) )
|
|
238
|
+
.clamp( 0.0, 1.0 );
|
|
239
|
+
const oneMinusFloor = max( float( 1.0 ).sub( noiseFloor ), float( 0.0001 ) );
|
|
240
|
+
const gradient = max( rawGradient.sub( noiseFloor ), float( 0.0 ) )
|
|
241
|
+
.div( oneMinusFloor )
|
|
201
242
|
.clamp( 0.0, 1.0 );
|
|
202
243
|
|
|
203
244
|
textureStore(
|
|
204
245
|
gradientStorageTex,
|
|
205
246
|
uvec2( uint( gx ), uint( gy ) ),
|
|
206
|
-
vec4( gradient,
|
|
247
|
+
vec4( gradient, rawGradient, sumMean.div( 25.0 ), 1.0 )
|
|
207
248
|
).toWriteOnly();
|
|
208
249
|
|
|
209
250
|
} );
|
|
@@ -227,13 +268,22 @@ export class ASVGF extends RenderStage {
|
|
|
227
268
|
|
|
228
269
|
_buildTemporalForDirection( writeTemporalTex ) {
|
|
229
270
|
|
|
271
|
+
const NORMAL_POWER = 16.0; // pow(dot, p): smooth surfaces ≈ 1, real edges ≈ 0
|
|
272
|
+
const DEPTH_SIGMA = 0.05; // exp(-relDelta/σ)
|
|
273
|
+
const VALIDITY_THRESHOLD = 0.01; // wSum below → disocclusion → fresh sample
|
|
274
|
+
|
|
230
275
|
const colorTex = this._colorTexNode;
|
|
276
|
+
const albedoTex = this._albedoTexNode;
|
|
231
277
|
const motionTex = this._motionTexNode;
|
|
278
|
+
const ndTex = this._normalDepthTexNode;
|
|
279
|
+
const prevNDTex = this._prevNormalDepthTexNode;
|
|
232
280
|
const prevTemporalTex = this._readTemporalTexNode;
|
|
281
|
+
const gradientTex = this._gradientReadTexNode;
|
|
282
|
+
const outputModulatedTex = this._outputModulatedTex;
|
|
233
283
|
|
|
234
|
-
const temporalAlpha = this.temporalAlpha;
|
|
235
284
|
const maxAccumFrames = this.maxAccumFrames;
|
|
236
|
-
const
|
|
285
|
+
const temporalAlphaMin = this.temporalAlpha;
|
|
286
|
+
const gradientStrength = this.gradientStrength;
|
|
237
287
|
const temporalEnabledU = this.temporalEnabledU;
|
|
238
288
|
const resW = this.resW;
|
|
239
289
|
const resH = this.resH;
|
|
@@ -249,9 +299,16 @@ export class ASVGF extends RenderStage {
|
|
|
249
299
|
|
|
250
300
|
const coord = ivec2( gx, gy );
|
|
251
301
|
const currentColor = textureLoad( colorTex, coord ).xyz;
|
|
302
|
+
const currentAlbedo = textureLoad( albedoTex, coord ).xyz;
|
|
303
|
+
|
|
304
|
+
// Same safeAlbedo on both demod and re-mod sides → exact
|
|
305
|
+
// round-trip for sky/miss rays where albedo=0.
|
|
306
|
+
const safeAlbedo = max( currentAlbedo, vec3( ALBEDO_EPS ) );
|
|
307
|
+
const currentLighting = currentColor.div( safeAlbedo );
|
|
252
308
|
|
|
253
|
-
//
|
|
254
|
-
const
|
|
309
|
+
// Defaults = fresh sample (no temporal blend).
|
|
310
|
+
const demodResult = vec4( currentLighting, 1.0 ).toVar();
|
|
311
|
+
const modulatedResult = vec4( currentColor, 1.0 ).toVar();
|
|
255
312
|
|
|
256
313
|
If( temporalEnabledU.greaterThan( 0.5 ), () => {
|
|
257
314
|
|
|
@@ -261,70 +318,93 @@ export class ASVGF extends RenderStage {
|
|
|
261
318
|
const prevXf = float( gx ).sub( motion.x.mul( resW ) );
|
|
262
319
|
const prevYf = float( gy ).sub( motion.y.mul( resH ) );
|
|
263
320
|
const prevOnScreen = prevXf.greaterThanEqual( 0.0 )
|
|
264
|
-
.and( prevXf.lessThan( float( resW ) ) )
|
|
321
|
+
.and( prevXf.lessThan( float( resW ).sub( 1.0 ) ) )
|
|
265
322
|
.and( prevYf.greaterThanEqual( 0.0 ) )
|
|
266
|
-
.and( prevYf.lessThan( float( resH ) ) );
|
|
323
|
+
.and( prevYf.lessThan( float( resH ).sub( 1.0 ) ) );
|
|
267
324
|
|
|
268
325
|
If( motionValid.and( prevOnScreen ), () => {
|
|
269
326
|
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
const
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
327
|
+
const ndCurrent = textureLoad( ndTex, coord );
|
|
328
|
+
const nCurrent = ndCurrent.xyz.mul( 2.0 ).sub( 1.0 );
|
|
329
|
+
const depthCurrent = ndCurrent.w;
|
|
330
|
+
|
|
331
|
+
const x0 = int( prevXf );
|
|
332
|
+
const y0 = int( prevYf );
|
|
333
|
+
const x1 = x0.add( int( 1 ) );
|
|
334
|
+
const y1 = y0.add( int( 1 ) );
|
|
335
|
+
const fx = prevXf.sub( float( x0 ) );
|
|
336
|
+
const fy = prevYf.sub( float( y0 ) );
|
|
337
|
+
|
|
338
|
+
const x0c = x0.clamp( int( 0 ), int( resW ).sub( 1 ) );
|
|
339
|
+
const x1c = x1.clamp( int( 0 ), int( resW ).sub( 1 ) );
|
|
340
|
+
const y0c = y0.clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
341
|
+
const y1c = y1.clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
342
|
+
|
|
343
|
+
const w00 = float( 1.0 ).sub( fx ).mul( float( 1.0 ).sub( fy ) );
|
|
344
|
+
const w10 = fx.mul( float( 1.0 ).sub( fy ) );
|
|
345
|
+
const w01 = float( 1.0 ).sub( fx ).mul( fy );
|
|
346
|
+
const w11 = fx.mul( fy );
|
|
347
|
+
|
|
348
|
+
// SVGF soft per-tap weight: bilinear × normal × depth
|
|
349
|
+
// (prev-frame normalDepth, geometric normals — stable).
|
|
350
|
+
const tapValid = ( xi, yi, bilinearW ) => {
|
|
351
|
+
|
|
352
|
+
const prevND = textureLoad( prevNDTex, ivec2( xi, yi ) );
|
|
353
|
+
const nPrev = prevND.xyz.mul( 2.0 ).sub( 1.0 );
|
|
354
|
+
const depthPrev = prevND.w;
|
|
355
|
+
const normalDot = dot( nCurrent, nPrev ).clamp( 0.0, 1.0 );
|
|
356
|
+
const normalW = pow( normalDot, float( NORMAL_POWER ) );
|
|
357
|
+
const depthDelta = abs( depthCurrent.sub( depthPrev ) )
|
|
358
|
+
.div( max( depthCurrent, float( 0.001 ) ) );
|
|
359
|
+
const depthW = exp( depthDelta.div( float( DEPTH_SIGMA ) ).negate() );
|
|
360
|
+
return bilinearW.mul( normalW ).mul( depthW );
|
|
361
|
+
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const v00 = tapValid( x0c, y0c, w00 );
|
|
365
|
+
const v10 = tapValid( x1c, y0c, w10 );
|
|
366
|
+
const v01 = tapValid( x0c, y1c, w01 );
|
|
367
|
+
const v11 = tapValid( x1c, y1c, w11 );
|
|
368
|
+
|
|
369
|
+
const wSum = v00.add( v10 ).add( v01 ).add( v11 );
|
|
370
|
+
|
|
371
|
+
If( wSum.greaterThan( float( VALIDITY_THRESHOLD ) ), () => {
|
|
372
|
+
|
|
373
|
+
const p00 = textureLoad( prevTemporalTex, ivec2( x0c, y0c ) );
|
|
374
|
+
const p10 = textureLoad( prevTemporalTex, ivec2( x1c, y0c ) );
|
|
375
|
+
const p01 = textureLoad( prevTemporalTex, ivec2( x0c, y1c ) );
|
|
376
|
+
const p11 = textureLoad( prevTemporalTex, ivec2( x1c, y1c ) );
|
|
377
|
+
|
|
378
|
+
const invWSum = float( 1.0 ).div( wSum );
|
|
379
|
+
const prevLighting = p00.xyz.mul( v00 )
|
|
380
|
+
.add( p10.xyz.mul( v10 ) )
|
|
381
|
+
.add( p01.xyz.mul( v01 ) )
|
|
382
|
+
.add( p11.xyz.mul( v11 ) )
|
|
383
|
+
.mul( invWSum );
|
|
384
|
+
|
|
385
|
+
const prevHistory = p00.w.mul( v00 )
|
|
386
|
+
.add( p10.w.mul( v10 ) )
|
|
387
|
+
.add( p01.w.mul( v01 ) )
|
|
388
|
+
.add( p11.w.mul( v11 ) )
|
|
389
|
+
.mul( invWSum );
|
|
390
|
+
|
|
391
|
+
// adaptive α — disabled by default (gradientStrength=0).
|
|
392
|
+
const gradient = textureLoad( gradientTex, coord ).x;
|
|
393
|
+
const adaptiveBoost = gradient.mul( gradientStrength ).clamp( 0.0, 1.0 );
|
|
394
|
+
|
|
395
|
+
const baseAlpha = max(
|
|
396
|
+
float( 1.0 ).div( prevHistory.add( 1.0 ) ),
|
|
397
|
+
temporalAlphaMin
|
|
398
|
+
);
|
|
399
|
+
const effectiveAlpha = mix( baseAlpha, float( 1.0 ), adaptiveBoost );
|
|
400
|
+
|
|
401
|
+
const blendedLighting = mix( prevLighting, currentLighting, effectiveAlpha );
|
|
402
|
+
const newHistory = min( prevHistory.add( 1.0 ), maxAccumFrames );
|
|
403
|
+
|
|
404
|
+
demodResult.assign( vec4( blendedLighting, newHistory ) );
|
|
405
|
+
modulatedResult.assign( vec4( blendedLighting.mul( safeAlbedo ), 1.0 ) );
|
|
322
406
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
} ).Else( () => {
|
|
326
|
-
|
|
327
|
-
result.assign( vec4( currentColor, 1.0 ) );
|
|
407
|
+
} );
|
|
328
408
|
|
|
329
409
|
} );
|
|
330
410
|
|
|
@@ -333,7 +413,13 @@ export class ASVGF extends RenderStage {
|
|
|
333
413
|
textureStore(
|
|
334
414
|
writeTemporalTex,
|
|
335
415
|
uvec2( uint( gx ), uint( gy ) ),
|
|
336
|
-
|
|
416
|
+
demodResult
|
|
417
|
+
).toWriteOnly();
|
|
418
|
+
|
|
419
|
+
textureStore(
|
|
420
|
+
outputModulatedTex,
|
|
421
|
+
uvec2( uint( gx ), uint( gy ) ),
|
|
422
|
+
modulatedResult
|
|
337
423
|
).toWriteOnly();
|
|
338
424
|
|
|
339
425
|
} );
|
|
@@ -372,18 +458,17 @@ export class ASVGF extends RenderStage {
|
|
|
372
458
|
const coord = ivec2( gx, gy );
|
|
373
459
|
const result = vec4( 0.0, 0.0, 0.0, 1.0 ).toVar();
|
|
374
460
|
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
// 0: beauty
|
|
461
|
+
// Chained If/ElseIf — separate If blocks let inactive-branch
|
|
462
|
+
// texture samples contaminate the output.
|
|
379
463
|
If( mode.equal( int( 0 ) ), () => {
|
|
380
464
|
|
|
465
|
+
// 0: beauty (modulated ASVGF output)
|
|
381
466
|
const c = textureLoad( colorTex, coord ).xyz;
|
|
382
467
|
result.assign( vec4( c, 1.0 ) );
|
|
383
468
|
|
|
384
469
|
} ).ElseIf( mode.equal( int( 1 ) ), () => {
|
|
385
470
|
|
|
386
|
-
// 1: 3×3 luminance variance of raw
|
|
471
|
+
// 1: 3×3 luminance variance of raw PT input
|
|
387
472
|
const meanLum = float( 0.0 ).toVar();
|
|
388
473
|
const meanLumSq = float( 0.0 ).toVar();
|
|
389
474
|
|
|
@@ -394,7 +479,7 @@ export class ASVGF extends RenderStage {
|
|
|
394
479
|
const sx = gx.add( dx ).clamp( int( 0 ), int( resW ).sub( 1 ) );
|
|
395
480
|
const sy = gy.add( dy ).clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
396
481
|
const s = textureLoad( rawColorTex, ivec2( sx, sy ) ).xyz;
|
|
397
|
-
const lum =
|
|
482
|
+
const lum = luminance( s );
|
|
398
483
|
meanLum.addAssign( lum );
|
|
399
484
|
meanLumSq.addAssign( lum.mul( lum ) );
|
|
400
485
|
|
|
@@ -409,7 +494,7 @@ export class ASVGF extends RenderStage {
|
|
|
409
494
|
const relVar = variance.div( max( meanLum.mul( meanLum ), float( 0.0001 ) ) );
|
|
410
495
|
const t = relVar.mul( 10.0 ).clamp( 0.0, 1.0 );
|
|
411
496
|
|
|
412
|
-
//
|
|
497
|
+
// blue → cyan → green → yellow → red
|
|
413
498
|
const r = t.sub( 0.5 ).mul( 4.0 ).clamp( 0.0, 1.0 );
|
|
414
499
|
const g = t.mul( 4.0 ).clamp( 0.0, 1.0 ).sub(
|
|
415
500
|
t.sub( 0.75 ).mul( 4.0 ).clamp( 0.0, 1.0 )
|
|
@@ -471,22 +556,11 @@ export class ASVGF extends RenderStage {
|
|
|
471
556
|
|
|
472
557
|
this.on( 'asvgf:setTemporal', ( data ) => {
|
|
473
558
|
|
|
474
|
-
if ( data && data.enabled !== undefined )
|
|
475
|
-
|
|
476
|
-
this.temporalEnabled = data.enabled;
|
|
477
|
-
this.temporalEnabledU.value = data.enabled ? 1.0 : 0.0;
|
|
478
|
-
|
|
479
|
-
}
|
|
559
|
+
if ( data && data.enabled !== undefined ) this.setTemporalEnabled( data.enabled );
|
|
480
560
|
|
|
481
561
|
} );
|
|
482
562
|
|
|
483
|
-
this.on( 'asvgf:updateParameters', ( data ) =>
|
|
484
|
-
|
|
485
|
-
if ( ! data ) return;
|
|
486
|
-
if ( data.temporalAlpha !== undefined ) this.temporalAlpha.value = data.temporalAlpha;
|
|
487
|
-
if ( data.phiColor !== undefined ) this.phiColor.value = data.phiColor;
|
|
488
|
-
|
|
489
|
-
} );
|
|
563
|
+
this.on( 'asvgf:updateParameters', ( data ) => this.updateParameters( data ) );
|
|
490
564
|
|
|
491
565
|
}
|
|
492
566
|
|
|
@@ -495,7 +569,11 @@ export class ASVGF extends RenderStage {
|
|
|
495
569
|
if ( ! this.enabled ) return;
|
|
496
570
|
|
|
497
571
|
const colorTex = context.getTexture( 'pathtracer:color' );
|
|
572
|
+
const albedoTex = context.getTexture( 'pathtracer:albedo' );
|
|
498
573
|
const normalDepthTex = context.getTexture( 'pathtracer:normalDepth' );
|
|
574
|
+
// First frame fallback — alias current ND.
|
|
575
|
+
const prevNormalDepthTex = context.getTexture( 'pathtracer:prevNormalDepth' )
|
|
576
|
+
|| normalDepthTex;
|
|
499
577
|
const motionTex = context.getTexture( 'motionVector:screenSpace' );
|
|
500
578
|
|
|
501
579
|
if ( ! colorTex ) return;
|
|
@@ -513,10 +591,28 @@ export class ASVGF extends RenderStage {
|
|
|
513
591
|
}
|
|
514
592
|
|
|
515
593
|
this._colorTexNode.value = colorTex;
|
|
594
|
+
if ( albedoTex ) this._albedoTexNode.value = albedoTex;
|
|
516
595
|
if ( motionTex ) this._motionTexNode.value = motionTex;
|
|
596
|
+
if ( normalDepthTex ) this._normalDepthTexNode.value = normalDepthTex;
|
|
597
|
+
if ( prevNormalDepthTex ) this._prevNormalDepthTexNode.value = prevNormalDepthTex;
|
|
598
|
+
|
|
599
|
+
const readTemporal = this.currentMoments === 0
|
|
600
|
+
? this._temporalTexB : this._temporalTexA;
|
|
601
|
+
const writeNode = this.currentMoments === 0
|
|
602
|
+
? this._temporalNodeA : this._temporalNodeB;
|
|
603
|
+
const writeTemporal = this.currentMoments === 0
|
|
604
|
+
? this._temporalTexA : this._temporalTexB;
|
|
605
|
+
|
|
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;
|
|
517
611
|
|
|
518
|
-
//
|
|
519
|
-
// so textureLoad codegen emits the required `level`
|
|
612
|
+
// First-frame compile while StorageTexture-typed nodes still hold
|
|
613
|
+
// EmptyTexture, so textureLoad codegen emits the required `level`
|
|
614
|
+
// parameter. Binding StorageTextures only AFTER compile keeps the
|
|
615
|
+
// codegen path correct (otherwise reads would return zero at runtime).
|
|
520
616
|
if ( ! this._compiled ) {
|
|
521
617
|
|
|
522
618
|
this.renderer.compute( this._gradientNode );
|
|
@@ -526,28 +622,43 @@ export class ASVGF extends RenderStage {
|
|
|
526
622
|
|
|
527
623
|
}
|
|
528
624
|
|
|
529
|
-
// Ping-pong: read opposite, write current
|
|
530
|
-
const readTemporal = this.currentMoments === 0
|
|
531
|
-
? this._temporalTexB : this._temporalTexA;
|
|
532
|
-
const writeNode = this.currentMoments === 0
|
|
533
|
-
? this._temporalNodeA : this._temporalNodeB;
|
|
534
|
-
const writeTemporal = this.currentMoments === 0
|
|
535
|
-
? this._temporalTexA : this._temporalTexB;
|
|
536
|
-
|
|
537
625
|
this._readTemporalTexNode.value = readTemporal;
|
|
626
|
+
this._gradientReadTexNode.value = this._gradientStorageTex;
|
|
627
|
+
|
|
628
|
+
// Skip the gradient dispatch when nothing consumes it. The temporal
|
|
629
|
+
// pass reads gradientTex unconditionally but multiplies by
|
|
630
|
+
// gradientStrength=0 → the stale prior frame's gradient texture is
|
|
631
|
+
// fine (the result is zeroed out anyway).
|
|
632
|
+
const needsGradient = this.gradientStrength.value > 0 || this.showHeatmap;
|
|
633
|
+
if ( needsGradient ) {
|
|
634
|
+
|
|
635
|
+
this.renderer.compute( this._gradientNode );
|
|
636
|
+
|
|
637
|
+
}
|
|
638
|
+
|
|
538
639
|
this.renderer.compute( writeNode );
|
|
539
640
|
|
|
540
|
-
|
|
541
|
-
|
|
641
|
+
// Cache this frame's pathtracer:color for next frame's gradient if it's
|
|
642
|
+
// active. Copy AFTER reads so we don't clobber the prev view.
|
|
643
|
+
if ( needsGradient ) {
|
|
644
|
+
|
|
645
|
+
this.renderer.copyTextureToTexture( colorTex, this._prevColorRT.texture );
|
|
646
|
+
this._prevColorReady = true;
|
|
647
|
+
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
context.setTexture( 'asvgf:demodulated', writeTemporal );
|
|
651
|
+
context.setTexture( 'asvgf:output', this._outputModulatedTex );
|
|
652
|
+
context.setTexture( 'asvgf:gradient', this._gradientStorageTex );
|
|
542
653
|
|
|
543
654
|
this.currentMoments = 1 - this.currentMoments;
|
|
544
655
|
|
|
545
656
|
if ( this.showHeatmap ) {
|
|
546
657
|
|
|
547
|
-
|
|
548
|
-
|
|
658
|
+
// Mode 0 needs modulated for direct display; mode 2 needs the
|
|
659
|
+
// ping-pong's history length in alpha.
|
|
549
660
|
this._heatmapRawColorTexNode.value = colorTex;
|
|
550
|
-
this._heatmapColorTexNode.value =
|
|
661
|
+
this._heatmapColorTexNode.value = this._outputModulatedTex;
|
|
551
662
|
this._heatmapTemporalTexNode.value = writeTemporal;
|
|
552
663
|
if ( normalDepthTex ) this._heatmapNDTexNode.value = normalDepthTex;
|
|
553
664
|
if ( motionTex ) this._heatmapMotionTexNode.value = motionTex;
|
|
@@ -558,15 +669,8 @@ export class ASVGF extends RenderStage {
|
|
|
558
669
|
|
|
559
670
|
}
|
|
560
671
|
|
|
561
|
-
this.frameCount ++;
|
|
562
|
-
|
|
563
672
|
}
|
|
564
673
|
|
|
565
|
-
/**
|
|
566
|
-
* Enable or disable the heatmap compute pass. When enabled, the heatmap
|
|
567
|
-
* is rendered each frame to {@link this.heatmapTarget} (a public RenderTarget)
|
|
568
|
-
* for the host to display however it wants.
|
|
569
|
-
*/
|
|
570
674
|
setHeatmapEnabled( enabled ) {
|
|
571
675
|
|
|
572
676
|
this.showHeatmap = enabled;
|
|
@@ -575,7 +679,7 @@ export class ASVGF extends RenderStage {
|
|
|
575
679
|
|
|
576
680
|
setTemporalEnabled( enabled ) {
|
|
577
681
|
|
|
578
|
-
this.
|
|
682
|
+
this.temporalEnabledU.value = enabled ? 1.0 : 0.0;
|
|
579
683
|
|
|
580
684
|
}
|
|
581
685
|
|
|
@@ -583,15 +687,18 @@ export class ASVGF extends RenderStage {
|
|
|
583
687
|
|
|
584
688
|
if ( ! params ) return;
|
|
585
689
|
if ( params.temporalAlpha !== undefined ) this.temporalAlpha.value = params.temporalAlpha;
|
|
586
|
-
if ( params.
|
|
690
|
+
if ( params.gradientStrength !== undefined ) this.gradientStrength.value = params.gradientStrength;
|
|
691
|
+
if ( params.gradientNoiseFloor !== undefined ) this.gradientNoiseFloor.value = params.gradientNoiseFloor;
|
|
692
|
+
if ( params.maxAccumFrames !== undefined ) this.maxAccumFrames.value = params.maxAccumFrames;
|
|
587
693
|
if ( params.debugMode !== undefined ) this.debugMode.value = params.debugMode;
|
|
588
694
|
|
|
589
695
|
}
|
|
590
696
|
|
|
591
697
|
resetTemporalData() {
|
|
592
698
|
|
|
593
|
-
this.frameCount = 0;
|
|
594
699
|
this.currentMoments = 0;
|
|
700
|
+
// Drop cache so post-reset frames don't see pre-reset prev color.
|
|
701
|
+
this._prevColorReady = false;
|
|
595
702
|
|
|
596
703
|
}
|
|
597
704
|
|
|
@@ -599,10 +706,11 @@ export class ASVGF extends RenderStage {
|
|
|
599
706
|
|
|
600
707
|
this._temporalTexA.setSize( width, height );
|
|
601
708
|
this._temporalTexB.setSize( width, height );
|
|
709
|
+
this._outputModulatedTex.setSize( width, height );
|
|
602
710
|
this._gradientStorageTex.setSize( width, height );
|
|
711
|
+
this._prevColorRT.setSize( width, height );
|
|
603
712
|
this._heatmapStorageTex.setSize( width, height );
|
|
604
713
|
this.heatmapTarget.setSize( width, height );
|
|
605
|
-
this.heatmapTarget.texture.needsUpdate = true;
|
|
606
714
|
this.resW.value = width;
|
|
607
715
|
this.resH.value = height;
|
|
608
716
|
|
|
@@ -613,12 +721,16 @@ export class ASVGF extends RenderStage {
|
|
|
613
721
|
this._temporalNodeB.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
|
|
614
722
|
this._heatmapComputeNode.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
|
|
615
723
|
|
|
724
|
+
// Buffers reallocated → re-run first-frame compile and re-seed cache.
|
|
725
|
+
this._compiled = false;
|
|
726
|
+
this._prevColorReady = false;
|
|
727
|
+
|
|
616
728
|
}
|
|
617
729
|
|
|
618
730
|
reset() {
|
|
619
731
|
|
|
620
|
-
// No-op: motion vectors handle camera moves;
|
|
621
|
-
//
|
|
732
|
+
// No-op: motion vectors handle camera moves; explicit asvgf:reset
|
|
733
|
+
// clears history on scene/mode change.
|
|
622
734
|
|
|
623
735
|
}
|
|
624
736
|
|
|
@@ -629,14 +741,21 @@ export class ASVGF extends RenderStage {
|
|
|
629
741
|
this._temporalNodeB?.dispose();
|
|
630
742
|
this._temporalTexA?.dispose();
|
|
631
743
|
this._temporalTexB?.dispose();
|
|
744
|
+
this._outputModulatedTex?.dispose();
|
|
632
745
|
this._gradientStorageTex?.dispose();
|
|
746
|
+
this._prevColorRT?.dispose();
|
|
633
747
|
this._heatmapComputeNode?.dispose();
|
|
634
748
|
this._heatmapStorageTex?.dispose();
|
|
635
749
|
this.heatmapTarget?.dispose();
|
|
636
750
|
|
|
637
751
|
this._colorTexNode?.dispose();
|
|
752
|
+
this._prevColorTexNode?.dispose();
|
|
753
|
+
this._albedoTexNode?.dispose();
|
|
638
754
|
this._motionTexNode?.dispose();
|
|
755
|
+
this._normalDepthTexNode?.dispose();
|
|
756
|
+
this._prevNormalDepthTexNode?.dispose();
|
|
639
757
|
this._readTemporalTexNode?.dispose();
|
|
758
|
+
this._gradientReadTexNode?.dispose();
|
|
640
759
|
|
|
641
760
|
this._heatmapRawColorTexNode?.dispose();
|
|
642
761
|
this._heatmapColorTexNode?.dispose();
|