rayzee 7.3.0 → 7.4.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 +2 -3
- package/dist/rayzee.es.js +1294 -1220
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +64 -50
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/EngineDefaults.js +9 -7
- package/src/Passes/OIDNDenoiser.js +27 -3
- package/src/PathTracerApp.js +4 -1
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/EmissiveTriangleBuilder.js +4 -3
- package/src/Processor/PackedRayBuffer.js +12 -33
- package/src/Processor/QueueManager.js +1 -0
- package/src/Processor/StorageTexturePool.js +11 -3
- package/src/Processor/VRAMTracker.js +50 -8
- package/src/RenderSettings.js +13 -2
- package/src/Stages/ASVGF.js +27 -0
- package/src/Stages/BilateralFilter.js +12 -0
- package/src/Stages/EdgeFilter.js +12 -0
- package/src/Stages/MotionVector.js +10 -0
- package/src/Stages/NormalDepth.js +10 -0
- package/src/Stages/PathTracer.js +35 -54
- package/src/Stages/PathTracerStage.js +0 -13
- package/src/Stages/SSRC.js +16 -0
- package/src/Stages/Variance.js +13 -0
- package/src/TSL/DebugKernel.js +2 -3
- package/src/TSL/FinalWriteKernel.js +28 -29
- package/src/TSL/GenerateKernel.js +12 -19
- package/src/TSL/ShadeKernel.js +160 -61
- package/src/managers/DenoisingManager.js +17 -0
- package/src/managers/UniformManager.js +4 -2
- package/src/managers/VideoRenderManager.js +0 -2
package/src/TSL/ShadeKernel.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import {
|
|
8
8
|
Fn, float, vec2, vec3, vec4, int, uint,
|
|
9
9
|
bool as tslBool,
|
|
10
|
-
If, normalize, max, exp, log, clamp, dot, length, select,
|
|
10
|
+
If, Loop, normalize, max, exp, log, clamp, dot, length, select,
|
|
11
11
|
instanceIndex,
|
|
12
12
|
sampler,
|
|
13
13
|
atomicAdd, atomicLoad, uintBitsToFloat,
|
|
@@ -25,7 +25,7 @@ import { traverseBVHShadow } from './BVHTraversal.js';
|
|
|
25
25
|
import { handleMaterialTransparency, MaterialInteractionResult } from './MaterialTransmission.js';
|
|
26
26
|
import { sampleChromaticCollision, sampleHenyeyGreenstein, subsurfaceCoefficients, CollisionSample, MediumCoeffs } from './Subsurface.js';
|
|
27
27
|
import { calculateIndirectLighting } from './LightsIndirect.js';
|
|
28
|
-
import { IndirectLightingResult } from './LightsCore.js';
|
|
28
|
+
import { IndirectLightingResult, sampleCone } from './LightsCore.js';
|
|
29
29
|
import { regularizePathContribution, generateSampledDirection, computeNDCDepth, handleRussianRoulette } from './PathTracerCore.js';
|
|
30
30
|
import { getImportanceSamplingInfo } from './MaterialProperties.js';
|
|
31
31
|
import { sampleClearcoat, ClearcoatResult } from './Clearcoat.js';
|
|
@@ -51,9 +51,9 @@ import {
|
|
|
51
51
|
readMediumStack, writeMediumStack, readMediumSigmaA, writeMediumSigmaA,
|
|
52
52
|
readPathBounces, readSssSteps, readSSSMedium, writeSSSMedium,
|
|
53
53
|
readHitDistance, readHitBarycentrics, readHitNormal,
|
|
54
|
-
readHitMaterialIndex, readHitTriangleIndex,
|
|
54
|
+
readHitMaterialIndex, readHitTriangleIndex,
|
|
55
55
|
writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf, writeRayRadiance,
|
|
56
|
-
writeGBuffer,
|
|
56
|
+
writeGBuffer, readGBuffer, gbDecodeNormalDepth,
|
|
57
57
|
readRayRadiance,
|
|
58
58
|
} from '../Processor/PackedRayBuffer.js';
|
|
59
59
|
|
|
@@ -64,9 +64,6 @@ const MISS_DIST = 1e19;
|
|
|
64
64
|
const CATCHER_T_MIN = 1e-4; // min ray-t for the analytic plane hit (skip behind/at the camera)
|
|
65
65
|
const CATCHER_LUMA_FLOOR = 1e-4; // denominator floor for the shadow ratio
|
|
66
66
|
const CATCHER_COVERAGE_MIN = 1e-3; // below this incident luma there is no shadow to catch
|
|
67
|
-
// Reserved G-buffer surface ID for catcher pixels (no real triangle); stable across frames so the
|
|
68
|
-
// denoiser treats the analytic plane as one coherent surface, not a background miss.
|
|
69
|
-
const CATCHER_SURFACE_ID = 0xFFFFFFFE;
|
|
70
67
|
|
|
71
68
|
export function buildShadeKernel( params ) {
|
|
72
69
|
|
|
@@ -91,7 +88,7 @@ export function buildShadeKernel( params ) {
|
|
|
91
88
|
spotLightsBuffer, numSpotLights,
|
|
92
89
|
maxBounceCount, maxSubsurfaceSteps,
|
|
93
90
|
currentBounce, // loop iteration = path length (advances on free bounces); drives RR/firefly/giScale
|
|
94
|
-
transparentBackground, backgroundIntensity, showBackground,
|
|
91
|
+
transparentBackground, backgroundIntensity, backgroundColor, backgroundBlurriness, backgroundBlurSamples, showBackground,
|
|
95
92
|
globalIlluminationIntensity,
|
|
96
93
|
cameraProjectionMatrix, cameraViewMatrix,
|
|
97
94
|
fireflyThreshold, frame, resolution,
|
|
@@ -99,10 +96,45 @@ export function buildShadeKernel( params ) {
|
|
|
99
96
|
emissiveBoost, totalTriangleCount, enableEmissiveTriangleSampling,
|
|
100
97
|
lightBVHNodeCount, reverseMapVec4Offset,
|
|
101
98
|
maxRayCount,
|
|
99
|
+
// Aux G-buffer (normal/depth/albedo + surface ID) feeds only the denoiser/OIDN MRT. Gated by a
|
|
100
|
+
// live uniform (1 = denoiser on) so the wavefront skips these writes when nothing consumes them.
|
|
101
|
+
auxGBufferEnabled,
|
|
102
102
|
} = params;
|
|
103
103
|
|
|
104
|
+
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
105
|
+
|
|
104
106
|
const useEmissiveNEE = lightBuffer !== undefined;
|
|
105
107
|
|
|
108
|
+
// Stochastic cone-jitter blur of an env backdrop lookup. Plain JS inliner (NOT a Fn — an rng Fn-param
|
|
109
|
+
// would freeze; see TSL pitfalls) so it mutates the caller's rngState .toVar() directly. Shared by the
|
|
110
|
+
// miss branch and the shadow catcher so their blur stays in lockstep (no horizon seam). normalize() the
|
|
111
|
+
// center direction — ground projection can return a non-unit vector, which would skew sampleCone's basis.
|
|
112
|
+
// Clamp the tap count to ≥1 so a 0 forced via the engine API can't produce a 0/0 NaN backdrop.
|
|
113
|
+
const sampleEnvBlurred = ( centerDir, halfAngle, samples, rng ) => {
|
|
114
|
+
|
|
115
|
+
const axis = normalize( centerDir ).toVar();
|
|
116
|
+
const n = max( samples, int( 1 ) ).toVar();
|
|
117
|
+
const acc = vec3( 0.0 ).toVar();
|
|
118
|
+
Loop( { start: int( 0 ), end: n, type: 'int', condition: '<' }, () => {
|
|
119
|
+
|
|
120
|
+
// per-component .toVar(): vec2(RandomValue, RandomValue) would collapse to u==v (TSL pitfall)
|
|
121
|
+
const u1 = RandomValue( rng ).toVar();
|
|
122
|
+
const u2 = RandomValue( rng ).toVar();
|
|
123
|
+
const jDir = sampleCone( axis, halfAngle, vec2( u1, u2 ) ).toVar();
|
|
124
|
+
acc.addAssign( sampleEnvironment( {
|
|
125
|
+
tex: envTexture,
|
|
126
|
+
samp: sampler( envTexture ),
|
|
127
|
+
direction: jDir,
|
|
128
|
+
environmentMatrix: envMatrix,
|
|
129
|
+
environmentIntensity,
|
|
130
|
+
enableEnvironmentLight: float( 1.0 ),
|
|
131
|
+
} ).xyz );
|
|
132
|
+
|
|
133
|
+
} );
|
|
134
|
+
return acc.div( float( n ) );
|
|
135
|
+
|
|
136
|
+
};
|
|
137
|
+
|
|
106
138
|
const computeFn = Fn( () => {
|
|
107
139
|
|
|
108
140
|
const threadIdx = instanceIndex;
|
|
@@ -125,13 +157,19 @@ export function buildShadeKernel( params ) {
|
|
|
125
157
|
|
|
126
158
|
} );
|
|
127
159
|
|
|
160
|
+
// Backdrop-view = the ray still travels the original camera direction (only alpha/transparent passthrough
|
|
161
|
+
// since the camera, REDIRECTED still clear). Captured at ARRIVAL (before the opaque/redirect bitOr below)
|
|
162
|
+
// so it stays valid for both the miss branch and the emissive-hit scale. This — not bounceIndex==0 — is
|
|
163
|
+
// the correct "is the env/emitter here a direct view" test, so env/emitters through alpha-cutout holes
|
|
164
|
+
// are treated like the open backdrop, not a GI bounce.
|
|
165
|
+
const isBackdropView = flags.bitAnd( uint( RAY_FLAG.REDIRECTED ) ).equal( uint( 0 ) ).toVar();
|
|
166
|
+
|
|
128
167
|
const origin = readRayOrigin( rayBufferRW, rayID ).toVar();
|
|
129
168
|
const direction = readRayDirection( rayBufferRW, rayID ).toVar();
|
|
130
169
|
const throughput = readRayThroughput( rayBufferRW, rayID ).toVar();
|
|
131
170
|
const currentRadiance = readRayRadiance( rayBufferRW, rayID ).toVar();
|
|
132
|
-
//
|
|
133
|
-
const
|
|
134
|
-
const pixelIndex = rayID.mod( maxRaysPerSample );
|
|
171
|
+
// One ray per pixel: rayID is the pixel index.
|
|
172
|
+
const pixelIndex = rayID;
|
|
135
173
|
const rngState = rngBufferRW.element( rayID ).toVar();
|
|
136
174
|
|
|
137
175
|
const hitDist = readHitDistance( hitBufferRO, rayID ).toVar();
|
|
@@ -146,7 +184,6 @@ export function buildShadeKernel( params ) {
|
|
|
146
184
|
// path length = loop iteration (advances every bounce incl. transmissive/SSS); drives RR/firefly/giScale/MIS. Megakernel: loop counter i.
|
|
147
185
|
const bounceIndex = int( currentBounce ).toVar();
|
|
148
186
|
const sssSteps = readSssSteps( rayBufferRW, rayID ).toVar();
|
|
149
|
-
const sampleIndex = int( rayID.div( maxRaysPerSample ) ).toVar();
|
|
150
187
|
|
|
151
188
|
// ── Analytic ground-plane shadow catcher (primary ray only, no geometry) ──
|
|
152
189
|
// A horizontal plane at y = groundCatcherHeight. For a bounce-0 ray that crosses it
|
|
@@ -219,26 +256,44 @@ export function buildShadeKernel( params ) {
|
|
|
219
256
|
const catcherEnvDir = groundProjectedEnvDir(
|
|
220
257
|
origin, direction, groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight, groundProjectionLevel,
|
|
221
258
|
).toVar();
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
259
|
+
// force-enable the sampler (pass 1.0): the visible backdrop is decoupled from env-lighting,
|
|
260
|
+
// so the catcher continues the HDRI even when the environment isn't used as a light.
|
|
261
|
+
// Only sample the env where it's actually shown as the catcher backdrop (showBackground); in
|
|
262
|
+
// color/transparent mode the catcher composites over backgroundColor / alpha, so the (up to
|
|
263
|
+
// N-tap) env work would be discarded. Blur matches the miss branch via the shared helper.
|
|
264
|
+
const envBehind = vec3( 0.0 ).toVar();
|
|
265
|
+
If( showBackground, () => {
|
|
266
|
+
|
|
267
|
+
If( backgroundBlurriness.greaterThan( 0.0 ), () => {
|
|
268
|
+
|
|
269
|
+
envBehind.assign( sampleEnvBlurred( catcherEnvDir, backgroundBlurriness.mul( 1.3 ), backgroundBlurSamples, rngState ) );
|
|
270
|
+
|
|
271
|
+
} ).Else( () => {
|
|
272
|
+
|
|
273
|
+
envBehind.assign( sampleEnvironment( {
|
|
274
|
+
tex: envTexture,
|
|
275
|
+
samp: sampler( envTexture ),
|
|
276
|
+
direction: catcherEnvDir,
|
|
277
|
+
environmentMatrix: envMatrix,
|
|
278
|
+
environmentIntensity,
|
|
279
|
+
enableEnvironmentLight: float( 1.0 ),
|
|
280
|
+
} ).xyz );
|
|
281
|
+
|
|
282
|
+
} );
|
|
283
|
+
|
|
284
|
+
} );
|
|
285
|
+
// Background mode: env image (showBackground) or the solid backgroundColor (color mode).
|
|
286
|
+
const bgColor = select( showBackground, envBehind.mul( backgroundIntensity ), backgroundColor );
|
|
231
287
|
const outRgb = select( transparentBackground, vec3( 0.0 ), bgColor.mul( ratio ) );
|
|
232
288
|
const outAlpha = select( transparentBackground, float( 1.0 ).sub( ratio ), float( 1.0 ) );
|
|
233
289
|
|
|
234
290
|
// The catcher is a real ground surface for the denoiser — write the plane's normal/depth
|
|
235
291
|
// + a neutral albedo (black albedo would break OIDN demodulation) and mark the pixel a
|
|
236
292
|
// valid surface, so OIDN/ASVGF don't smear the caught shadow as a background miss.
|
|
237
|
-
If(
|
|
293
|
+
If( auxOn, () => {
|
|
238
294
|
|
|
239
295
|
const planeDepth = computeNDCDepth( { worldPos: planePoint, cameraProjectionMatrix, cameraViewMatrix } );
|
|
240
296
|
writeGBuffer( gBufferRW, pixelIndex, planeN, planeDepth, vec3( 1.0 ) );
|
|
241
|
-
writeGBufferSurfaceID( gBufferRW, pixelIndex, uint( CATCHER_SURFACE_ID ), uint( CATCHER_SURFACE_ID ), 0.5, 0.5, uint( 1 ) );
|
|
242
297
|
|
|
243
298
|
} );
|
|
244
299
|
|
|
@@ -255,14 +310,26 @@ export function buildShadeKernel( params ) {
|
|
|
255
310
|
|
|
256
311
|
If( hitDist.greaterThan( MISS_DIST ), () => {
|
|
257
312
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
313
|
+
// Background and environment-lighting are decoupled (independent axes):
|
|
314
|
+
// • Visible backdrop: a PRIMARY ray draws the env image only when showBackground — regardless
|
|
315
|
+
// of enableEnvironmentLight (so you can show the HDRI without it lighting the scene).
|
|
316
|
+
// • Env as a light: SECONDARY bounces add the env (implicit MIS hit) only when enableEnvironmentLight.
|
|
317
|
+
// Backdrop-view = the ray still travels the original camera direction (only alpha/transparent
|
|
318
|
+
// passthrough since the camera). This — NOT bounceIndex==0 — is the correct test for "the env here
|
|
319
|
+
// is the direct backdrop", so env seen through alpha-cutout foliage holes is treated identically to
|
|
320
|
+
// the open sky (blur, intensity, show/hide, color-mode, ground projection all match).
|
|
321
|
+
// isBackdropView was captured at arrival (above) so it survives the REDIRECTED bitOr on opaque hits.
|
|
322
|
+
const wantBackdrop = isBackdropView.and( showBackground ); // draw env image as backdrop
|
|
323
|
+
const wantEnvLight = isBackdropView.not().and( enableEnvironmentLight ); // env as light on redirected bounces
|
|
324
|
+
|
|
325
|
+
If( wantBackdrop.or( wantEnvLight ), () => {
|
|
326
|
+
|
|
327
|
+
// Ground projection bends the backdrop-view env lookup onto a projected sphere+disk so the lower
|
|
328
|
+
// env hemisphere reads as a ground plane. Backdrop-view only (incl. through alpha-cutout holes,
|
|
329
|
+
// since they keep the camera direction); redirected bounces see the raw envmap as a light. The
|
|
330
|
+
// shared helper (also used by the shadow catcher) keeps the two in lockstep.
|
|
264
331
|
const envDir = direction.toVar();
|
|
265
|
-
If(
|
|
332
|
+
If( isBackdropView, () => {
|
|
266
333
|
|
|
267
334
|
envDir.assign( groundProjectedEnvDir(
|
|
268
335
|
origin, direction, groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight, groundProjectionLevel,
|
|
@@ -270,25 +337,32 @@ export function buildShadeKernel( params ) {
|
|
|
270
337
|
|
|
271
338
|
} );
|
|
272
339
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
340
|
+
// Backdrop-view rays blur the env (cone jitter, shared helper); redirected env-light bounces take
|
|
341
|
+
// the sharp Else. force-enable the sampler (pass 1.0): the wantBackdrop/wantEnvLight gate above
|
|
342
|
+
// already decided visibility, so the backdrop shows the HDRI even when env-lighting is off.
|
|
343
|
+
// Direction-space jitter keeps the blur free of equirect pole/seam artifacts; accumulation
|
|
344
|
+
// converges the noise. Opt-in — blurriness 0 takes the sharp Else (zero cost).
|
|
345
|
+
const envColor = vec3( 0.0 ).toVar();
|
|
346
|
+
If( isBackdropView.and( backgroundBlurriness.greaterThan( 0.0 ) ), () => {
|
|
347
|
+
|
|
348
|
+
envColor.assign( sampleEnvBlurred( envDir, backgroundBlurriness.mul( 1.3 ), backgroundBlurSamples, rngState ) );
|
|
281
349
|
|
|
282
|
-
|
|
283
|
-
If( bounceIndex.equal( 0 ).and( showBackground.not() ), () => {
|
|
350
|
+
} ).Else( () => {
|
|
284
351
|
|
|
285
|
-
envColor.assign(
|
|
352
|
+
envColor.assign( sampleEnvironment( {
|
|
353
|
+
tex: envTexture,
|
|
354
|
+
samp: sampler( envTexture ),
|
|
355
|
+
direction: envDir,
|
|
356
|
+
environmentMatrix: envMatrix,
|
|
357
|
+
environmentIntensity,
|
|
358
|
+
enableEnvironmentLight: float( 1.0 ),
|
|
359
|
+
} ).xyz );
|
|
286
360
|
|
|
287
361
|
} );
|
|
288
362
|
|
|
289
363
|
// MIS weight for implicit env hit — prevents double-counting with NEE
|
|
290
364
|
const envMisWeight = float( 1.0 ).toVar();
|
|
291
|
-
If(
|
|
365
|
+
If( isBackdropView.not().and( useEnvMapIS ), () => {
|
|
292
366
|
|
|
293
367
|
const prevBouncePdf = readRayPdf( rayBufferRW, rayID );
|
|
294
368
|
If( prevBouncePdf.greaterThan( 0.0 ), () => {
|
|
@@ -307,18 +381,23 @@ export function buildShadeKernel( params ) {
|
|
|
307
381
|
|
|
308
382
|
} );
|
|
309
383
|
|
|
310
|
-
const envGiScale = select(
|
|
311
|
-
const envScale = select(
|
|
384
|
+
const envGiScale = select( isBackdropView.not(), globalIlluminationIntensity, float( 1.0 ) );
|
|
385
|
+
const envScale = select( isBackdropView, backgroundIntensity, envMisWeight.mul( envGiScale ) );
|
|
312
386
|
|
|
313
387
|
// Firefly-suppress the env contribution (megakernel parity: PathTracerCore.js:780). Without
|
|
314
388
|
// this, indirect bounces escaping to a bright environment are unsuppressed spikes that OIDN
|
|
315
389
|
// smears into white blobs. The miss branch Return()s before the hit-branch clamp (~line 712),
|
|
316
390
|
// so it must be applied here.
|
|
391
|
+
// Firefly path length: a backdrop view (incl. through alpha-cutout holes) is a DIRECT view of the
|
|
392
|
+
// sky → use 0 (loosest clamp, same as the open-sky bounce-0 backdrop) so a bright HDRI sun doesn't
|
|
393
|
+
// read dimmer behind foliage cutouts than beside them. Only redirected GI bounces get the tighter
|
|
394
|
+
// path-length threshold.
|
|
395
|
+
const fireflyPathLen = select( isBackdropView, float( 0.0 ), float( bounceIndex ) );
|
|
317
396
|
currentRadiance.assign( vec4(
|
|
318
397
|
currentRadiance.xyz.add(
|
|
319
398
|
regularizePathContribution(
|
|
320
|
-
throughput.mul( envColor
|
|
321
|
-
|
|
399
|
+
throughput.mul( envColor ).mul( envScale ),
|
|
400
|
+
fireflyPathLen, fireflyThreshold, int( frame ),
|
|
322
401
|
),
|
|
323
402
|
),
|
|
324
403
|
currentRadiance.w
|
|
@@ -326,6 +405,18 @@ export function buildShadeKernel( params ) {
|
|
|
326
405
|
|
|
327
406
|
} );
|
|
328
407
|
|
|
408
|
+
// Solid-color backdrop ('color' mode): a primary ray that doesn't show the env image and isn't
|
|
409
|
+
// transparent fills with backgroundColor (default black). Tinted by throughput so it reads
|
|
410
|
+
// correctly behind colored glass, matching the env-backdrop path.
|
|
411
|
+
If( isBackdropView.and( showBackground.not() ).and( transparentBackground.not() ), () => {
|
|
412
|
+
|
|
413
|
+
currentRadiance.assign( vec4(
|
|
414
|
+
currentRadiance.xyz.add( throughput.mul( backgroundColor ) ),
|
|
415
|
+
currentRadiance.w
|
|
416
|
+
) );
|
|
417
|
+
|
|
418
|
+
} );
|
|
419
|
+
|
|
329
420
|
// Transparent-bg alpha: see-through only if the ray escaped WITHOUT ever hitting opaque
|
|
330
421
|
// geometry (megakernel parity: PathTracerCore.js:784). A secondary bounce off an opaque
|
|
331
422
|
// surface that escapes to env keeps alpha 1 (HAS_HIT_OPAQUE set), so glass-in-front-of-an-
|
|
@@ -402,6 +493,8 @@ export function buildShadeKernel( params ) {
|
|
|
402
493
|
throughput.divAssign( rrP );
|
|
403
494
|
|
|
404
495
|
// free-bounce continuation: ray stays in the same medium, so medium stack + coeffs persist
|
|
496
|
+
// SSS scatter changes direction → no longer the direct backdrop view.
|
|
497
|
+
flags.assign( flags.bitOr( uint( RAY_FLAG.REDIRECTED ) ) );
|
|
405
498
|
writeRayOriginMeta( rayBufferRW, rayID, scatterPoint, cameraDepth, sssSteps );
|
|
406
499
|
writeRayDirFlags( rayBufferRW, rayID, newDir, flags );
|
|
407
500
|
// Free bounce: preserve prevBouncePdf (megakernel leaves it untouched across SSS scatter,
|
|
@@ -477,26 +570,19 @@ export function buildShadeKernel( params ) {
|
|
|
477
570
|
} );
|
|
478
571
|
|
|
479
572
|
// first-hit MRT data (bounce 0 only)
|
|
480
|
-
If( bounceIndex.equal( 0 ), () => {
|
|
573
|
+
If( bounceIndex.equal( 0 ).and( auxOn ), () => {
|
|
481
574
|
|
|
482
575
|
const linearDepth = computeNDCDepth( {
|
|
483
576
|
worldPos: hitPoint,
|
|
484
577
|
cameraProjectionMatrix,
|
|
485
578
|
cameraViewMatrix,
|
|
486
579
|
} );
|
|
487
|
-
// G-buffer is per-pixel
|
|
580
|
+
// G-buffer is per-pixel (rayID == pixelIndex). writeGBuffer half-packs (normal/depth/albedo).
|
|
488
581
|
// Write the primary DEPTH now with the miss-default aux; the real normal/albedo are captured below
|
|
489
582
|
// (aux-extend) and may extend through specular surfaces (gap #9). Glass rays Return at the transparency
|
|
490
583
|
// block before that capture, so a glass-then-escape pixel keeps this default aux — megakernel parity
|
|
491
584
|
// (objectNormal/objectColor stay at their init for transmissive-then-miss).
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
writeGBuffer( gBufferRW, pixelIndex, vec3( 0.0, 0.0, 1.0 ), linearDepth, vec3( 0.0 ) );
|
|
495
|
-
// Persist the primary-hit surface ID (Tier-1 A-SVGF correlated re-projection). Hit-only
|
|
496
|
-
// branch (misses Return above), so this marks the pixel valid; bary from the bounce-0 hit.
|
|
497
|
-
writeGBufferSurfaceID( gBufferRW, pixelIndex, hitTriIdx, readHitMeshIndex( hitBufferRO, rayID ), hitUV.x, hitUV.y, uint( 1 ) );
|
|
498
|
-
|
|
499
|
-
} );
|
|
585
|
+
writeGBuffer( gBufferRW, pixelIndex, vec3( 0.0, 0.0, 1.0 ), linearDepth, vec3( 0.0 ) );
|
|
500
586
|
|
|
501
587
|
} );
|
|
502
588
|
|
|
@@ -640,6 +726,14 @@ export function buildShadeKernel( params ) {
|
|
|
640
726
|
|
|
641
727
|
// SSS = free bounce (depth unchanged); transmission advances camera-bounce depth.
|
|
642
728
|
// Transmissive / alpha-skip / SSS-boundary are all FREE bounces — they do NOT advance camera depth (megakernel parity, gap #4). cameraDepth advances only on opaque scatter (below).
|
|
729
|
+
// Backdrop-view survives a pure alpha/transparent passthrough (direction unchanged) but is cleared by
|
|
730
|
+
// any redirection (refraction/reflection/SSS boundary), so env through a leaf hole stays the blurred
|
|
731
|
+
// backdrop while env through glass becomes sharp redirected light.
|
|
732
|
+
If( interaction.isAlphaSkip.not(), () => {
|
|
733
|
+
|
|
734
|
+
flags.assign( flags.bitOr( uint( RAY_FLAG.REDIRECTED ) ) );
|
|
735
|
+
|
|
736
|
+
} );
|
|
643
737
|
writeRayOriginMeta( rayBufferRW, rayID, newOrigin, cameraDepth, sssSteps );
|
|
644
738
|
writeRayDirFlags( rayBufferRW, rayID, interaction.direction, flags );
|
|
645
739
|
// Free bounce: preserve prevBouncePdf (megakernel keeps the last opaque-scatter pdf across
|
|
@@ -657,12 +751,17 @@ export function buildShadeKernel( params ) {
|
|
|
657
751
|
// PathTracerCore.js:1042). Flag the chain so a later env-escape keeps alpha 1 (the gate in the
|
|
658
752
|
// miss branch). Alpha itself already defaults to 1 from Generate in transparent-bg mode, so there
|
|
659
753
|
// is nothing to set here — a ray dying inside geometry (SSS walk) stays solid without reaching this.
|
|
660
|
-
|
|
754
|
+
// Hit opaque geometry: set HAS_HIT_OPAQUE and mark REDIRECTED (this is a real surface scatter,
|
|
755
|
+
// so any later env-escape is redirected light, not the direct backdrop). Single positive bitOr.
|
|
756
|
+
flags.assign( flags.bitOr( uint( RAY_FLAG.HAS_HIT_OPAQUE | RAY_FLAG.REDIRECTED ) ) );
|
|
661
757
|
|
|
662
758
|
const emissive = matSamples.emissive.toVar();
|
|
663
759
|
If( length( emissive ).greaterThan( 0.0 ), () => {
|
|
664
760
|
|
|
665
|
-
|
|
761
|
+
// Key on backdrop-view (not bounceIndex>0) so an emitter seen DIRECTLY through an alpha-cutout hole
|
|
762
|
+
// renders at full intensity (1.0) like a direct view, consistent with env-through-hole — instead of
|
|
763
|
+
// being GI-scaled as if it were an indirect bounce. (MIS below already self-guards via prevBouncePdf.)
|
|
764
|
+
const emissiveGiScale = select( isBackdropView.not(), globalIlluminationIntensity, float( 1.0 ) );
|
|
666
765
|
|
|
667
766
|
// MIS weight vs emissive-triangle NEE (megakernel parity: PathTracerCore.js:1117). On a secondary
|
|
668
767
|
// hit (bounceIndex>0) the prior bounce's NEE also sampled this emitter — power-heuristic balances the
|
|
@@ -734,7 +833,7 @@ export function buildShadeKernel( params ) {
|
|
|
734
833
|
// visible (the surface reflected in a mirror / seen behind glass), not the specular surface. Glass
|
|
735
834
|
// Returns at the transparency block above, so its aux is replaced by the surface behind it. Depth
|
|
736
835
|
// stays at the primary hit (read back + re-packed; the snorm depth re-pack is idempotent — no drift).
|
|
737
|
-
If(
|
|
836
|
+
If( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ).and( auxOn ), () => {
|
|
738
837
|
|
|
739
838
|
const primaryDepth = gbDecodeNormalDepth( readGBuffer( gBufferRW, pixelIndex ) ).w;
|
|
740
839
|
writeGBuffer( gBufferRW, pixelIndex, N, primaryDepth, albedo.xyz );
|
|
@@ -754,13 +853,13 @@ export function buildShadeKernel( params ) {
|
|
|
754
853
|
material.clearcoat, material.emissive, material.subsurface,
|
|
755
854
|
) ).toVar();
|
|
756
855
|
|
|
757
|
-
// STBN keyed on (pixel, bounceIndex, frame)
|
|
856
|
+
// STBN keyed on (pixel, bounceIndex, frame).
|
|
758
857
|
const _resX = int( resolution.x ).toVar();
|
|
759
858
|
const _pixelCoord = vec2(
|
|
760
859
|
float( int( pixelIndex ).mod( _resX ) ).add( 0.5 ),
|
|
761
860
|
float( int( pixelIndex ).div( _resX ) ).add( 0.5 ),
|
|
762
861
|
);
|
|
763
|
-
const xi = getRandomSample( _pixelCoord,
|
|
862
|
+
const xi = getRandomSample( _pixelCoord, int( 0 ), bounceIndex, rngState, int( - 1 ), resolution, frame ).toVar();
|
|
764
863
|
const emptyWeights = BRDFWeights( {
|
|
765
864
|
specular: float( 0.0 ), diffuse: float( 0.0 ), sheen: float( 0.0 ),
|
|
766
865
|
clearcoat: float( 0.0 ), transmission: float( 0.0 ), iridescence: float( 0.0 ),
|
|
@@ -365,6 +365,21 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
365
365
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
// PathTracer's aux MRT (normalDepth + albedo) is consumed by the real-time denoisers (ASVGF/
|
|
369
|
+
// BilateralFilter read albedo; ASVGF/SSRC/EdgeFilter read normalDepth) and by OIDN (reads the
|
|
370
|
+
// MRT read-targets directly). When none are active the wavefront skips those writes entirely.
|
|
371
|
+
s.pathTracer?.setAuxGBufferEnabled?.( normalNeeded || !! this.denoiser?.enabled );
|
|
372
|
+
|
|
373
|
+
// Reclaim VRAM: free the big 2048² StorageTextures of any denoiser/G-buffer stage that ended up
|
|
374
|
+
// disabled (lazily re-created on the next dispatch after re-enable). Every strategy/denoiser
|
|
375
|
+
// toggle funnels through here after the enabled flags above are settled, so this is the one
|
|
376
|
+
// choke point. dispose() is idempotent, so re-running it for an already-released stage is a no-op.
|
|
377
|
+
for ( const stage of [ s.asvgf, s.variance, s.bilateralFilter, s.ssrc, s.edgeFilter, nd, mv ] ) {
|
|
378
|
+
|
|
379
|
+
if ( stage && ! stage.enabled ) stage.releaseGPUMemory?.();
|
|
380
|
+
|
|
381
|
+
}
|
|
382
|
+
|
|
368
383
|
}
|
|
369
384
|
|
|
370
385
|
// ── Render Completion Chain ───────────────────────────────────
|
|
@@ -595,6 +610,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
595
610
|
setOIDNEnabled( enabled ) {
|
|
596
611
|
|
|
597
612
|
if ( this.denoiser ) this.denoiser.enabled = enabled;
|
|
613
|
+
// OIDN reads the PathTracer aux MRT; re-sync so the wavefront produces it while OIDN is on.
|
|
614
|
+
this._syncGBufferStages();
|
|
598
615
|
|
|
599
616
|
}
|
|
600
617
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { uniform, uniformArray } from 'three/tsl';
|
|
9
|
-
import { Vector2, Matrix4, Vector3 } from 'three';
|
|
9
|
+
import { Vector2, Matrix4, Vector3, Color } from 'three';
|
|
10
10
|
import { samplingTechniqueUniform } from '../TSL/Random.js';
|
|
11
11
|
import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
|
|
12
12
|
|
|
@@ -161,7 +161,6 @@ export class UniformManager {
|
|
|
161
161
|
// Frame and sampling
|
|
162
162
|
u( 'frame', 0, 'uint' );
|
|
163
163
|
u( 'maxBounces', DEFAULT_STATE.bounces, 'int' );
|
|
164
|
-
u( 'samplesPerPixel', DEFAULT_STATE.samplesPerPixel, 'int' );
|
|
165
164
|
u( 'maxSamples', DEFAULT_STATE.maxSamples, 'int' );
|
|
166
165
|
u( 'transmissiveBounces', DEFAULT_STATE.transmissiveBounces, 'int' );
|
|
167
166
|
u( 'maxSubsurfaceSteps', DEFAULT_STATE.maxSubsurfaceSteps, 'int' );
|
|
@@ -177,6 +176,9 @@ export class UniformManager {
|
|
|
177
176
|
// Environment
|
|
178
177
|
u( 'environmentIntensity', DEFAULT_STATE.environmentIntensity, 'float' );
|
|
179
178
|
u( 'backgroundIntensity', DEFAULT_STATE.backgroundIntensity, 'float' );
|
|
179
|
+
u( 'backgroundColor', new Color( 0, 0, 0 ), 'color' ); // linear; solid backdrop in 'color' mode
|
|
180
|
+
u( 'backgroundBlurriness', DEFAULT_STATE.backgroundBlurriness, 'float' );
|
|
181
|
+
u( 'backgroundBlurSamples', DEFAULT_STATE.backgroundBlurSamples, 'int' );
|
|
180
182
|
ub( 'showBackground', DEFAULT_STATE.showBackground );
|
|
181
183
|
ub( 'transparentBackground', DEFAULT_STATE.transparentBackground );
|
|
182
184
|
ub( 'enableEnvironment', DEFAULT_STATE.enableEnvironment );
|
|
@@ -277,7 +277,6 @@ export class VideoRenderManager {
|
|
|
277
277
|
return {
|
|
278
278
|
maxSamples: app.settings.get( 'maxSamples' ),
|
|
279
279
|
maxBounces: app.settings.get( 'maxBounces' ),
|
|
280
|
-
samplesPerPixel: app.settings.get( 'samplesPerPixel' ),
|
|
281
280
|
transmissiveBounces: app.settings.get( 'transmissiveBounces' ),
|
|
282
281
|
renderMode: app.stages.pathTracer?.renderMode?.value,
|
|
283
282
|
controlsEnabled: app.cameraManager.controls?.enabled,
|
|
@@ -300,7 +299,6 @@ export class VideoRenderManager {
|
|
|
300
299
|
app.settings.setMany( {
|
|
301
300
|
maxSamples: state.maxSamples,
|
|
302
301
|
maxBounces: state.maxBounces,
|
|
303
|
-
samplesPerPixel: state.samplesPerPixel,
|
|
304
302
|
transmissiveBounces: state.transmissiveBounces,
|
|
305
303
|
}, { silent: true } );
|
|
306
304
|
|