rayzee 7.20.3 → 7.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/rayzee.es.js +2621 -2683
- package/dist/rayzee.umd.js +60 -58
- package/package.json +1 -1
- package/src/EngineDefaults.js +3 -2
- package/src/PathTracerApp.js +7 -0
- package/src/Pipeline/BindingAudit.js +178 -0
- package/src/Pipeline/RenderPipeline.js +5 -2
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/PackedRayBuffer.js +33 -8
- package/src/Stages/ASVGF.js +10 -10
- package/src/Stages/BilateralFilter.js +70 -11
- package/src/Stages/EdgeFilter.js +44 -6
- package/src/Stages/NormalDepth.js +10 -2
- package/src/Stages/PathTracer.js +7 -3
- package/src/Stages/PathTracerStage.js +15 -46
- package/src/TSL/Common.js +1 -47
- package/src/TSL/EmissiveSampling.js +40 -44
- package/src/TSL/LightsCore.js +3 -0
- package/src/TSL/LightsDirect.js +4 -4
- package/src/TSL/LightsSampling.js +138 -327
- package/src/TSL/PathTracerCore.js +2 -2
- package/src/TSL/Random.js +2 -2
- package/src/TSL/ShadeKernel.js +39 -17
- package/src/TSL/Struct.js +0 -10
- package/src/index.js +5 -0
- package/src/managers/EnvironmentManager.js +0 -5
- package/src/managers/UniformManager.js +4 -1
package/src/Stages/EdgeFilter.js
CHANGED
|
@@ -73,6 +73,8 @@ export class EdgeFilter extends RenderStage {
|
|
|
73
73
|
|
|
74
74
|
this.stepSizeU = uniform( 1, 'int' );
|
|
75
75
|
this.isLastIterationU = uniform( 0, 'int' );
|
|
76
|
+
// 1 on pass 0 → seed variance from variance:output; later passes read the carried value.
|
|
77
|
+
this.isFirstIterationU = uniform( 1, 'int' );
|
|
76
78
|
this.resW = uniform( options.width || 1 );
|
|
77
79
|
this.resH = uniform( options.height || 1 );
|
|
78
80
|
|
|
@@ -202,6 +204,7 @@ export class EdgeFilter extends RenderStage {
|
|
|
202
204
|
const phiDepth = this.phiDepth;
|
|
203
205
|
const stepSize = this.stepSizeU;
|
|
204
206
|
const isLastIterationU = this.isLastIterationU;
|
|
207
|
+
const isFirstIterationU = this.isFirstIterationU;
|
|
205
208
|
const resW = this.resW;
|
|
206
209
|
const resH = this.resH;
|
|
207
210
|
|
|
@@ -216,7 +219,8 @@ export class EdgeFilter extends RenderStage {
|
|
|
216
219
|
|
|
217
220
|
const coord = ivec2( gx, gy );
|
|
218
221
|
|
|
219
|
-
const
|
|
222
|
+
const centerSample = textureLoad( readTex, coord );
|
|
223
|
+
const centerLighting = centerSample.xyz;
|
|
220
224
|
const centerLum = luminance( centerLighting );
|
|
221
225
|
const centerND = textureLoad( ndTex, coord );
|
|
222
226
|
const centerDepth = centerND.w;
|
|
@@ -233,13 +237,30 @@ export class EdgeFilter extends RenderStage {
|
|
|
233
237
|
// max() falls back to the spatial estimate as temporal collapses on a
|
|
234
238
|
// converging accumulation buffer. Dividing by albedoLum rescales the
|
|
235
239
|
// modulated-space variance into demodulated space (std ∝ 1/albedo).
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
240
|
+
//
|
|
241
|
+
// Filtered alongside colour and carried between passes as √variance in the
|
|
242
|
+
// ping-pong's spare alpha, so sigma_l tightens as the signal smooths. Stored as
|
|
243
|
+
// the root because converged variance (~1e-6) is a half-float subnormal.
|
|
244
|
+
const isFirst = isFirstIterationU.equal( int( 1 ) );
|
|
245
|
+
const seedVariance = ( at ) => {
|
|
246
|
+
|
|
247
|
+
const v = textureLoad( varTex, at );
|
|
248
|
+
return max( v.z, v.w );
|
|
249
|
+
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const centerVariance = isFirst.select(
|
|
253
|
+
seedVariance( coord ),
|
|
254
|
+
centerSample.w.mul( centerSample.w )
|
|
255
|
+
).toVar();
|
|
256
|
+
|
|
257
|
+
const sigmaL = phiLuminance.mul( sqrt( max( centerVariance, float( 0.0 ) ) ) )
|
|
239
258
|
.div( centerAlbedoLum ).add( float( 0.0001 ) );
|
|
240
259
|
|
|
241
260
|
const colorSum = vec3( 0.0 ).toVar();
|
|
242
261
|
const weightSum = float( 0.0 ).toVar();
|
|
262
|
+
// Squared weights, normalised by weightSum² — the variance of a weighted mean.
|
|
263
|
+
const varianceSum = float( 0.0 ).toVar();
|
|
243
264
|
|
|
244
265
|
for ( let iy = 0; iy < 5; iy ++ ) {
|
|
245
266
|
|
|
@@ -253,7 +274,8 @@ export class EdgeFilter extends RenderStage {
|
|
|
253
274
|
const sy = gy.add( stepSize.mul( dy ) ).clamp( int( 0 ), int( resH ).sub( 1 ) );
|
|
254
275
|
const sCoord = ivec2( sx, sy );
|
|
255
276
|
|
|
256
|
-
const
|
|
277
|
+
const sSample = textureLoad( readTex, sCoord );
|
|
278
|
+
const sLighting = sSample.xyz;
|
|
257
279
|
const sLum = luminance( sLighting );
|
|
258
280
|
const sND = textureLoad( ndTex, sCoord );
|
|
259
281
|
const sDepth = sND.w;
|
|
@@ -276,14 +298,22 @@ export class EdgeFilter extends RenderStage {
|
|
|
276
298
|
const geomW = mix( float( 1.0 ), normW.mul( depW ), bothHit );
|
|
277
299
|
const w = float( kw ).mul( lumW ).mul( geomW ).mul( sameKind );
|
|
278
300
|
|
|
301
|
+
const sVariance = isFirst.select(
|
|
302
|
+
seedVariance( sCoord ),
|
|
303
|
+
sSample.w.mul( sSample.w )
|
|
304
|
+
);
|
|
305
|
+
|
|
279
306
|
colorSum.addAssign( sLighting.mul( w ) );
|
|
280
307
|
weightSum.addAssign( w );
|
|
308
|
+
varianceSum.addAssign( w.mul( w ).mul( max( sVariance, float( 0.0 ) ) ) );
|
|
281
309
|
|
|
282
310
|
}
|
|
283
311
|
|
|
284
312
|
}
|
|
285
313
|
|
|
286
314
|
const filtered = colorSum.div( max( weightSum, float( 0.0001 ) ) );
|
|
315
|
+
const filteredVariance = varianceSum
|
|
316
|
+
.div( max( weightSum.mul( weightSum ), float( 1e-8 ) ) );
|
|
287
317
|
|
|
288
318
|
// Final pass: remodulate by albedo and blend against the raw color by
|
|
289
319
|
// filterStrength. Inner passes stay in demodulated space.
|
|
@@ -293,7 +323,14 @@ export class EdgeFilter extends RenderStage {
|
|
|
293
323
|
const finalModulated = mix( rawColor, remodded, filterStrength );
|
|
294
324
|
const output = isLast.select( finalModulated, filtered );
|
|
295
325
|
|
|
296
|
-
|
|
326
|
+
// The last pass restores the path tracer's coverage — Compositor reads .w as
|
|
327
|
+
// image alpha when transparentBackground is on.
|
|
328
|
+
const carriedAlpha = isLast.select(
|
|
329
|
+
textureLoad( colorTex, coord ).w,
|
|
330
|
+
sqrt( max( filteredVariance, float( 0.0 ) ) )
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
textureStore( writeStorageTex, uvec2( uint( gx ), uint( gy ) ), vec4( output, carriedAlpha ) ).toWriteOnly();
|
|
297
334
|
|
|
298
335
|
} );
|
|
299
336
|
|
|
@@ -378,6 +415,7 @@ export class EdgeFilter extends RenderStage {
|
|
|
378
415
|
|
|
379
416
|
this.stepSizeU.value = 1 << i;
|
|
380
417
|
this.isLastIterationU.value = ( i === this.iterations - 1 ) ? 1 : 0;
|
|
418
|
+
this.isFirstIterationU.value = i === 0 ? 1 : 0;
|
|
381
419
|
this._readTexNode.value = readTex;
|
|
382
420
|
|
|
383
421
|
this.renderer.compute( writeNode );
|
|
@@ -122,8 +122,11 @@ export class NormalDepth extends RenderStage {
|
|
|
122
122
|
|
|
123
123
|
this.on( 'pipeline:reset', () => {
|
|
124
124
|
|
|
125
|
+
// _hasHistory is deliberately NOT cleared, mirroring MotionVector.reset(): the
|
|
126
|
+
// prev-frame G-buffer tracks camera motion, not accumulation, and a camera move
|
|
127
|
+
// resets accumulation every frame of a drag. Clearing it made render() overwrite
|
|
128
|
+
// prevRT with the current frame, so ASVGF's temporal gate could never reject.
|
|
125
129
|
this._dirty = true;
|
|
126
|
-
this._hasHistory = false;
|
|
127
130
|
|
|
128
131
|
} );
|
|
129
132
|
|
|
@@ -149,6 +152,8 @@ export class NormalDepth extends RenderStage {
|
|
|
149
152
|
this._bvhStorageNode = null;
|
|
150
153
|
this._matStorageNode = null;
|
|
151
154
|
this._dirty = true;
|
|
155
|
+
// New geometry — the previous frame describes a scene that is no longer there.
|
|
156
|
+
this._hasHistory = false;
|
|
152
157
|
|
|
153
158
|
}
|
|
154
159
|
|
|
@@ -367,13 +372,16 @@ export class NormalDepth extends RenderStage {
|
|
|
367
372
|
this._outputStorageTex?.dispose();
|
|
368
373
|
this._shadingStorageTex?.dispose();
|
|
369
374
|
this.reset();
|
|
375
|
+
// The textures backing the ping-pong are gone, so the prev-frame G-buffer is too.
|
|
376
|
+
this._hasHistory = false;
|
|
370
377
|
|
|
371
378
|
}
|
|
372
379
|
|
|
380
|
+
// Accumulation reset only — _hasHistory survives on purpose (see the pipeline:reset
|
|
381
|
+
// listener). It is invalidated by setSize(), a geometry swap, and releaseGPUMemory().
|
|
373
382
|
reset() {
|
|
374
383
|
|
|
375
384
|
this._dirty = true;
|
|
376
|
-
this._hasHistory = false;
|
|
377
385
|
|
|
378
386
|
}
|
|
379
387
|
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -272,6 +272,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
272
272
|
|
|
273
273
|
this._updateAccumulationUniforms( frameValue, renderMode );
|
|
274
274
|
this.frame.value = frameValue;
|
|
275
|
+
this.seedFrame.value = this._seedTick ++;
|
|
275
276
|
|
|
276
277
|
this._setWfDispatch();
|
|
277
278
|
|
|
@@ -942,7 +943,9 @@ export class PathTracer extends PathTracerStage {
|
|
|
942
943
|
rngBufferRW: pb.rngBuffer.rw,
|
|
943
944
|
gBufferRW,
|
|
944
945
|
resolution: this.resolution,
|
|
945
|
-
|
|
946
|
+
// RNG axis only (baseSeed + stratified jitter) — takes the seed counter, not the
|
|
947
|
+
// accumulation index. FinalWrite keeps `frame`.
|
|
948
|
+
frame: this.seedFrame,
|
|
946
949
|
cameraWorldMatrix: this.cameraWorldMatrix,
|
|
947
950
|
cameraProjectionMatrixInverse: this.cameraProjectionMatrixInverse,
|
|
948
951
|
enableDOF: this.enableDOF,
|
|
@@ -1180,7 +1183,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
1180
1183
|
environmentIntensity: this.environmentIntensity,
|
|
1181
1184
|
envMatrix: this.environmentMatrix,
|
|
1182
1185
|
enableEnvironmentLight: this.enableEnvironment,
|
|
1183
|
-
useEnvMapIS: this.useEnvMapIS,
|
|
1184
1186
|
groundProjectionEnabled: this.groundProjectionEnabled,
|
|
1185
1187
|
groundProjectionRadius: this.groundProjectionRadius,
|
|
1186
1188
|
groundProjectionHeight: this.groundProjectionHeight,
|
|
@@ -1210,7 +1212,9 @@ export class PathTracer extends PathTracerStage {
|
|
|
1210
1212
|
cameraProjectionMatrix: this.cameraProjectionMatrix,
|
|
1211
1213
|
cameraViewMatrix: this.cameraViewMatrix,
|
|
1212
1214
|
fireflyThreshold: this.fireflyThreshold,
|
|
1213
|
-
frame
|
|
1215
|
+
// RNG axis only (keys STBN via frame & 63).
|
|
1216
|
+
frame: this.seedFrame,
|
|
1217
|
+
accumFrame: this.frame,
|
|
1214
1218
|
resolution: this.resolution,
|
|
1215
1219
|
emissiveTriangleCount: this.emissiveTriangleCount,
|
|
1216
1220
|
emissiveVec4Offset: this.emissiveVec4Offset,
|
|
@@ -51,7 +51,6 @@ const BVH_VEC4_PER_NODE = 4;
|
|
|
51
51
|
* - camera:moved - When camera position/orientation changes
|
|
52
52
|
* - asvgf:reset - Request ASVGF to reset temporal data
|
|
53
53
|
* - asvgf:updateParameters - Update ASVGF parameters
|
|
54
|
-
* - asvgf:setTemporal - Enable/disable ASVGF temporal accumulation
|
|
55
54
|
*
|
|
56
55
|
* Textures published to context:
|
|
57
56
|
* - pathtracer:color - Main color output
|
|
@@ -143,6 +142,11 @@ export class PathTracerStage extends RenderStage {
|
|
|
143
142
|
this.lastCameraMatrix = new Matrix4();
|
|
144
143
|
this.lastProjectionMatrix = new Matrix4();
|
|
145
144
|
|
|
145
|
+
// Sampler seed axis — advances per rendered frame, survives accumulation resets.
|
|
146
|
+
// Pinned back to the accumulation index in deterministic mode.
|
|
147
|
+
this._seedTick = 0;
|
|
148
|
+
this._pinSeedToFrame = false;
|
|
149
|
+
|
|
146
150
|
// Denoising management state
|
|
147
151
|
this.lastRenderMode = - 1;
|
|
148
152
|
this.renderModeChangeTimeout = null;
|
|
@@ -293,18 +297,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
293
297
|
|
|
294
298
|
}
|
|
295
299
|
},
|
|
296
|
-
useEnvMapIS: {
|
|
297
|
-
get value() {
|
|
298
|
-
|
|
299
|
-
return self.useEnvMapIS.value;
|
|
300
|
-
|
|
301
|
-
},
|
|
302
|
-
set value( v ) {
|
|
303
|
-
|
|
304
|
-
self.useEnvMapIS.value = v;
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
},
|
|
308
300
|
enableAccumulation: {
|
|
309
301
|
get value() {
|
|
310
302
|
|
|
@@ -348,7 +340,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
348
340
|
enabled: DEFAULT_STATE.interactionModeEnabled,
|
|
349
341
|
qualitySettings: {
|
|
350
342
|
maxBounceCount: 1,
|
|
351
|
-
useEnvMapIS: false,
|
|
352
343
|
enableAccumulation: false,
|
|
353
344
|
enableEmissiveTriangleSampling: false,
|
|
354
345
|
},
|
|
@@ -651,10 +642,14 @@ export class PathTracerStage extends RenderStage {
|
|
|
651
642
|
this.isComplete = false;
|
|
652
643
|
this.performanceMonitor?.reset();
|
|
653
644
|
|
|
654
|
-
|
|
655
|
-
|
|
645
|
+
// lastRenderMode is deliberately NOT invalidated here — clearing it made
|
|
646
|
+
// manageASVGFForRenderMode see a phantom mode change 50 ms after every reset, wiping
|
|
647
|
+
// ASVGF's history. A real mode change is still caught by the renderMode comparison.
|
|
656
648
|
this.lastInteractionModeState = false;
|
|
657
649
|
|
|
650
|
+
// Only deterministic mode rewinds the seed axis; see _pinSeedToFrame.
|
|
651
|
+
if ( this._pinSeedToFrame ) this._seedTick = 0;
|
|
652
|
+
|
|
658
653
|
}
|
|
659
654
|
|
|
660
655
|
/**
|
|
@@ -1238,7 +1233,10 @@ export class PathTracerStage extends RenderStage {
|
|
|
1238
1233
|
if ( this.pendingRenderMode !== null && this.pendingRenderMode !== this.lastRenderMode ) {
|
|
1239
1234
|
|
|
1240
1235
|
this.lastRenderMode = this.pendingRenderMode;
|
|
1241
|
-
|
|
1236
|
+
// History from the previous mode is not comparable. temporalAlpha is NOT
|
|
1237
|
+
// touched here — it is owned by ASVGF_QUALITY_PRESETS, and overwriting it
|
|
1238
|
+
// with a hardcoded value meant `medium` and `high` never took effect.
|
|
1239
|
+
this.emit( 'asvgf:reset' );
|
|
1242
1240
|
|
|
1243
1241
|
}
|
|
1244
1242
|
|
|
@@ -1249,35 +1247,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
1249
1247
|
|
|
1250
1248
|
}
|
|
1251
1249
|
|
|
1252
|
-
this._handleFullQuadASVGF();
|
|
1253
|
-
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
_onRenderModeChanged( newMode ) {
|
|
1257
|
-
|
|
1258
|
-
if ( newMode === 1 ) {
|
|
1259
|
-
|
|
1260
|
-
this.emit( 'asvgf:updateParameters', {
|
|
1261
|
-
enableDebug: false,
|
|
1262
|
-
temporalAlpha: 0.15
|
|
1263
|
-
} );
|
|
1264
|
-
|
|
1265
|
-
} else {
|
|
1266
|
-
|
|
1267
|
-
this.emit( 'asvgf:updateParameters', {
|
|
1268
|
-
temporalAlpha: 0.1,
|
|
1269
|
-
} );
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
this.emit( 'asvgf:reset' );
|
|
1274
|
-
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
_handleFullQuadASVGF() {
|
|
1278
|
-
|
|
1279
|
-
this.emit( 'asvgf:setTemporal', { enabled: true } );
|
|
1280
|
-
|
|
1281
1250
|
}
|
|
1282
1251
|
|
|
1283
1252
|
// ===== UNIFORM & DATA SETTERS =====
|
package/src/TSL/Common.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { Fn, wgslFn, float, vec2, vec3, vec4, int, mat3, If, max, dot, clamp
|
|
1
|
+
import { Fn, wgslFn, float, vec2, vec3, vec4, int, mat3, If, max, dot, clamp } from 'three/tsl';
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
AnisoFrame,
|
|
5
5
|
DotProducts,
|
|
6
6
|
MaterialClassification,
|
|
7
|
-
MISStrategy,
|
|
8
7
|
RayTracingMaterial,
|
|
9
8
|
ShadowMaterial,
|
|
10
9
|
} from './Struct.js';
|
|
@@ -299,51 +298,6 @@ export const classifyMaterial = Fn( ( [ metalness, roughness, transmission, clea
|
|
|
299
298
|
|
|
300
299
|
} );
|
|
301
300
|
|
|
302
|
-
// Dynamic MIS strategy based on material properties
|
|
303
|
-
export const selectOptimalMISStrategy = Fn( ( [ roughness, metalness, bounceIndex, throughput ] ) => {
|
|
304
|
-
|
|
305
|
-
const throughputStrength = maxComponent( { v: throughput } ).toVar();
|
|
306
|
-
|
|
307
|
-
// Environment is now handled via deterministic NEE — not part of stochastic selection.
|
|
308
|
-
// Only lights and BRDF are stochastically selected.
|
|
309
|
-
const brdfWeight = float( 0.5 ).toVar();
|
|
310
|
-
const lightWeight = float( 0.5 ).toVar();
|
|
311
|
-
const useBRDFSampling = tslBool( true );
|
|
312
|
-
const useLightSampling = throughputStrength.greaterThan( 0.01 );
|
|
313
|
-
|
|
314
|
-
If( roughness.lessThan( 0.1 ).and( metalness.greaterThan( 0.8 ) ), () => {
|
|
315
|
-
|
|
316
|
-
// Highly specular materials — favor BRDF
|
|
317
|
-
brdfWeight.assign( 0.7 );
|
|
318
|
-
lightWeight.assign( 0.3 );
|
|
319
|
-
|
|
320
|
-
} ).ElseIf( roughness.greaterThan( 0.7 ), () => {
|
|
321
|
-
|
|
322
|
-
// Diffuse materials — favor light sampling
|
|
323
|
-
brdfWeight.assign( 0.4 );
|
|
324
|
-
lightWeight.assign( 0.6 );
|
|
325
|
-
|
|
326
|
-
} );
|
|
327
|
-
|
|
328
|
-
// Gentle adjustment for very deep bounces — shift toward BRDF
|
|
329
|
-
If( bounceIndex.greaterThan( int( 5 ) ), () => {
|
|
330
|
-
|
|
331
|
-
brdfWeight.assign( 0.6 );
|
|
332
|
-
lightWeight.assign( 0.4 );
|
|
333
|
-
|
|
334
|
-
} );
|
|
335
|
-
|
|
336
|
-
return MISStrategy( {
|
|
337
|
-
brdfWeight,
|
|
338
|
-
lightWeight,
|
|
339
|
-
envWeight: float( 0.0 ),
|
|
340
|
-
useBRDFSampling,
|
|
341
|
-
useLightSampling,
|
|
342
|
-
useEnvSampling: tslBool( false ),
|
|
343
|
-
} );
|
|
344
|
-
|
|
345
|
-
} );
|
|
346
|
-
|
|
347
301
|
// Storage buffer access — flat 1D indexing (WebGPU native)
|
|
348
302
|
// No 2D coordinate math needed: directly indexes into the buffer
|
|
349
303
|
export const getDatafromStorageBuffer = Fn( ( [ buffer, stride, sampleIndex, dataOffset ] ) => {
|
|
@@ -545,61 +545,57 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
|
|
|
545
545
|
distance: float( 0.0 ),
|
|
546
546
|
} ).toVar();
|
|
547
547
|
|
|
548
|
-
//
|
|
549
|
-
|
|
548
|
+
// No rough-diffuse secondary-bounce skip: dropping NEE while the emissive-hit MIS still
|
|
549
|
+
// down-weights BSDF hits deletes emitter energy from deep GI.
|
|
550
550
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
triangleBuffer,
|
|
559
|
-
) );
|
|
560
|
-
|
|
561
|
-
If( emissiveSample.valid.and( emissiveSample.pdf.greaterThan( 0.0 ) ), () => {
|
|
551
|
+
// Sample emissive triangle (CDF importance-weighted)
|
|
552
|
+
const emissiveSample = EmissiveSample.wrap( sampleEmissiveTriangle(
|
|
553
|
+
hitPoint, normal, rngState,
|
|
554
|
+
pixelCoord, resolution, frame, dimBase,
|
|
555
|
+
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
556
|
+
triangleBuffer,
|
|
557
|
+
) );
|
|
562
558
|
|
|
563
|
-
|
|
564
|
-
result.emissionOnly.assign( emissiveSample.emission );
|
|
565
|
-
result.distance.assign( emissiveSample.distance );
|
|
559
|
+
If( emissiveSample.valid.and( emissiveSample.pdf.greaterThan( 0.0 ) ), () => {
|
|
566
560
|
|
|
567
|
-
|
|
568
|
-
|
|
561
|
+
result.hasEmissive.assign( true );
|
|
562
|
+
result.emissionOnly.assign( emissiveSample.emission );
|
|
563
|
+
result.distance.assign( emissiveSample.distance );
|
|
569
564
|
|
|
570
|
-
|
|
565
|
+
// Check geometric validity
|
|
566
|
+
const NoL = max( float( 0.0 ), dot( normal, emissiveSample.direction ) );
|
|
571
567
|
|
|
572
|
-
|
|
573
|
-
const rayOffset = calculateRayOffsetFn( hitPoint, normal, material );
|
|
574
|
-
const rayOrigin = hitPoint.add( rayOffset );
|
|
568
|
+
If( NoL.greaterThan( 0.0 ), () => {
|
|
575
569
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
570
|
+
// Calculate ray offset for shadow ray
|
|
571
|
+
const rayOffset = calculateRayOffsetFn( hitPoint, normal, material );
|
|
572
|
+
const rayOrigin = hitPoint.add( rayOffset );
|
|
579
573
|
|
|
580
|
-
|
|
574
|
+
// Trace shadow ray
|
|
575
|
+
const shadowDist = emissiveSample.distance.sub( 0.001 );
|
|
576
|
+
const visibility = traceShadowRayFn( rayOrigin, emissiveSample.direction, shadowDist );
|
|
581
577
|
|
|
582
|
-
|
|
583
|
-
// would otherwise run twice with identical inputs).
|
|
584
|
-
const dots = DotProducts.wrap( computeDotProductsAniso( normal, viewDir, emissiveSample.direction, material ) );
|
|
585
|
-
const brdfValue = evaluateMaterialResponseFromDots( material, dots );
|
|
586
|
-
const brdfPdf = calculateMaterialPDFFromDots( material, dots );
|
|
578
|
+
If( visibility.greaterThan( 0.0 ), () => {
|
|
587
579
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
);
|
|
580
|
+
// Share H + dot products between BRDF eval and PDF (computeDotProducts
|
|
581
|
+
// would otherwise run twice with identical inputs).
|
|
582
|
+
const dots = DotProducts.wrap( computeDotProductsAniso( normal, viewDir, emissiveSample.direction, material ) );
|
|
583
|
+
const brdfValue = evaluateMaterialResponseFromDots( material, dots );
|
|
584
|
+
const brdfPdf = calculateMaterialPDFFromDots( material, dots );
|
|
594
585
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
586
|
+
// MIS weight: balance light sampling vs BRDF sampling
|
|
587
|
+
const misWeight = select(
|
|
588
|
+
brdfPdf.greaterThan( 0.0 ),
|
|
589
|
+
powerHeuristic( { pdf1: emissiveSample.pdf, pdf2: brdfPdf } ),
|
|
590
|
+
float( 1.0 )
|
|
591
|
+
);
|
|
601
592
|
|
|
602
|
-
|
|
593
|
+
// MC estimator: Le * brdf * cos_surface / pdf_solidAngle
|
|
594
|
+
// emissiveBoost is an additional user-controlled intensity multiplier
|
|
595
|
+
result.contribution.assign(
|
|
596
|
+
emissiveSample.emission.mul( brdfValue ).mul( NoL )
|
|
597
|
+
.div( emissiveSample.pdf ).mul( visibility ).mul( emissiveBoost ).mul( misWeight )
|
|
598
|
+
);
|
|
603
599
|
|
|
604
600
|
} );
|
|
605
601
|
|
package/src/TSL/LightsCore.js
CHANGED
|
@@ -83,6 +83,9 @@ export const LightSample = struct( {
|
|
|
83
83
|
distance: 'float',
|
|
84
84
|
lightType: 'int',
|
|
85
85
|
valid: 'bool',
|
|
86
|
+
// Reservoir denominator, carried out so the BSDF-hit MIS partner can rebuild the
|
|
87
|
+
// selection pdf without walking every light buffer a second time.
|
|
88
|
+
selectionTotalWeight: 'float',
|
|
86
89
|
} );
|
|
87
90
|
|
|
88
91
|
export const IndirectLightingResult = struct( {
|
package/src/TSL/LightsDirect.js
CHANGED
|
@@ -74,7 +74,7 @@ export const traceShadowRay = Fn( ( [
|
|
|
74
74
|
const rayOrigin = origin.toVar();
|
|
75
75
|
const remainingDist = float( maxDist ).toVar();
|
|
76
76
|
|
|
77
|
-
const MAX_SHADOW_TRANSMISSIONS =
|
|
77
|
+
const MAX_SHADOW_TRANSMISSIONS = 16;
|
|
78
78
|
|
|
79
79
|
Loop( { start: int( 0 ), end: int( MAX_SHADOW_TRANSMISSIONS ) }, () => {
|
|
80
80
|
|
|
@@ -160,7 +160,7 @@ export const traceShadowRay = Fn( ( [
|
|
|
160
160
|
const blendAlpha = clamp( shadowMaterial.color.a.mul( shadowMaterial.opacity ).mul( texAlpha ), 0.0, 1.0 );
|
|
161
161
|
transmittance.mulAssign( float( 1.0 ).sub( blendAlpha ) );
|
|
162
162
|
|
|
163
|
-
If( transmittance.lessThan(
|
|
163
|
+
If( transmittance.lessThan( 1e-4 ), () => {
|
|
164
164
|
|
|
165
165
|
transmittance.assign( 0.0 );
|
|
166
166
|
Break();
|
|
@@ -221,7 +221,7 @@ export const traceShadowRay = Fn( ( [
|
|
|
221
221
|
transmittance.mulAssign( matTransmittance );
|
|
222
222
|
|
|
223
223
|
// Early exit if almost no light passes through
|
|
224
|
-
If( transmittance.lessThan(
|
|
224
|
+
If( transmittance.lessThan( 1e-4 ), () => {
|
|
225
225
|
|
|
226
226
|
transmittance.assign( 0.0 );
|
|
227
227
|
Break();
|
|
@@ -237,7 +237,7 @@ export const traceShadowRay = Fn( ( [
|
|
|
237
237
|
// Handle transparent materials
|
|
238
238
|
transmittance.mulAssign( float( 1.0 ).sub( shadowMaterial.opacity ) );
|
|
239
239
|
|
|
240
|
-
If( transmittance.lessThan(
|
|
240
|
+
If( transmittance.lessThan( 1e-4 ), () => {
|
|
241
241
|
|
|
242
242
|
transmittance.assign( 0.0 );
|
|
243
243
|
Break();
|