rayzee 7.3.0 → 7.4.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 +2 -3
- package/dist/rayzee.es.js +1234 -1209
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +58 -44
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/EngineDefaults.js +9 -7
- 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 +3 -4
- package/src/Processor/QueueManager.js +1 -0
- package/src/RenderSettings.js +13 -2
- package/src/Stages/PathTracer.js +31 -52
- package/src/Stages/PathTracerStage.js +0 -13
- package/src/TSL/DebugKernel.js +2 -3
- package/src/TSL/FinalWriteKernel.js +28 -29
- package/src/TSL/GenerateKernel.js +11 -16
- package/src/TSL/ShadeKernel.js +161 -55
- package/src/managers/DenoisingManager.js +7 -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';
|
|
@@ -91,7 +91,7 @@ export function buildShadeKernel( params ) {
|
|
|
91
91
|
spotLightsBuffer, numSpotLights,
|
|
92
92
|
maxBounceCount, maxSubsurfaceSteps,
|
|
93
93
|
currentBounce, // loop iteration = path length (advances on free bounces); drives RR/firefly/giScale
|
|
94
|
-
transparentBackground, backgroundIntensity, showBackground,
|
|
94
|
+
transparentBackground, backgroundIntensity, backgroundColor, backgroundBlurriness, backgroundBlurSamples, showBackground,
|
|
95
95
|
globalIlluminationIntensity,
|
|
96
96
|
cameraProjectionMatrix, cameraViewMatrix,
|
|
97
97
|
fireflyThreshold, frame, resolution,
|
|
@@ -99,10 +99,45 @@ export function buildShadeKernel( params ) {
|
|
|
99
99
|
emissiveBoost, totalTriangleCount, enableEmissiveTriangleSampling,
|
|
100
100
|
lightBVHNodeCount, reverseMapVec4Offset,
|
|
101
101
|
maxRayCount,
|
|
102
|
+
// Aux G-buffer (normal/depth/albedo + surface ID) feeds only the denoiser/OIDN MRT. Gated by a
|
|
103
|
+
// live uniform (1 = denoiser on) so the wavefront skips these writes when nothing consumes them.
|
|
104
|
+
auxGBufferEnabled,
|
|
102
105
|
} = params;
|
|
103
106
|
|
|
107
|
+
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
108
|
+
|
|
104
109
|
const useEmissiveNEE = lightBuffer !== undefined;
|
|
105
110
|
|
|
111
|
+
// Stochastic cone-jitter blur of an env backdrop lookup. Plain JS inliner (NOT a Fn — an rng Fn-param
|
|
112
|
+
// would freeze; see TSL pitfalls) so it mutates the caller's rngState .toVar() directly. Shared by the
|
|
113
|
+
// miss branch and the shadow catcher so their blur stays in lockstep (no horizon seam). normalize() the
|
|
114
|
+
// center direction — ground projection can return a non-unit vector, which would skew sampleCone's basis.
|
|
115
|
+
// Clamp the tap count to ≥1 so a 0 forced via the engine API can't produce a 0/0 NaN backdrop.
|
|
116
|
+
const sampleEnvBlurred = ( centerDir, halfAngle, samples, rng ) => {
|
|
117
|
+
|
|
118
|
+
const axis = normalize( centerDir ).toVar();
|
|
119
|
+
const n = max( samples, int( 1 ) ).toVar();
|
|
120
|
+
const acc = vec3( 0.0 ).toVar();
|
|
121
|
+
Loop( { start: int( 0 ), end: n, type: 'int', condition: '<' }, () => {
|
|
122
|
+
|
|
123
|
+
// per-component .toVar(): vec2(RandomValue, RandomValue) would collapse to u==v (TSL pitfall)
|
|
124
|
+
const u1 = RandomValue( rng ).toVar();
|
|
125
|
+
const u2 = RandomValue( rng ).toVar();
|
|
126
|
+
const jDir = sampleCone( axis, halfAngle, vec2( u1, u2 ) ).toVar();
|
|
127
|
+
acc.addAssign( sampleEnvironment( {
|
|
128
|
+
tex: envTexture,
|
|
129
|
+
samp: sampler( envTexture ),
|
|
130
|
+
direction: jDir,
|
|
131
|
+
environmentMatrix: envMatrix,
|
|
132
|
+
environmentIntensity,
|
|
133
|
+
enableEnvironmentLight: float( 1.0 ),
|
|
134
|
+
} ).xyz );
|
|
135
|
+
|
|
136
|
+
} );
|
|
137
|
+
return acc.div( float( n ) );
|
|
138
|
+
|
|
139
|
+
};
|
|
140
|
+
|
|
106
141
|
const computeFn = Fn( () => {
|
|
107
142
|
|
|
108
143
|
const threadIdx = instanceIndex;
|
|
@@ -125,13 +160,19 @@ export function buildShadeKernel( params ) {
|
|
|
125
160
|
|
|
126
161
|
} );
|
|
127
162
|
|
|
163
|
+
// Backdrop-view = the ray still travels the original camera direction (only alpha/transparent passthrough
|
|
164
|
+
// since the camera, REDIRECTED still clear). Captured at ARRIVAL (before the opaque/redirect bitOr below)
|
|
165
|
+
// so it stays valid for both the miss branch and the emissive-hit scale. This — not bounceIndex==0 — is
|
|
166
|
+
// the correct "is the env/emitter here a direct view" test, so env/emitters through alpha-cutout holes
|
|
167
|
+
// are treated like the open backdrop, not a GI bounce.
|
|
168
|
+
const isBackdropView = flags.bitAnd( uint( RAY_FLAG.REDIRECTED ) ).equal( uint( 0 ) ).toVar();
|
|
169
|
+
|
|
128
170
|
const origin = readRayOrigin( rayBufferRW, rayID ).toVar();
|
|
129
171
|
const direction = readRayDirection( rayBufferRW, rayID ).toVar();
|
|
130
172
|
const throughput = readRayThroughput( rayBufferRW, rayID ).toVar();
|
|
131
173
|
const currentRadiance = readRayRadiance( rayBufferRW, rayID ).toVar();
|
|
132
|
-
//
|
|
133
|
-
const
|
|
134
|
-
const pixelIndex = rayID.mod( maxRaysPerSample );
|
|
174
|
+
// One ray per pixel: rayID is the pixel index.
|
|
175
|
+
const pixelIndex = rayID;
|
|
135
176
|
const rngState = rngBufferRW.element( rayID ).toVar();
|
|
136
177
|
|
|
137
178
|
const hitDist = readHitDistance( hitBufferRO, rayID ).toVar();
|
|
@@ -146,7 +187,6 @@ export function buildShadeKernel( params ) {
|
|
|
146
187
|
// path length = loop iteration (advances every bounce incl. transmissive/SSS); drives RR/firefly/giScale/MIS. Megakernel: loop counter i.
|
|
147
188
|
const bounceIndex = int( currentBounce ).toVar();
|
|
148
189
|
const sssSteps = readSssSteps( rayBufferRW, rayID ).toVar();
|
|
149
|
-
const sampleIndex = int( rayID.div( maxRaysPerSample ) ).toVar();
|
|
150
190
|
|
|
151
191
|
// ── Analytic ground-plane shadow catcher (primary ray only, no geometry) ──
|
|
152
192
|
// A horizontal plane at y = groundCatcherHeight. For a bounce-0 ray that crosses it
|
|
@@ -219,22 +259,41 @@ export function buildShadeKernel( params ) {
|
|
|
219
259
|
const catcherEnvDir = groundProjectedEnvDir(
|
|
220
260
|
origin, direction, groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight, groundProjectionLevel,
|
|
221
261
|
).toVar();
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
262
|
+
// force-enable the sampler (pass 1.0): the visible backdrop is decoupled from env-lighting,
|
|
263
|
+
// so the catcher continues the HDRI even when the environment isn't used as a light.
|
|
264
|
+
// Only sample the env where it's actually shown as the catcher backdrop (showBackground); in
|
|
265
|
+
// color/transparent mode the catcher composites over backgroundColor / alpha, so the (up to
|
|
266
|
+
// N-tap) env work would be discarded. Blur matches the miss branch via the shared helper.
|
|
267
|
+
const envBehind = vec3( 0.0 ).toVar();
|
|
268
|
+
If( showBackground, () => {
|
|
269
|
+
|
|
270
|
+
If( backgroundBlurriness.greaterThan( 0.0 ), () => {
|
|
271
|
+
|
|
272
|
+
envBehind.assign( sampleEnvBlurred( catcherEnvDir, backgroundBlurriness.mul( 1.3 ), backgroundBlurSamples, rngState ) );
|
|
273
|
+
|
|
274
|
+
} ).Else( () => {
|
|
275
|
+
|
|
276
|
+
envBehind.assign( sampleEnvironment( {
|
|
277
|
+
tex: envTexture,
|
|
278
|
+
samp: sampler( envTexture ),
|
|
279
|
+
direction: catcherEnvDir,
|
|
280
|
+
environmentMatrix: envMatrix,
|
|
281
|
+
environmentIntensity,
|
|
282
|
+
enableEnvironmentLight: float( 1.0 ),
|
|
283
|
+
} ).xyz );
|
|
284
|
+
|
|
285
|
+
} );
|
|
286
|
+
|
|
287
|
+
} );
|
|
288
|
+
// Background mode: env image (showBackground) or the solid backgroundColor (color mode).
|
|
289
|
+
const bgColor = select( showBackground, envBehind.mul( backgroundIntensity ), backgroundColor );
|
|
231
290
|
const outRgb = select( transparentBackground, vec3( 0.0 ), bgColor.mul( ratio ) );
|
|
232
291
|
const outAlpha = select( transparentBackground, float( 1.0 ).sub( ratio ), float( 1.0 ) );
|
|
233
292
|
|
|
234
293
|
// The catcher is a real ground surface for the denoiser — write the plane's normal/depth
|
|
235
294
|
// + a neutral albedo (black albedo would break OIDN demodulation) and mark the pixel a
|
|
236
295
|
// valid surface, so OIDN/ASVGF don't smear the caught shadow as a background miss.
|
|
237
|
-
If(
|
|
296
|
+
If( auxOn, () => {
|
|
238
297
|
|
|
239
298
|
const planeDepth = computeNDCDepth( { worldPos: planePoint, cameraProjectionMatrix, cameraViewMatrix } );
|
|
240
299
|
writeGBuffer( gBufferRW, pixelIndex, planeN, planeDepth, vec3( 1.0 ) );
|
|
@@ -255,14 +314,26 @@ export function buildShadeKernel( params ) {
|
|
|
255
314
|
|
|
256
315
|
If( hitDist.greaterThan( MISS_DIST ), () => {
|
|
257
316
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
317
|
+
// Background and environment-lighting are decoupled (independent axes):
|
|
318
|
+
// • Visible backdrop: a PRIMARY ray draws the env image only when showBackground — regardless
|
|
319
|
+
// of enableEnvironmentLight (so you can show the HDRI without it lighting the scene).
|
|
320
|
+
// • Env as a light: SECONDARY bounces add the env (implicit MIS hit) only when enableEnvironmentLight.
|
|
321
|
+
// Backdrop-view = the ray still travels the original camera direction (only alpha/transparent
|
|
322
|
+
// passthrough since the camera). This — NOT bounceIndex==0 — is the correct test for "the env here
|
|
323
|
+
// is the direct backdrop", so env seen through alpha-cutout foliage holes is treated identically to
|
|
324
|
+
// the open sky (blur, intensity, show/hide, color-mode, ground projection all match).
|
|
325
|
+
// isBackdropView was captured at arrival (above) so it survives the REDIRECTED bitOr on opaque hits.
|
|
326
|
+
const wantBackdrop = isBackdropView.and( showBackground ); // draw env image as backdrop
|
|
327
|
+
const wantEnvLight = isBackdropView.not().and( enableEnvironmentLight ); // env as light on redirected bounces
|
|
328
|
+
|
|
329
|
+
If( wantBackdrop.or( wantEnvLight ), () => {
|
|
330
|
+
|
|
331
|
+
// Ground projection bends the backdrop-view env lookup onto a projected sphere+disk so the lower
|
|
332
|
+
// env hemisphere reads as a ground plane. Backdrop-view only (incl. through alpha-cutout holes,
|
|
333
|
+
// since they keep the camera direction); redirected bounces see the raw envmap as a light. The
|
|
334
|
+
// shared helper (also used by the shadow catcher) keeps the two in lockstep.
|
|
264
335
|
const envDir = direction.toVar();
|
|
265
|
-
If(
|
|
336
|
+
If( isBackdropView, () => {
|
|
266
337
|
|
|
267
338
|
envDir.assign( groundProjectedEnvDir(
|
|
268
339
|
origin, direction, groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight, groundProjectionLevel,
|
|
@@ -270,25 +341,32 @@ export function buildShadeKernel( params ) {
|
|
|
270
341
|
|
|
271
342
|
} );
|
|
272
343
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
344
|
+
// Backdrop-view rays blur the env (cone jitter, shared helper); redirected env-light bounces take
|
|
345
|
+
// the sharp Else. force-enable the sampler (pass 1.0): the wantBackdrop/wantEnvLight gate above
|
|
346
|
+
// already decided visibility, so the backdrop shows the HDRI even when env-lighting is off.
|
|
347
|
+
// Direction-space jitter keeps the blur free of equirect pole/seam artifacts; accumulation
|
|
348
|
+
// converges the noise. Opt-in — blurriness 0 takes the sharp Else (zero cost).
|
|
349
|
+
const envColor = vec3( 0.0 ).toVar();
|
|
350
|
+
If( isBackdropView.and( backgroundBlurriness.greaterThan( 0.0 ) ), () => {
|
|
351
|
+
|
|
352
|
+
envColor.assign( sampleEnvBlurred( envDir, backgroundBlurriness.mul( 1.3 ), backgroundBlurSamples, rngState ) );
|
|
281
353
|
|
|
282
|
-
|
|
283
|
-
If( bounceIndex.equal( 0 ).and( showBackground.not() ), () => {
|
|
354
|
+
} ).Else( () => {
|
|
284
355
|
|
|
285
|
-
envColor.assign(
|
|
356
|
+
envColor.assign( sampleEnvironment( {
|
|
357
|
+
tex: envTexture,
|
|
358
|
+
samp: sampler( envTexture ),
|
|
359
|
+
direction: envDir,
|
|
360
|
+
environmentMatrix: envMatrix,
|
|
361
|
+
environmentIntensity,
|
|
362
|
+
enableEnvironmentLight: float( 1.0 ),
|
|
363
|
+
} ).xyz );
|
|
286
364
|
|
|
287
365
|
} );
|
|
288
366
|
|
|
289
367
|
// MIS weight for implicit env hit — prevents double-counting with NEE
|
|
290
368
|
const envMisWeight = float( 1.0 ).toVar();
|
|
291
|
-
If(
|
|
369
|
+
If( isBackdropView.not().and( useEnvMapIS ), () => {
|
|
292
370
|
|
|
293
371
|
const prevBouncePdf = readRayPdf( rayBufferRW, rayID );
|
|
294
372
|
If( prevBouncePdf.greaterThan( 0.0 ), () => {
|
|
@@ -307,18 +385,23 @@ export function buildShadeKernel( params ) {
|
|
|
307
385
|
|
|
308
386
|
} );
|
|
309
387
|
|
|
310
|
-
const envGiScale = select(
|
|
311
|
-
const envScale = select(
|
|
388
|
+
const envGiScale = select( isBackdropView.not(), globalIlluminationIntensity, float( 1.0 ) );
|
|
389
|
+
const envScale = select( isBackdropView, backgroundIntensity, envMisWeight.mul( envGiScale ) );
|
|
312
390
|
|
|
313
391
|
// Firefly-suppress the env contribution (megakernel parity: PathTracerCore.js:780). Without
|
|
314
392
|
// this, indirect bounces escaping to a bright environment are unsuppressed spikes that OIDN
|
|
315
393
|
// smears into white blobs. The miss branch Return()s before the hit-branch clamp (~line 712),
|
|
316
394
|
// so it must be applied here.
|
|
395
|
+
// Firefly path length: a backdrop view (incl. through alpha-cutout holes) is a DIRECT view of the
|
|
396
|
+
// sky → use 0 (loosest clamp, same as the open-sky bounce-0 backdrop) so a bright HDRI sun doesn't
|
|
397
|
+
// read dimmer behind foliage cutouts than beside them. Only redirected GI bounces get the tighter
|
|
398
|
+
// path-length threshold.
|
|
399
|
+
const fireflyPathLen = select( isBackdropView, float( 0.0 ), float( bounceIndex ) );
|
|
317
400
|
currentRadiance.assign( vec4(
|
|
318
401
|
currentRadiance.xyz.add(
|
|
319
402
|
regularizePathContribution(
|
|
320
|
-
throughput.mul( envColor
|
|
321
|
-
|
|
403
|
+
throughput.mul( envColor ).mul( envScale ),
|
|
404
|
+
fireflyPathLen, fireflyThreshold, int( frame ),
|
|
322
405
|
),
|
|
323
406
|
),
|
|
324
407
|
currentRadiance.w
|
|
@@ -326,6 +409,18 @@ export function buildShadeKernel( params ) {
|
|
|
326
409
|
|
|
327
410
|
} );
|
|
328
411
|
|
|
412
|
+
// Solid-color backdrop ('color' mode): a primary ray that doesn't show the env image and isn't
|
|
413
|
+
// transparent fills with backgroundColor (default black). Tinted by throughput so it reads
|
|
414
|
+
// correctly behind colored glass, matching the env-backdrop path.
|
|
415
|
+
If( isBackdropView.and( showBackground.not() ).and( transparentBackground.not() ), () => {
|
|
416
|
+
|
|
417
|
+
currentRadiance.assign( vec4(
|
|
418
|
+
currentRadiance.xyz.add( throughput.mul( backgroundColor ) ),
|
|
419
|
+
currentRadiance.w
|
|
420
|
+
) );
|
|
421
|
+
|
|
422
|
+
} );
|
|
423
|
+
|
|
329
424
|
// Transparent-bg alpha: see-through only if the ray escaped WITHOUT ever hitting opaque
|
|
330
425
|
// geometry (megakernel parity: PathTracerCore.js:784). A secondary bounce off an opaque
|
|
331
426
|
// surface that escapes to env keeps alpha 1 (HAS_HIT_OPAQUE set), so glass-in-front-of-an-
|
|
@@ -402,6 +497,8 @@ export function buildShadeKernel( params ) {
|
|
|
402
497
|
throughput.divAssign( rrP );
|
|
403
498
|
|
|
404
499
|
// free-bounce continuation: ray stays in the same medium, so medium stack + coeffs persist
|
|
500
|
+
// SSS scatter changes direction → no longer the direct backdrop view.
|
|
501
|
+
flags.assign( flags.bitOr( uint( RAY_FLAG.REDIRECTED ) ) );
|
|
405
502
|
writeRayOriginMeta( rayBufferRW, rayID, scatterPoint, cameraDepth, sssSteps );
|
|
406
503
|
writeRayDirFlags( rayBufferRW, rayID, newDir, flags );
|
|
407
504
|
// Free bounce: preserve prevBouncePdf (megakernel leaves it untouched across SSS scatter,
|
|
@@ -477,26 +574,22 @@ export function buildShadeKernel( params ) {
|
|
|
477
574
|
} );
|
|
478
575
|
|
|
479
576
|
// first-hit MRT data (bounce 0 only)
|
|
480
|
-
If( bounceIndex.equal( 0 ), () => {
|
|
577
|
+
If( bounceIndex.equal( 0 ).and( auxOn ), () => {
|
|
481
578
|
|
|
482
579
|
const linearDepth = computeNDCDepth( {
|
|
483
580
|
worldPos: hitPoint,
|
|
484
581
|
cameraProjectionMatrix,
|
|
485
582
|
cameraViewMatrix,
|
|
486
583
|
} );
|
|
487
|
-
// G-buffer is per-pixel
|
|
584
|
+
// G-buffer is per-pixel (rayID == pixelIndex). writeGBuffer half-packs (normal/depth/albedo).
|
|
488
585
|
// Write the primary DEPTH now with the miss-default aux; the real normal/albedo are captured below
|
|
489
586
|
// (aux-extend) and may extend through specular surfaces (gap #9). Glass rays Return at the transparency
|
|
490
587
|
// block before that capture, so a glass-then-escape pixel keeps this default aux — megakernel parity
|
|
491
588
|
// (objectNormal/objectColor stay at their init for transmissive-then-miss).
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
} );
|
|
589
|
+
writeGBuffer( gBufferRW, pixelIndex, vec3( 0.0, 0.0, 1.0 ), linearDepth, vec3( 0.0 ) );
|
|
590
|
+
// Persist the primary-hit surface ID (Tier-1 A-SVGF correlated re-projection). Hit-only
|
|
591
|
+
// branch (misses Return above), so this marks the pixel valid; bary from the bounce-0 hit.
|
|
592
|
+
writeGBufferSurfaceID( gBufferRW, pixelIndex, hitTriIdx, readHitMeshIndex( hitBufferRO, rayID ), hitUV.x, hitUV.y, uint( 1 ) );
|
|
500
593
|
|
|
501
594
|
} );
|
|
502
595
|
|
|
@@ -640,6 +733,14 @@ export function buildShadeKernel( params ) {
|
|
|
640
733
|
|
|
641
734
|
// SSS = free bounce (depth unchanged); transmission advances camera-bounce depth.
|
|
642
735
|
// 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).
|
|
736
|
+
// Backdrop-view survives a pure alpha/transparent passthrough (direction unchanged) but is cleared by
|
|
737
|
+
// any redirection (refraction/reflection/SSS boundary), so env through a leaf hole stays the blurred
|
|
738
|
+
// backdrop while env through glass becomes sharp redirected light.
|
|
739
|
+
If( interaction.isAlphaSkip.not(), () => {
|
|
740
|
+
|
|
741
|
+
flags.assign( flags.bitOr( uint( RAY_FLAG.REDIRECTED ) ) );
|
|
742
|
+
|
|
743
|
+
} );
|
|
643
744
|
writeRayOriginMeta( rayBufferRW, rayID, newOrigin, cameraDepth, sssSteps );
|
|
644
745
|
writeRayDirFlags( rayBufferRW, rayID, interaction.direction, flags );
|
|
645
746
|
// Free bounce: preserve prevBouncePdf (megakernel keeps the last opaque-scatter pdf across
|
|
@@ -657,12 +758,17 @@ export function buildShadeKernel( params ) {
|
|
|
657
758
|
// PathTracerCore.js:1042). Flag the chain so a later env-escape keeps alpha 1 (the gate in the
|
|
658
759
|
// miss branch). Alpha itself already defaults to 1 from Generate in transparent-bg mode, so there
|
|
659
760
|
// is nothing to set here — a ray dying inside geometry (SSS walk) stays solid without reaching this.
|
|
660
|
-
|
|
761
|
+
// Hit opaque geometry: set HAS_HIT_OPAQUE and mark REDIRECTED (this is a real surface scatter,
|
|
762
|
+
// so any later env-escape is redirected light, not the direct backdrop). Single positive bitOr.
|
|
763
|
+
flags.assign( flags.bitOr( uint( RAY_FLAG.HAS_HIT_OPAQUE | RAY_FLAG.REDIRECTED ) ) );
|
|
661
764
|
|
|
662
765
|
const emissive = matSamples.emissive.toVar();
|
|
663
766
|
If( length( emissive ).greaterThan( 0.0 ), () => {
|
|
664
767
|
|
|
665
|
-
|
|
768
|
+
// Key on backdrop-view (not bounceIndex>0) so an emitter seen DIRECTLY through an alpha-cutout hole
|
|
769
|
+
// renders at full intensity (1.0) like a direct view, consistent with env-through-hole — instead of
|
|
770
|
+
// being GI-scaled as if it were an indirect bounce. (MIS below already self-guards via prevBouncePdf.)
|
|
771
|
+
const emissiveGiScale = select( isBackdropView.not(), globalIlluminationIntensity, float( 1.0 ) );
|
|
666
772
|
|
|
667
773
|
// MIS weight vs emissive-triangle NEE (megakernel parity: PathTracerCore.js:1117). On a secondary
|
|
668
774
|
// hit (bounceIndex>0) the prior bounce's NEE also sampled this emitter — power-heuristic balances the
|
|
@@ -734,7 +840,7 @@ export function buildShadeKernel( params ) {
|
|
|
734
840
|
// visible (the surface reflected in a mirror / seen behind glass), not the specular surface. Glass
|
|
735
841
|
// Returns at the transparency block above, so its aux is replaced by the surface behind it. Depth
|
|
736
842
|
// stays at the primary hit (read back + re-packed; the snorm depth re-pack is idempotent — no drift).
|
|
737
|
-
If(
|
|
843
|
+
If( flags.bitAnd( uint( RAY_FLAG.AUX_LOCKED ) ).equal( uint( 0 ) ).and( auxOn ), () => {
|
|
738
844
|
|
|
739
845
|
const primaryDepth = gbDecodeNormalDepth( readGBuffer( gBufferRW, pixelIndex ) ).w;
|
|
740
846
|
writeGBuffer( gBufferRW, pixelIndex, N, primaryDepth, albedo.xyz );
|
|
@@ -754,13 +860,13 @@ export function buildShadeKernel( params ) {
|
|
|
754
860
|
material.clearcoat, material.emissive, material.subsurface,
|
|
755
861
|
) ).toVar();
|
|
756
862
|
|
|
757
|
-
// STBN keyed on (pixel, bounceIndex, frame)
|
|
863
|
+
// STBN keyed on (pixel, bounceIndex, frame).
|
|
758
864
|
const _resX = int( resolution.x ).toVar();
|
|
759
865
|
const _pixelCoord = vec2(
|
|
760
866
|
float( int( pixelIndex ).mod( _resX ) ).add( 0.5 ),
|
|
761
867
|
float( int( pixelIndex ).div( _resX ) ).add( 0.5 ),
|
|
762
868
|
);
|
|
763
|
-
const xi = getRandomSample( _pixelCoord,
|
|
869
|
+
const xi = getRandomSample( _pixelCoord, int( 0 ), bounceIndex, rngState, int( - 1 ), resolution, frame ).toVar();
|
|
764
870
|
const emptyWeights = BRDFWeights( {
|
|
765
871
|
specular: float( 0.0 ), diffuse: float( 0.0 ), sheen: float( 0.0 ),
|
|
766
872
|
clearcoat: float( 0.0 ), transmission: float( 0.0 ), iridescence: float( 0.0 ),
|
|
@@ -365,6 +365,11 @@ 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
|
+
|
|
368
373
|
}
|
|
369
374
|
|
|
370
375
|
// ── Render Completion Chain ───────────────────────────────────
|
|
@@ -595,6 +600,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
595
600
|
setOIDNEnabled( enabled ) {
|
|
596
601
|
|
|
597
602
|
if ( this.denoiser ) this.denoiser.enabled = enabled;
|
|
603
|
+
// OIDN reads the PathTracer aux MRT; re-sync so the wavefront produces it while OIDN is on.
|
|
604
|
+
this._syncGBufferStages();
|
|
598
605
|
|
|
599
606
|
}
|
|
600
607
|
|
|
@@ -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
|
|