rayzee 7.1.0 → 7.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/rayzee.es.js +1441 -1293
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +45 -45
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +13 -16
- package/src/PathTracerApp.js +16 -4
- package/src/Processor/EmissiveTriangleBuilder.js +39 -5
- package/src/Processor/LightBVHBuilder.js +113 -27
- package/src/Processor/SceneProcessor.js +19 -1
- package/src/Stages/ASVGF.js +40 -14
- package/src/Stages/BilateralFilter.js +23 -4
- package/src/Stages/NormalDepth.js +101 -7
- package/src/Stages/PathTracer.js +1 -0
- package/src/Stages/PathTracerStage.js +14 -3
- package/src/Stages/Variance.js +7 -3
- package/src/TSL/EmissiveSampling.js +3 -2
- package/src/TSL/LightBVHSampling.js +216 -25
- package/src/TSL/ShadeKernel.js +21 -6
- package/src/managers/CameraManager.js +19 -0
- package/src/managers/UniformManager.js +4 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, wgslFn, vec3, vec4, float, int, uint, ivec2, uvec2, uniform, If, max, sqrt,
|
|
1
|
+
import { Fn, wgslFn, vec3, vec4, float, int, uint, ivec2, uvec2, uniform, If, max, mix, sqrt,
|
|
2
2
|
textureLoad, textureStore, localId, workgroupId } from 'three/tsl';
|
|
3
3
|
import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
|
|
4
4
|
import { HalfFloatType, RGBAFormat, LinearFilter, Box2, Vector2 } from 'three';
|
|
@@ -59,6 +59,9 @@ export class BilateralFilter extends RenderStage {
|
|
|
59
59
|
this.renderer = renderer;
|
|
60
60
|
this.inputTextureName = options.inputTextureName || 'asvgf:demodulated';
|
|
61
61
|
this.normalDepthTextureName = options.normalDepthTextureName || 'pathtracer:normalDepth';
|
|
62
|
+
// Mapped (normal/bump-perturbed) normal for the normal edge-stop — geometric
|
|
63
|
+
// normals are flat across a normal-mapped surface so normW can't preserve bump detail.
|
|
64
|
+
this.shadingNormalTextureName = options.shadingNormalTextureName || 'pathtracer:shadingNormal';
|
|
62
65
|
this.albedoTextureName = options.albedoTextureName || 'pathtracer:albedo';
|
|
63
66
|
this.varianceTextureName = options.varianceTextureName || 'variance:output';
|
|
64
67
|
this.iterations = options.iterations ?? 4;
|
|
@@ -69,6 +72,10 @@ export class BilateralFilter extends RenderStage {
|
|
|
69
72
|
this.phiNormal = uniform( options.phiNormal ?? 128.0 );
|
|
70
73
|
this.phiDepth = uniform( options.phiDepth ?? 0.05 );
|
|
71
74
|
this.phiLuminance = uniform( options.phiLuminance ?? 4.0 );
|
|
75
|
+
// Blend Variance's spatial-variance channel into sigma_l (1 = max(temporal, spatial),
|
|
76
|
+
// 0 = temporal-only). Widens the luminance gate where history is thin but the
|
|
77
|
+
// neighbourhood is noisy (disocclusion). Default on — validated −1.7% RMSE @4spp.
|
|
78
|
+
this.spatialVarianceWeight = uniform( options.spatialVarianceWeight ?? 1.0 );
|
|
72
79
|
this.stepSizeU = uniform( 1, 'int' );
|
|
73
80
|
// 1 on the final iteration → multiply by albedo to remodulate.
|
|
74
81
|
this.isLastIterationU = uniform( 0, 'int' );
|
|
@@ -77,6 +84,7 @@ export class BilateralFilter extends RenderStage {
|
|
|
77
84
|
|
|
78
85
|
this._readTexNode = new TextureNode();
|
|
79
86
|
this._normalDepthTexNode = new TextureNode();
|
|
87
|
+
this._shadingNormalTexNode = new TextureNode();
|
|
80
88
|
this._albedoTexNode = new TextureNode();
|
|
81
89
|
this._varianceTexNode = new TextureNode();
|
|
82
90
|
|
|
@@ -133,12 +141,14 @@ export class BilateralFilter extends RenderStage {
|
|
|
133
141
|
|
|
134
142
|
const readTexNode = this._readTexNode;
|
|
135
143
|
const ndTexNode = this._normalDepthTexNode;
|
|
144
|
+
const snTexNode = this._shadingNormalTexNode;
|
|
136
145
|
const albedoTexNode = this._albedoTexNode;
|
|
137
146
|
const varTexNode = this._varianceTexNode;
|
|
138
147
|
const phiColor = this.phiColor;
|
|
139
148
|
const phiNormal = this.phiNormal;
|
|
140
149
|
const phiDepth = this.phiDepth;
|
|
141
150
|
const phiLuminance = this.phiLuminance;
|
|
151
|
+
const spatialVarianceWeight = this.spatialVarianceWeight;
|
|
142
152
|
const stepSize = this.stepSizeU;
|
|
143
153
|
const isLastIterationU = this.isLastIterationU;
|
|
144
154
|
const resW = this.resW;
|
|
@@ -165,7 +175,8 @@ export class BilateralFilter extends RenderStage {
|
|
|
165
175
|
const coord = ivec2( gx, gy );
|
|
166
176
|
const centerColor = textureLoad( readTexNode, coord ).xyz;
|
|
167
177
|
const centerND = textureLoad( ndTexNode, coord );
|
|
168
|
-
|
|
178
|
+
// Normal edge-stop reads the mapped (shading) normal; depth gate stays geometric.
|
|
179
|
+
const centerNormal = textureLoad( snTexNode, coord ).xyz.mul( 2.0 ).sub( 1.0 );
|
|
169
180
|
const centerDepth = centerND.w;
|
|
170
181
|
const centerLum = luminance( centerColor );
|
|
171
182
|
const centerSafeAlbedo = max( textureLoad( albedoTexNode, coord ).xyz, vec3( ALBEDO_EPS ) );
|
|
@@ -176,7 +187,10 @@ export class BilateralFilter extends RenderStage {
|
|
|
176
187
|
// from demodulation — otherwise dark materials get an
|
|
177
188
|
// under-estimated sigma → over-strict luminance gate → no
|
|
178
189
|
// blending → silhouette dark-outline artifact.
|
|
179
|
-
|
|
190
|
+
// .z = temporal variance, .w = spatial (3×3) variance. Blend toward
|
|
191
|
+
// max(temporal, spatial) so disoccluded/low-history pixels widen sigma_l.
|
|
192
|
+
const vSample = textureLoad( varTexNode, coord );
|
|
193
|
+
const variance = mix( vSample.z, max( vSample.z, vSample.w ), spatialVarianceWeight );
|
|
180
194
|
const sigmaL = phiLuminance
|
|
181
195
|
.mul( sqrt( max( variance, float( 0.0 ) ) ) )
|
|
182
196
|
.div( centerAlbedoLum )
|
|
@@ -201,7 +215,7 @@ export class BilateralFilter extends RenderStage {
|
|
|
201
215
|
|
|
202
216
|
const sColor = textureLoad( readTexNode, ivec2( sx, sy ) ).xyz;
|
|
203
217
|
const sND = textureLoad( ndTexNode, ivec2( sx, sy ) );
|
|
204
|
-
const sNormal =
|
|
218
|
+
const sNormal = textureLoad( snTexNode, ivec2( sx, sy ) ).xyz.mul( 2.0 ).sub( 1.0 );
|
|
205
219
|
const sDepth = sND.w;
|
|
206
220
|
const sLum = luminance( sColor );
|
|
207
221
|
|
|
@@ -253,6 +267,8 @@ export class BilateralFilter extends RenderStage {
|
|
|
253
267
|
|| context.getTexture( 'asvgf:output' )
|
|
254
268
|
|| context.getTexture( 'pathtracer:color' );
|
|
255
269
|
const ndTex = context.getTexture( this.normalDepthTextureName );
|
|
270
|
+
// Fall back to geometric normalDepth if the mapped normal isn't published.
|
|
271
|
+
const snTex = context.getTexture( this.shadingNormalTextureName ) || ndTex;
|
|
256
272
|
const albedoTex = context.getTexture( this.albedoTextureName );
|
|
257
273
|
const varTex = context.getTexture( this.varianceTextureName );
|
|
258
274
|
|
|
@@ -273,6 +289,7 @@ export class BilateralFilter extends RenderStage {
|
|
|
273
289
|
|
|
274
290
|
// RenderTarget textures — safe to bind before first-compile.
|
|
275
291
|
if ( ndTex ) this._normalDepthTexNode.value = ndTex;
|
|
292
|
+
if ( snTex ) this._shadingNormalTexNode.value = snTex;
|
|
276
293
|
if ( albedoTex ) this._albedoTexNode.value = albedoTex;
|
|
277
294
|
|
|
278
295
|
// First-frame compile while StorageTexture-typed nodes still hold
|
|
@@ -329,6 +346,7 @@ export class BilateralFilter extends RenderStage {
|
|
|
329
346
|
if ( params.phiNormal !== undefined ) this.phiNormal.value = params.phiNormal;
|
|
330
347
|
if ( params.phiDepth !== undefined ) this.phiDepth.value = params.phiDepth;
|
|
331
348
|
if ( params.phiLuminance !== undefined ) this.phiLuminance.value = params.phiLuminance;
|
|
349
|
+
if ( params.spatialVarianceWeight !== undefined ) this.spatialVarianceWeight.value = params.spatialVarianceWeight;
|
|
332
350
|
if ( params.atrousIterations !== undefined ) this.iterations = params.atrousIterations;
|
|
333
351
|
|
|
334
352
|
}
|
|
@@ -364,6 +382,7 @@ export class BilateralFilter extends RenderStage {
|
|
|
364
382
|
this._outputTarget?.dispose();
|
|
365
383
|
this._readTexNode?.dispose();
|
|
366
384
|
this._normalDepthTexNode?.dispose();
|
|
385
|
+
this._shadingNormalTexNode?.dispose();
|
|
367
386
|
this._albedoTexNode?.dispose();
|
|
368
387
|
this._varianceTexNode?.dispose();
|
|
369
388
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Fn, vec3, vec4, float, int, uint, uvec2, uniform, normalize, mat3, storage, If,
|
|
2
|
-
textureStore, workgroupId, localId } from 'three/tsl';
|
|
2
|
+
texture, textureStore, workgroupId, localId } from 'three/tsl';
|
|
3
3
|
import { RenderTarget, StorageTexture } from 'three/webgpu';
|
|
4
|
-
import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4, Box2, Vector2 } from 'three';
|
|
4
|
+
import { HalfFloatType, RGBAFormat, NearestFilter, LinearFilter, DataArrayTexture, Matrix4, Box2, Vector2 } from 'three';
|
|
5
5
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
6
6
|
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
7
|
-
import { Ray, HitInfo } from '../TSL/Struct.js';
|
|
7
|
+
import { Ray, HitInfo, RayTracingMaterial, UVCache } from '../TSL/Struct.js';
|
|
8
8
|
import { traverseBVH } from '../TSL/BVHTraversal.js';
|
|
9
|
+
import { getMaterial } from '../TSL/Common.js';
|
|
10
|
+
import { computeUVCache, processNormal, processBump } from '../TSL/TextureSampling.js';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* NormalDepth — primary-ray G-buffer for SVGF gates.
|
|
@@ -21,7 +23,13 @@ import { traverseBVH } from '../TSL/BVHTraversal.js';
|
|
|
21
23
|
* aliases current — without that aliasing prev would point at older data
|
|
22
24
|
* while this frame's motion vector reflects zero motion → false rejection.
|
|
23
25
|
*
|
|
24
|
-
*
|
|
26
|
+
* Also emits a SHADING normal (geometric normal perturbed by the normal/bump
|
|
27
|
+
* map, recomputed from the SAME deterministic hit — no extra ray) so the
|
|
28
|
+
* spatial denoiser's edge-stop can see normal-map detail the flat geometric
|
|
29
|
+
* normal hides. Deterministic ⇒ jitter-free, so it's safe for the gates.
|
|
30
|
+
*
|
|
31
|
+
* Publishes: pathtracer:normalDepth, pathtracer:prevNormalDepth,
|
|
32
|
+
* pathtracer:shadingNormal
|
|
25
33
|
*/
|
|
26
34
|
export class NormalDepth extends RenderStage {
|
|
27
35
|
|
|
@@ -52,6 +60,22 @@ export class NormalDepth extends RenderStage {
|
|
|
52
60
|
this._outputStorageTex.minFilter = NearestFilter;
|
|
53
61
|
this._outputStorageTex.magFilter = NearestFilter;
|
|
54
62
|
|
|
63
|
+
// Shading-normal output (geometric normal perturbed by normal/bump map).
|
|
64
|
+
// Single buffer — only the spatial filter (current frame) consumes it.
|
|
65
|
+
this._shadingStorageTex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
66
|
+
this._shadingStorageTex.type = HalfFloatType;
|
|
67
|
+
this._shadingStorageTex.format = RGBAFormat;
|
|
68
|
+
this._shadingStorageTex.minFilter = NearestFilter;
|
|
69
|
+
this._shadingStorageTex.magFilter = NearestFilter;
|
|
70
|
+
this._shadingRT = new RenderTarget( w, h, {
|
|
71
|
+
type: HalfFloatType,
|
|
72
|
+
format: RGBAFormat,
|
|
73
|
+
minFilter: NearestFilter,
|
|
74
|
+
magFilter: NearestFilter,
|
|
75
|
+
depthBuffer: false,
|
|
76
|
+
stencilBuffer: false
|
|
77
|
+
} );
|
|
78
|
+
|
|
55
79
|
this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
|
|
56
80
|
|
|
57
81
|
// Ping-pong RTs share format with the StorageTexture so copyTextureToTexture works.
|
|
@@ -73,11 +97,30 @@ export class NormalDepth extends RenderStage {
|
|
|
73
97
|
|
|
74
98
|
this._triStorageNode = null;
|
|
75
99
|
this._bvhStorageNode = null;
|
|
100
|
+
this._matStorageNode = null;
|
|
76
101
|
this._lastTriAttr = null;
|
|
77
102
|
this._lastBvhAttr = null;
|
|
103
|
+
this._lastMatAttr = null;
|
|
78
104
|
this._computeNode = null;
|
|
79
105
|
this._computeBuilt = false;
|
|
80
106
|
|
|
107
|
+
// Normal/bump map array nodes — persistent placeholders, value swapped to
|
|
108
|
+
// the real DataArrayTextures on model load. processNormal/processBump
|
|
109
|
+
// runtime-guard on map indices, so the placeholder is never sampled.
|
|
110
|
+
this._normalMapsTex = texture( this._makePlaceholderArray() );
|
|
111
|
+
this._bumpMapsTex = texture( this._makePlaceholderArray() );
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_makePlaceholderArray() {
|
|
116
|
+
|
|
117
|
+
const t = new DataArrayTexture( new Uint8Array( [ 128, 128, 255, 255 ] ), 1, 1, 1 );
|
|
118
|
+
t.minFilter = LinearFilter;
|
|
119
|
+
t.magFilter = LinearFilter;
|
|
120
|
+
t.generateMipmaps = false;
|
|
121
|
+
t.needsUpdate = true;
|
|
122
|
+
return t;
|
|
123
|
+
|
|
81
124
|
}
|
|
82
125
|
|
|
83
126
|
setupEventListeners() {
|
|
@@ -102,10 +145,12 @@ export class NormalDepth extends RenderStage {
|
|
|
102
145
|
const pt = this.pathTracer;
|
|
103
146
|
if ( ! pt ) return false;
|
|
104
147
|
|
|
148
|
+
const matAttr = pt.materialData?.materialStorageAttr;
|
|
105
149
|
const triSwapped = pt.triangleStorageAttr && pt.triangleStorageAttr !== this._lastTriAttr;
|
|
106
150
|
const bvhSwapped = pt.bvhStorageAttr && pt.bvhStorageAttr !== this._lastBvhAttr;
|
|
151
|
+
const matSwapped = matAttr && matAttr !== this._lastMatAttr;
|
|
107
152
|
|
|
108
|
-
if ( triSwapped || bvhSwapped ) {
|
|
153
|
+
if ( triSwapped || bvhSwapped || matSwapped ) {
|
|
109
154
|
|
|
110
155
|
// Buffer identity changed → compute's bind group is stale; rebuild.
|
|
111
156
|
this._computeNode?.dispose?.();
|
|
@@ -113,6 +158,7 @@ export class NormalDepth extends RenderStage {
|
|
|
113
158
|
this._computeBuilt = false;
|
|
114
159
|
this._triStorageNode = null;
|
|
115
160
|
this._bvhStorageNode = null;
|
|
161
|
+
this._matStorageNode = null;
|
|
116
162
|
this._dirty = true;
|
|
117
163
|
|
|
118
164
|
}
|
|
@@ -133,10 +179,22 @@ export class NormalDepth extends RenderStage {
|
|
|
133
179
|
|
|
134
180
|
}
|
|
135
181
|
|
|
182
|
+
if ( matAttr && ! this._matStorageNode ) {
|
|
183
|
+
|
|
184
|
+
this._matStorageNode = storage( matAttr, 'vec4', matAttr.count ).toReadOnly();
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// In-place map swaps (model change) — graph closes over the node, only .value changes.
|
|
189
|
+
const md = pt.materialData;
|
|
190
|
+
if ( md?.normalMaps ) this._normalMapsTex.value = md.normalMaps;
|
|
191
|
+
if ( md?.bumpMaps ) this._bumpMapsTex.value = md.bumpMaps;
|
|
192
|
+
|
|
136
193
|
this._lastTriAttr = pt.triangleStorageAttr || this._lastTriAttr;
|
|
137
194
|
this._lastBvhAttr = pt.bvhStorageAttr || this._lastBvhAttr;
|
|
195
|
+
this._lastMatAttr = matAttr || this._lastMatAttr;
|
|
138
196
|
|
|
139
|
-
return !! ( this._triStorageNode && this._bvhStorageNode );
|
|
197
|
+
return !! ( this._triStorageNode && this._bvhStorageNode && this._matStorageNode );
|
|
140
198
|
|
|
141
199
|
}
|
|
142
200
|
|
|
@@ -144,11 +202,15 @@ export class NormalDepth extends RenderStage {
|
|
|
144
202
|
|
|
145
203
|
const triStorage = this._triStorageNode;
|
|
146
204
|
const bvhStorage = this._bvhStorageNode;
|
|
205
|
+
const matStorage = this._matStorageNode;
|
|
206
|
+
const normalMaps = this._normalMapsTex;
|
|
207
|
+
const bumpMaps = this._bumpMapsTex;
|
|
147
208
|
const camWorld = this.cameraWorldMatrix;
|
|
148
209
|
const camProjInv = this.cameraProjectionMatrixInverse;
|
|
149
210
|
const resW = this.resolutionWidth;
|
|
150
211
|
const resH = this.resolutionHeight;
|
|
151
212
|
const outputTex = this._outputStorageTex;
|
|
213
|
+
const shadingTex = this._shadingStorageTex;
|
|
152
214
|
|
|
153
215
|
const WG_SIZE = 8;
|
|
154
216
|
|
|
@@ -195,6 +257,31 @@ export class NormalDepth extends RenderStage {
|
|
|
195
257
|
result
|
|
196
258
|
).toWriteOnly();
|
|
197
259
|
|
|
260
|
+
// Shading normal: perturb the geometric normal by the normal/bump map
|
|
261
|
+
// from the SAME hit (deterministic UV → jitter-free). Miss → geo default.
|
|
262
|
+
const shadingNormal = hit.normal.toVar();
|
|
263
|
+
If( hit.didHit, () => {
|
|
264
|
+
|
|
265
|
+
const material = RayTracingMaterial.wrap(
|
|
266
|
+
getMaterial( hit.materialIndex, matStorage )
|
|
267
|
+
).toVar();
|
|
268
|
+
const uvCache = UVCache.wrap( computeUVCache( hit.uv, material ) ).toVar();
|
|
269
|
+
const mapped = processNormal( normalMaps, hit.normal, material, uvCache ).toVar();
|
|
270
|
+
shadingNormal.assign( processBump( bumpMaps, mapped, material, uvCache ) );
|
|
271
|
+
|
|
272
|
+
} );
|
|
273
|
+
|
|
274
|
+
const shadingResult = hit.didHit.select(
|
|
275
|
+
vec4( shadingNormal.mul( 0.5 ).add( 0.5 ), depth ),
|
|
276
|
+
vec4( 0.0, 0.0, 0.0, float( 1e6 ) )
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
textureStore(
|
|
280
|
+
shadingTex,
|
|
281
|
+
uvec2( uint( gx ), uint( gy ) ),
|
|
282
|
+
shadingResult
|
|
283
|
+
).toWriteOnly();
|
|
284
|
+
|
|
198
285
|
} );
|
|
199
286
|
|
|
200
287
|
} );
|
|
@@ -233,6 +320,7 @@ export class NormalDepth extends RenderStage {
|
|
|
233
320
|
const currentRT = this._currentIdx === 0 ? this._rtA : this._rtB;
|
|
234
321
|
context.setTexture( 'pathtracer:normalDepth', currentRT.texture );
|
|
235
322
|
context.setTexture( 'pathtracer:prevNormalDepth', currentRT.texture );
|
|
323
|
+
context.setTexture( 'pathtracer:shadingNormal', this._shadingRT.texture );
|
|
236
324
|
return;
|
|
237
325
|
|
|
238
326
|
}
|
|
@@ -257,9 +345,10 @@ export class NormalDepth extends RenderStage {
|
|
|
257
345
|
|
|
258
346
|
this.renderer.compute( this._computeNode );
|
|
259
347
|
|
|
260
|
-
// Copy only the active region out of the over-allocated
|
|
348
|
+
// Copy only the active region out of the over-allocated StorageTextures.
|
|
261
349
|
this._srcRegion.max.set( writeRT.width, writeRT.height );
|
|
262
350
|
this.renderer.copyTextureToTexture( this._outputStorageTex, writeRT.texture, this._srcRegion );
|
|
351
|
+
this.renderer.copyTextureToTexture( this._shadingStorageTex, this._shadingRT.texture, this._srcRegion );
|
|
263
352
|
|
|
264
353
|
// First dispatch: seed prev from current so ASVGF doesn't see false
|
|
265
354
|
// disocclusion on frame 1.
|
|
@@ -272,6 +361,7 @@ export class NormalDepth extends RenderStage {
|
|
|
272
361
|
|
|
273
362
|
context.setTexture( 'pathtracer:normalDepth', writeRT.texture );
|
|
274
363
|
context.setTexture( 'pathtracer:prevNormalDepth', prevRT.texture );
|
|
364
|
+
context.setTexture( 'pathtracer:shadingNormal', this._shadingRT.texture );
|
|
275
365
|
|
|
276
366
|
this._dirty = false;
|
|
277
367
|
|
|
@@ -294,6 +384,8 @@ export class NormalDepth extends RenderStage {
|
|
|
294
384
|
this._rtA.texture.needsUpdate = true;
|
|
295
385
|
this._rtB.setSize( width, height );
|
|
296
386
|
this._rtB.texture.needsUpdate = true;
|
|
387
|
+
this._shadingRT.setSize( width, height );
|
|
388
|
+
this._shadingRT.texture.needsUpdate = true;
|
|
297
389
|
this._hasHistory = false;
|
|
298
390
|
this.resolutionWidth.value = width;
|
|
299
391
|
this.resolutionHeight.value = height;
|
|
@@ -314,6 +406,8 @@ export class NormalDepth extends RenderStage {
|
|
|
314
406
|
|
|
315
407
|
this._computeNode?.dispose();
|
|
316
408
|
this._outputStorageTex?.dispose();
|
|
409
|
+
this._shadingStorageTex?.dispose();
|
|
410
|
+
this._shadingRT?.dispose();
|
|
317
411
|
this._rtA?.dispose();
|
|
318
412
|
this._rtB?.dispose();
|
|
319
413
|
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -754,6 +754,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
754
754
|
totalTriangleCount: this.totalTriangleCount,
|
|
755
755
|
enableEmissiveTriangleSampling: this.enableEmissiveTriangleSampling,
|
|
756
756
|
lightBVHNodeCount: this.lightBVHNodeCount,
|
|
757
|
+
reverseMapVec4Offset: this.reverseMapVec4Offset,
|
|
757
758
|
currentBounce: this._wfCurrentBounce,
|
|
758
759
|
maxRayCount: this._wfMaxRayCount,
|
|
759
760
|
} );
|
|
@@ -198,9 +198,12 @@ export class PathTracerStage extends RenderStage {
|
|
|
198
198
|
this.lightStorageAttr = new StorageInstancedBufferAttribute( new Float32Array( 16 ), 4 );
|
|
199
199
|
this.lightStorageNode = storage( this.lightStorageAttr, 'vec4', 1 ).toReadOnly();
|
|
200
200
|
|
|
201
|
-
// Cached CPU-side data — rebuilt into the packed buffer whenever
|
|
201
|
+
// Cached CPU-side data — rebuilt into the packed buffer whenever any source changes.
|
|
202
202
|
this._lbvhDataCache = null;
|
|
203
203
|
this._emissiveDataCache = null;
|
|
204
|
+
// Per-triangle bit-trail map (root→leaf Light BVH path, 1 float per triangleIndex); packed
|
|
205
|
+
// after the emissive entries so the bounce-hit MIS path can re-walk the descent pdf.
|
|
206
|
+
this._bitTrailMapCache = null;
|
|
204
207
|
|
|
205
208
|
// Per-mesh visibility is packed into the TLAS BLAS-pointer leaf's slot [2]
|
|
206
209
|
// (see TLASBuilder.flatten + BVHTraversal.js). The InstanceTable holds the
|
|
@@ -1296,14 +1299,18 @@ export class PathTracerStage extends RenderStage {
|
|
|
1296
1299
|
const LBVH_STRIDE = 4; // vec4s per LBVH node — must match LightBVHSampling.js
|
|
1297
1300
|
const lbvh = this._lbvhDataCache;
|
|
1298
1301
|
const emis = this._emissiveDataCache;
|
|
1302
|
+
const trail = this._bitTrailMapCache;
|
|
1299
1303
|
const lbvhLen = lbvh ? lbvh.length : 0;
|
|
1300
1304
|
const emisLen = emis ? emis.length : 0;
|
|
1305
|
+
// Bit-trail map packs 4 trails per vec4 → pad to a vec4 boundary.
|
|
1306
|
+
const trailPadded = trail ? Math.ceil( trail.length / 4 ) * 4 : 0;
|
|
1301
1307
|
|
|
1302
1308
|
// Ensure at least a minimal non-empty buffer so GPU allocation remains valid.
|
|
1303
|
-
const totalLen = Math.max( lbvhLen + emisLen, 4 );
|
|
1309
|
+
const totalLen = Math.max( lbvhLen + emisLen + trailPadded, 4 );
|
|
1304
1310
|
const combined = new Float32Array( totalLen );
|
|
1305
1311
|
if ( lbvh ) combined.set( lbvh, 0 );
|
|
1306
1312
|
if ( emis ) combined.set( emis, lbvhLen );
|
|
1313
|
+
if ( trail ) combined.set( trail, lbvhLen + emisLen );
|
|
1307
1314
|
|
|
1308
1315
|
this.lightStorageAttr = new StorageInstancedBufferAttribute( combined, 4 );
|
|
1309
1316
|
this.lightStorageNode.value = this.lightStorageAttr;
|
|
@@ -1311,14 +1318,18 @@ export class PathTracerStage extends RenderStage {
|
|
|
1311
1318
|
|
|
1312
1319
|
// Offset (in vec4 elements) where emissive data starts.
|
|
1313
1320
|
this.emissiveVec4Offset.value = ( this.lightBVHNodeCount.value || 0 ) * LBVH_STRIDE;
|
|
1321
|
+
// Offset (in vec4 elements) where the bit-trail map starts (lbvhLen + emisLen are float
|
|
1322
|
+
// counts, both multiples of 4, so this divides cleanly).
|
|
1323
|
+
this.reverseMapVec4Offset.value = ( lbvhLen + emisLen ) / 4;
|
|
1314
1324
|
|
|
1315
1325
|
}
|
|
1316
1326
|
|
|
1317
|
-
setEmissiveTriangleData( emissiveData, count, totalPower = 0 ) {
|
|
1327
|
+
setEmissiveTriangleData( emissiveData, count, totalPower = 0, bitTrailMap = null ) {
|
|
1318
1328
|
|
|
1319
1329
|
if ( ! emissiveData ) return;
|
|
1320
1330
|
|
|
1321
1331
|
this._emissiveDataCache = emissiveData;
|
|
1332
|
+
if ( bitTrailMap ) this._bitTrailMapCache = bitTrailMap;
|
|
1322
1333
|
this.emissiveTriangleCount.value = count;
|
|
1323
1334
|
this.emissiveTotalPower.value = totalPower;
|
|
1324
1335
|
this._rebuildLightBuffer();
|
package/src/Stages/Variance.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fn, wgslFn, float, int, uint, ivec2, uvec2, uniform, If, max,
|
|
1
|
+
import { Fn, wgslFn, float, int, uint, ivec2, uvec2, uniform, If, max, select,
|
|
2
2
|
textureLoad, textureStore, workgroupArray, workgroupBarrier, localId, workgroupId } from 'three/tsl';
|
|
3
3
|
import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
|
|
4
4
|
import { FloatType, RGBAFormat, LinearFilter, Box2, Vector2 } from 'three';
|
|
@@ -6,6 +6,10 @@ import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
|
6
6
|
import { luminance } from '../TSL/Common.js';
|
|
7
7
|
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
8
8
|
|
|
9
|
+
// NaN/±Inf guard: a poisoned luminance would otherwise corrupt the moment EMA forever
|
|
10
|
+
// (mean/meanSq → variance → BilateralFilter sigmaL). NaN (x!=x) → 0, ±Inf → [0,1e7].
|
|
11
|
+
const sanitizeLum = ( x ) => select( x.equal( x ), x, float( 0.0 ) ).clamp( 0.0, 1e7 );
|
|
12
|
+
|
|
9
13
|
// ── wgslFn helpers ──────────────────────────────────────────
|
|
10
14
|
|
|
11
15
|
/**
|
|
@@ -203,7 +207,7 @@ export class Variance extends RenderStage {
|
|
|
203
207
|
const gy1 = tileOriginY.add( int( sy1 ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
204
208
|
|
|
205
209
|
const sColor1 = textureLoad( colorTex, ivec2( gx1, gy1 ) ).xyz;
|
|
206
|
-
sharedLum.element( linearIdx ).assign( luminance( sColor1 ) );
|
|
210
|
+
sharedLum.element( linearIdx ).assign( sanitizeLum( luminance( sColor1 ) ) );
|
|
207
211
|
|
|
208
212
|
// Load #2: threads 0-35 load positions 64-99
|
|
209
213
|
If( linearIdx.lessThan( uint( EXTRA_LOAD ) ), () => {
|
|
@@ -215,7 +219,7 @@ export class Variance extends RenderStage {
|
|
|
215
219
|
const gy2 = tileOriginY.add( int( sy2 ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
216
220
|
|
|
217
221
|
const sColor2 = textureLoad( colorTex, ivec2( gx2, gy2 ) ).xyz;
|
|
218
|
-
sharedLum.element( idx2 ).assign( luminance( sColor2 ) );
|
|
222
|
+
sharedLum.element( idx2 ).assign( sanitizeLum( luminance( sColor2 ) ) );
|
|
219
223
|
|
|
220
224
|
} );
|
|
221
225
|
|
|
@@ -312,8 +312,9 @@ export const calculateEmissiveLightPdf = Fn( ( [
|
|
|
312
312
|
// Targeted material read: only fetch emissive data (2 vec4s instead of full 27)
|
|
313
313
|
const matData1 = getDatafromStorageBuffer( materialBuffer, triData.materialIndex, int( MATERIAL_SLOT.EMISSIVE_ROUGHNESS ), MATERIAL_SLOTS );
|
|
314
314
|
const matData2 = getDatafromStorageBuffer( materialBuffer, triData.materialIndex, int( MATERIAL_SLOT.IOR_TRANSMISSION ), MATERIAL_SLOTS );
|
|
315
|
-
|
|
316
|
-
const
|
|
315
|
+
// Rec.709 luma — must match EmissiveTriangleBuilder's power weighting for MIS consistency.
|
|
316
|
+
const luma = matData1.x.mul( 0.2126 ).add( matData1.y.mul( 0.7152 ) ).add( matData1.z.mul( 0.0722 ) );
|
|
317
|
+
const power = max( luma.mul( matData2.a ).mul( area ), float( 1e-10 ) );
|
|
317
318
|
const selectionPdf = power.div( max( emissiveTotalPower, float( 1e-10 ) ) );
|
|
318
319
|
|
|
319
320
|
const result = float( 0.0 ).toVar();
|