rayzee 6.0.0 → 6.1.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 +5 -0
- package/dist/assets/TexturesWorker-DBqGmVdR.js.map +1 -1
- package/dist/rayzee.es.js +2396 -2072
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +49 -53
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/EngineDefaults.js +3 -0
- package/src/PathTracerApp.js +18 -8
- package/src/Pipeline/RenderStage.js +3 -0
- package/src/Processor/IESParser.js +340 -0
- package/src/Processor/LightSerializer.js +32 -4
- package/src/Processor/SceneProcessor.js +0 -1
- package/src/Processor/ShaderBuilder.js +40 -1
- package/src/Processor/Workers/TexturesWorker.js +1 -1
- package/src/RenderSettings.js +3 -0
- package/src/Stages/NormalDepth.js +3 -19
- package/src/Stages/PathTracer.js +15 -9
- package/src/TSL/BVHTraversal.js +5 -18
- package/src/TSL/Clearcoat.js +1 -5
- package/src/TSL/Common.js +2 -2
- package/src/TSL/Debugger.js +0 -14
- package/src/TSL/EmissiveSampling.js +20 -22
- package/src/TSL/Environment.js +60 -14
- package/src/TSL/Fresnel.js +13 -4
- package/src/TSL/LightsCore.js +238 -5
- package/src/TSL/LightsDirect.js +16 -5
- package/src/TSL/LightsIndirect.js +6 -38
- package/src/TSL/LightsSampling.js +119 -185
- package/src/TSL/MaterialEvaluation.js +25 -14
- package/src/TSL/MaterialProperties.js +14 -34
- package/src/TSL/MaterialTransmission.js +100 -222
- package/src/TSL/PathTracer.js +5 -4
- package/src/TSL/PathTracerCore.js +152 -140
- package/src/TSL/Struct.js +7 -1
- package/src/TSL/TextureSampling.js +2 -2
- package/src/index.js +2 -0
- package/src/managers/AnimationManager.js +3 -6
- package/src/managers/DenoisingManager.js +1 -1
- package/src/managers/GoboManager.js +277 -0
- package/src/managers/IESManager.js +268 -0
- package/src/managers/LightManager.js +33 -1
- package/src/managers/TransformManager.js +3 -3
- package/src/managers/UniformManager.js +5 -5
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
* Contains:
|
|
8
8
|
* - getOrCreateMaterialClassification — cached material classification
|
|
9
9
|
* - generateSampledDirection — BRDF direction sampling with multi-lobe CDF
|
|
10
|
-
* -
|
|
11
|
-
* - handleRussianRoulette — adaptive path termination
|
|
10
|
+
* - handleRussianRoulette — adaptive path termination (inlines path importance)
|
|
12
11
|
* - sampleBackgroundLighting — environment background sampling
|
|
13
12
|
* - regularizePathContribution — firefly suppression
|
|
14
13
|
* - Trace — main path tracing loop
|
|
@@ -26,6 +25,7 @@ import {
|
|
|
26
25
|
max,
|
|
27
26
|
min,
|
|
28
27
|
exp,
|
|
28
|
+
log,
|
|
29
29
|
clamp,
|
|
30
30
|
mix,
|
|
31
31
|
dot,
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
getMaterial,
|
|
58
58
|
powerHeuristic,
|
|
59
59
|
balanceHeuristic,
|
|
60
|
+
computeDotProducts,
|
|
60
61
|
} from './Common.js';
|
|
61
62
|
import {
|
|
62
63
|
DirectionSample,
|
|
@@ -68,10 +69,11 @@ import {
|
|
|
68
69
|
MaterialSamples,
|
|
69
70
|
RayTracingMaterial,
|
|
70
71
|
ImportanceSamplingInfo,
|
|
72
|
+
DotProducts,
|
|
71
73
|
} from './Struct.js';
|
|
72
74
|
import { RandomValue, getRandomSample } from './Random.js';
|
|
73
75
|
import { traverseBVH } from './BVHTraversal.js';
|
|
74
|
-
import { sampleEnvironment, sampleEquirect } from './Environment.js';
|
|
76
|
+
import { sampleEnvironment, sampleEquirect, getGroundProjectedDirection } from './Environment.js';
|
|
75
77
|
import { sampleAllMaterialTextures } from './TextureSampling.js';
|
|
76
78
|
import { refineDisplacedIntersection, DisplacementResult } from './Displacement.js';
|
|
77
79
|
import { handleMaterialTransparency, MaterialInteractionResult, sampleMicrofacetTransmission, MicrofacetTransmissionResult } from './MaterialTransmission.js';
|
|
@@ -82,7 +84,7 @@ import {
|
|
|
82
84
|
createMaterialCache,
|
|
83
85
|
getImportanceSamplingInfo,
|
|
84
86
|
} from './MaterialProperties.js';
|
|
85
|
-
import { evaluateMaterialResponse } from './MaterialEvaluation.js';
|
|
87
|
+
import { evaluateMaterialResponse, evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
|
|
86
88
|
import { dielectricF0 } from './Fresnel.js';
|
|
87
89
|
import {
|
|
88
90
|
ImportanceSampleCosine,
|
|
@@ -90,7 +92,7 @@ import {
|
|
|
90
92
|
sampleGGXVNDF,
|
|
91
93
|
} from './MaterialSampling.js';
|
|
92
94
|
import { sampleClearcoat, ClearcoatResult } from './Clearcoat.js';
|
|
93
|
-
import { calculateDirectLightingUnified,
|
|
95
|
+
import { calculateDirectLightingUnified, calculateMaterialPDFFromDots } from './LightsSampling.js';
|
|
94
96
|
import { calculateIndirectLighting } from './LightsIndirect.js';
|
|
95
97
|
import { IndirectLightingResult } from './LightsCore.js';
|
|
96
98
|
import { calculateEmissiveTriangleContribution, calculateEmissiveLightPdf, EmissiveSample } from './EmissiveSampling.js';
|
|
@@ -152,9 +154,11 @@ export const getOrCreateMaterialClassification = Fn( ( [
|
|
|
152
154
|
// =============================================================================
|
|
153
155
|
|
|
154
156
|
export const generateSampledDirection = Fn( ( [
|
|
155
|
-
V, N, material,
|
|
156
|
-
//
|
|
157
|
-
|
|
157
|
+
V, N, material, xi, rngState,
|
|
158
|
+
// Caller-resolved material classification (avoids redundant classifyMaterial —
|
|
159
|
+
// TSL Fn can't write back to caller variables, so the caller is responsible
|
|
160
|
+
// for keeping psCachedClassification current and passes it in here).
|
|
161
|
+
mc,
|
|
158
162
|
weightsComputed, cachedBrdfWeights,
|
|
159
163
|
materialCacheCached, cachedMaterialCache,
|
|
160
164
|
] ) => {
|
|
@@ -163,12 +167,6 @@ export const generateSampledDirection = Fn( ( [
|
|
|
163
167
|
const resultValue = vec3( 0.0 ).toVar();
|
|
164
168
|
const resultPdf = float( 0.0 ).toVar();
|
|
165
169
|
|
|
166
|
-
// Get material classification (cached or computed)
|
|
167
|
-
const mc = MaterialClassification.wrap( getOrCreateMaterialClassification(
|
|
168
|
-
material, materialIndex,
|
|
169
|
-
classificationCached, lastMaterialIndex, cachedClassification,
|
|
170
|
-
) ).toVar();
|
|
171
|
-
|
|
172
170
|
// Compute BRDF weights
|
|
173
171
|
const weights = cachedBrdfWeights.toVar();
|
|
174
172
|
|
|
@@ -291,8 +289,9 @@ export const generateSampledDirection = Fn( ( [
|
|
|
291
289
|
If( sampled.not(), () => {
|
|
292
290
|
|
|
293
291
|
const entering = dot( V, N ).greaterThan( 0.0 ).toVar();
|
|
292
|
+
// pathWavelength=0 — only direction/PDF are consumed here, throughput goes via handleTransmission
|
|
294
293
|
const mtResult = MicrofacetTransmissionResult.wrap( sampleMicrofacetTransmission(
|
|
295
|
-
V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState,
|
|
294
|
+
V, N, material.ior, material.roughness, entering, material.dispersion, xi, rngState, float( 0.0 ),
|
|
296
295
|
) );
|
|
297
296
|
resultDirection.assign( mtResult.direction );
|
|
298
297
|
resultPdf.assign( max( mtResult.pdf, MIN_PDF ) );
|
|
@@ -311,63 +310,6 @@ export const generateSampledDirection = Fn( ( [
|
|
|
311
310
|
|
|
312
311
|
} );
|
|
313
312
|
|
|
314
|
-
// =============================================================================
|
|
315
|
-
// Path Contribution Estimation
|
|
316
|
-
// =============================================================================
|
|
317
|
-
|
|
318
|
-
export const estimatePathContribution = Fn( ( [
|
|
319
|
-
throughput, direction, material, materialIndex,
|
|
320
|
-
classificationCached, lastMaterialIndex, cachedClassification,
|
|
321
|
-
enableEnvironmentLight, useEnvMapIS,
|
|
322
|
-
] ) => {
|
|
323
|
-
|
|
324
|
-
const throughputStrength = max( maxComponent( { v: throughput } ), 0.0 ).toVar();
|
|
325
|
-
|
|
326
|
-
// Use cached material classification
|
|
327
|
-
const mc = MaterialClassification.wrap( getOrCreateMaterialClassification(
|
|
328
|
-
material, materialIndex,
|
|
329
|
-
classificationCached, lastMaterialIndex, cachedClassification,
|
|
330
|
-
) ).toVar();
|
|
331
|
-
|
|
332
|
-
// Enhanced material importance with interaction bonuses
|
|
333
|
-
const materialImportance = mc.complexityScore.toVar();
|
|
334
|
-
|
|
335
|
-
// Interaction complexity bonuses
|
|
336
|
-
If( mc.isMetallic.and( mc.isSmooth ), () => {
|
|
337
|
-
|
|
338
|
-
materialImportance.addAssign( 0.15 );
|
|
339
|
-
|
|
340
|
-
} );
|
|
341
|
-
If( mc.isTransmissive.and( mc.hasClearcoat ), () => {
|
|
342
|
-
|
|
343
|
-
materialImportance.addAssign( 0.12 );
|
|
344
|
-
|
|
345
|
-
} );
|
|
346
|
-
If( mc.isEmissive, () => {
|
|
347
|
-
|
|
348
|
-
materialImportance.addAssign( 0.1 );
|
|
349
|
-
|
|
350
|
-
} );
|
|
351
|
-
materialImportance.assign( clamp( materialImportance, 0.0, 1.0 ) );
|
|
352
|
-
|
|
353
|
-
// Direction importance calculation
|
|
354
|
-
const directionImportance = float( 0.5 ).toVar();
|
|
355
|
-
|
|
356
|
-
If( enableEnvironmentLight.and( useEnvMapIS ).and( throughputStrength.greaterThan( 0.01 ) ), () => {
|
|
357
|
-
|
|
358
|
-
const cosTheta = clamp( direction.y, 0.0, 1.0 );
|
|
359
|
-
directionImportance.assign( mix( float( 0.3 ), float( 0.8 ), cosTheta.mul( cosTheta ) ) );
|
|
360
|
-
|
|
361
|
-
} );
|
|
362
|
-
|
|
363
|
-
// Enhanced weighting
|
|
364
|
-
const throughputWeight = smoothstep( float( 0.001 ), float( 0.1 ), throughputStrength );
|
|
365
|
-
return throughputStrength.mul(
|
|
366
|
-
mix( materialImportance.mul( 0.7 ), directionImportance, 0.3 ),
|
|
367
|
-
).mul( throughputWeight );
|
|
368
|
-
|
|
369
|
-
} );
|
|
370
|
-
|
|
371
313
|
// =============================================================================
|
|
372
314
|
// Russian Roulette Path Termination
|
|
373
315
|
// =============================================================================
|
|
@@ -375,7 +317,6 @@ export const estimatePathContribution = Fn( ( [
|
|
|
375
317
|
export const handleRussianRoulette = Fn( ( [
|
|
376
318
|
depth, throughput, material, materialIndex, rayDirection, rngState,
|
|
377
319
|
classificationCached, lastMaterialIndex, cachedClassification,
|
|
378
|
-
weightsComputed, pathImportance,
|
|
379
320
|
enableEnvironmentLight, useEnvMapIS,
|
|
380
321
|
] ) => {
|
|
381
322
|
|
|
@@ -443,22 +384,37 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
443
384
|
|
|
444
385
|
} ).Else( () => {
|
|
445
386
|
|
|
446
|
-
// Path
|
|
447
|
-
const
|
|
387
|
+
// Path contribution estimate — reuses throughputStrength + mc from outer scope.
|
|
388
|
+
const estMaterialImportance = mc.complexityScore.toVar();
|
|
389
|
+
If( mc.isMetallic.and( mc.isSmooth ), () => {
|
|
448
390
|
|
|
449
|
-
|
|
391
|
+
estMaterialImportance.addAssign( 0.15 );
|
|
450
392
|
|
|
451
|
-
|
|
393
|
+
} );
|
|
394
|
+
If( mc.isTransmissive.and( mc.hasClearcoat ), () => {
|
|
452
395
|
|
|
453
|
-
|
|
396
|
+
estMaterialImportance.addAssign( 0.12 );
|
|
454
397
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
) );
|
|
398
|
+
} );
|
|
399
|
+
If( mc.isEmissive, () => {
|
|
400
|
+
|
|
401
|
+
estMaterialImportance.addAssign( 0.1 );
|
|
460
402
|
|
|
461
403
|
} );
|
|
404
|
+
estMaterialImportance.assign( clamp( estMaterialImportance, 0.0, 1.0 ) );
|
|
405
|
+
|
|
406
|
+
const directionImportance = float( 0.5 ).toVar();
|
|
407
|
+
If( enableEnvironmentLight.and( useEnvMapIS ).and( throughputStrength.greaterThan( 0.01 ) ), () => {
|
|
408
|
+
|
|
409
|
+
const cosTheta = clamp( rayDirection.y, 0.0, 1.0 );
|
|
410
|
+
directionImportance.assign( mix( float( 0.3 ), float( 0.8 ), cosTheta.mul( cosTheta ) ) );
|
|
411
|
+
|
|
412
|
+
} );
|
|
413
|
+
|
|
414
|
+
const throughputWeight = smoothstep( float( 0.001 ), float( 0.1 ), throughputStrength );
|
|
415
|
+
const pathContribution = throughputStrength.mul(
|
|
416
|
+
mix( estMaterialImportance.mul( 0.7 ), directionImportance, 0.3 ),
|
|
417
|
+
).mul( throughputWeight ).toVar();
|
|
462
418
|
|
|
463
419
|
// Smooth adaptive continuation probability (no discrete depth brackets)
|
|
464
420
|
// Early behavior: throughput + material driven, generous
|
|
@@ -515,9 +471,10 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
515
471
|
// =============================================================================
|
|
516
472
|
|
|
517
473
|
export const sampleBackgroundLighting = Fn( ( [
|
|
518
|
-
isPrimaryRay, direction,
|
|
474
|
+
isPrimaryRay, rayOrigin, direction,
|
|
519
475
|
envTexture, envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
520
476
|
showBackground, backgroundIntensity,
|
|
477
|
+
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
521
478
|
] ) => {
|
|
522
479
|
|
|
523
480
|
// Only hide background for primary camera rays when showBackground is false
|
|
@@ -530,8 +487,18 @@ export const sampleBackgroundLighting = Fn( ( [
|
|
|
530
487
|
|
|
531
488
|
} ).Else( () => {
|
|
532
489
|
|
|
490
|
+
// Primary-ray only: indirect bounces must see the raw envmap so shading stays physically correct.
|
|
491
|
+
const effectiveDir = direction.toVar();
|
|
492
|
+
If( isPrimaryRay.and( groundProjectionEnabled ), () => {
|
|
493
|
+
|
|
494
|
+
effectiveDir.assign( getGroundProjectedDirection(
|
|
495
|
+
rayOrigin, direction, groundProjectionRadius, groundProjectionHeight,
|
|
496
|
+
) );
|
|
497
|
+
|
|
498
|
+
} );
|
|
499
|
+
|
|
533
500
|
const sampled = sampleEnvironment( {
|
|
534
|
-
tex: envTexture, samp: sampler( envTexture ), direction, environmentMatrix: envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
501
|
+
tex: envTexture, samp: sampler( envTexture ), direction: effectiveDir, environmentMatrix: envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
535
502
|
} );
|
|
536
503
|
|
|
537
504
|
If( isPrimaryRay, () => {
|
|
@@ -566,7 +533,7 @@ export const regularizePathContribution = /*@__PURE__*/ wgslFn( `
|
|
|
566
533
|
// =============================================================================
|
|
567
534
|
|
|
568
535
|
export const Trace = Fn( ( [
|
|
569
|
-
ray, rngState, rayIndex,
|
|
536
|
+
ray, rngState, rayIndex,
|
|
570
537
|
// BVH / Scene
|
|
571
538
|
bvhBuffer,
|
|
572
539
|
triangleBuffer,
|
|
@@ -585,11 +552,12 @@ export const Trace = Fn( ( [
|
|
|
585
552
|
envCDFBuffer,
|
|
586
553
|
envTotalSum, envCompensationDelta, envResolution,
|
|
587
554
|
enableEnvironmentLight, useEnvMapIS,
|
|
555
|
+
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
588
556
|
// Rendering parameters
|
|
589
557
|
maxBounceCount, transmissiveBounces,
|
|
590
558
|
backgroundIntensity, showBackground, transparentBackground,
|
|
591
559
|
fireflyThreshold, globalIlluminationIntensity,
|
|
592
|
-
|
|
560
|
+
enableEmissiveTriangleSampling,
|
|
593
561
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
594
562
|
lightBVHBuffer, lightBVHNodeCount,
|
|
595
563
|
// Per-pixel info
|
|
@@ -613,11 +581,29 @@ export const Trace = Fn( ( [
|
|
|
613
581
|
// behind glass), not the glass surface itself.
|
|
614
582
|
const auxLocked = tslBool( false ).toVar();
|
|
615
583
|
|
|
616
|
-
// Medium stack for transmission (
|
|
584
|
+
// Medium stack for transmission (slots 1-3 for nested media, depth 0 = air).
|
|
585
|
+
// Each slot tracks IOR plus KHR_materials_volume attenuation (color + distance)
|
|
586
|
+
// so per-bounce in-volume absorption uses the actual ray path length.
|
|
617
587
|
const mediumStackDepth = int( 0 ).toVar();
|
|
618
588
|
const mediumStack_ior_1 = float( 1.0 ).toVar();
|
|
619
589
|
const mediumStack_ior_2 = float( 1.0 ).toVar();
|
|
620
590
|
const mediumStack_ior_3 = float( 1.0 ).toVar();
|
|
591
|
+
const mediumStack_attColor_1 = vec3( 1.0 ).toVar();
|
|
592
|
+
const mediumStack_attColor_2 = vec3( 1.0 ).toVar();
|
|
593
|
+
const mediumStack_attColor_3 = vec3( 1.0 ).toVar();
|
|
594
|
+
const mediumStack_attDist_1 = float( 0.0 ).toVar();
|
|
595
|
+
const mediumStack_attDist_2 = float( 0.0 ).toVar();
|
|
596
|
+
const mediumStack_attDist_3 = float( 0.0 ).toVar();
|
|
597
|
+
// Precomputed Beer-Lambert absorption coefficient sigma_a = -log(attColor)/attDist.
|
|
598
|
+
// Stored at push time so per-bounce absorption inside a medium becomes a single
|
|
599
|
+
// exp(-sigma_a * thickness) instead of a log + div + exp every bounce.
|
|
600
|
+
const mediumStack_sigmaA_1 = vec3( 0.0 ).toVar();
|
|
601
|
+
const mediumStack_sigmaA_2 = vec3( 0.0 ).toVar();
|
|
602
|
+
const mediumStack_sigmaA_3 = vec3( 0.0 ).toVar();
|
|
603
|
+
|
|
604
|
+
// Locked at the first dispersive transmission; reused for subsequent transmissions on
|
|
605
|
+
// the path so multi-bounce dispersion doesn't collapse under repeated colorWeight ×.
|
|
606
|
+
const pathWavelength = float( 0.0 ).toVar();
|
|
621
607
|
|
|
622
608
|
// Render state
|
|
623
609
|
const stateTraversals = maxBounceCount.toVar();
|
|
@@ -630,8 +616,6 @@ export const Trace = Fn( ( [
|
|
|
630
616
|
const psWeightsComputed = tslBool( false ).toVar();
|
|
631
617
|
const psClassificationCached = tslBool( false ).toVar();
|
|
632
618
|
const psMaterialCacheCached = tslBool( false ).toVar();
|
|
633
|
-
const psTexturesLoaded = tslBool( false ).toVar();
|
|
634
|
-
const psPathImportance = float( 0.0 ).toVar();
|
|
635
619
|
const psLastMaterialIndex = int( - 1 ).toVar();
|
|
636
620
|
|
|
637
621
|
// Cached classification
|
|
@@ -688,16 +672,41 @@ export const Trace = Fn( ( [
|
|
|
688
672
|
currentRay,
|
|
689
673
|
bvhBuffer,
|
|
690
674
|
triangleBuffer,
|
|
691
|
-
materialBuffer,
|
|
692
675
|
) ).toVar();
|
|
693
676
|
|
|
677
|
+
// KHR_materials_volume: apply Beer's law over the actual distance the ray
|
|
678
|
+
// traveled inside the current medium. Top-of-stack holds the medium the ray
|
|
679
|
+
// is currently in — depth==0 means air (no absorption). sigma_a was
|
|
680
|
+
// precomputed at push time, so this collapses to a single exp().
|
|
681
|
+
If( hitInfo.didHit.and( mediumStackDepth.greaterThan( int( 0 ) ) ), () => {
|
|
682
|
+
|
|
683
|
+
const mSigmaA = vec3( 0.0 ).toVar();
|
|
684
|
+
If( mediumStackDepth.equal( int( 1 ) ), () => {
|
|
685
|
+
|
|
686
|
+
mSigmaA.assign( mediumStack_sigmaA_1 );
|
|
687
|
+
|
|
688
|
+
} ).ElseIf( mediumStackDepth.equal( int( 2 ) ), () => {
|
|
689
|
+
|
|
690
|
+
mSigmaA.assign( mediumStack_sigmaA_2 );
|
|
691
|
+
|
|
692
|
+
} ).ElseIf( mediumStackDepth.equal( int( 3 ) ), () => {
|
|
693
|
+
|
|
694
|
+
mSigmaA.assign( mediumStack_sigmaA_3 );
|
|
695
|
+
|
|
696
|
+
} );
|
|
697
|
+
|
|
698
|
+
throughput.mulAssign( exp( mSigmaA.mul( hitInfo.dst ).negate() ) );
|
|
699
|
+
|
|
700
|
+
} );
|
|
701
|
+
|
|
694
702
|
If( hitInfo.didHit.not(), () => {
|
|
695
703
|
|
|
696
704
|
// ENVIRONMENT LIGHTING
|
|
697
705
|
const envColor = sampleBackgroundLighting(
|
|
698
|
-
stateIsPrimaryRay, rayDirection,
|
|
706
|
+
stateIsPrimaryRay, rayOrigin, rayDirection,
|
|
699
707
|
envTexture, envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
700
708
|
showBackground, backgroundIntensity,
|
|
709
|
+
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
701
710
|
);
|
|
702
711
|
|
|
703
712
|
// MIS weight for implicit environment hit — prevents double-counting with NEE.
|
|
@@ -799,10 +808,12 @@ export const Trace = Fn( ( [
|
|
|
799
808
|
|
|
800
809
|
// Handle transparent materials
|
|
801
810
|
const interaction = MaterialInteractionResult.wrap( handleMaterialTransparency(
|
|
802
|
-
currentRay,
|
|
811
|
+
currentRay, N, material, rngState,
|
|
803
812
|
stateTransmissiveTraversals,
|
|
804
813
|
currentMediumIOR, previousMediumIOR,
|
|
814
|
+
pathWavelength,
|
|
805
815
|
) ).toVar();
|
|
816
|
+
pathWavelength.assign( interaction.pathWavelength );
|
|
806
817
|
|
|
807
818
|
If( interaction.continueRay, () => {
|
|
808
819
|
|
|
@@ -819,22 +830,39 @@ export const Trace = Fn( ( [
|
|
|
819
830
|
|
|
820
831
|
If( interaction.entering, () => {
|
|
821
832
|
|
|
822
|
-
// Push new medium onto stack
|
|
833
|
+
// Push new medium onto stack (IOR + KHR_materials_volume attenuation)
|
|
823
834
|
If( mediumStackDepth.lessThan( int( 3 ) ), () => {
|
|
824
835
|
|
|
825
836
|
mediumStackDepth.addAssign( 1 );
|
|
826
837
|
|
|
838
|
+
// Precompute sigma_a = -log(attColor)/attDist once at push time.
|
|
839
|
+
// attDist==0 means "no absorption" — store sigma_a=0 so exp() returns 1.
|
|
840
|
+
const mSigmaA = select(
|
|
841
|
+
material.attenuationDistance.greaterThan( 0.0 ),
|
|
842
|
+
log( max( material.attenuationColor, vec3( 0.001 ) ) ).negate().div( material.attenuationDistance ),
|
|
843
|
+
vec3( 0.0 )
|
|
844
|
+
).toVar();
|
|
845
|
+
|
|
827
846
|
If( mediumStackDepth.equal( int( 1 ) ), () => {
|
|
828
847
|
|
|
829
848
|
mediumStack_ior_1.assign( material.ior );
|
|
849
|
+
mediumStack_attColor_1.assign( material.attenuationColor );
|
|
850
|
+
mediumStack_attDist_1.assign( material.attenuationDistance );
|
|
851
|
+
mediumStack_sigmaA_1.assign( mSigmaA );
|
|
830
852
|
|
|
831
853
|
} ).ElseIf( mediumStackDepth.equal( int( 2 ) ), () => {
|
|
832
854
|
|
|
833
855
|
mediumStack_ior_2.assign( material.ior );
|
|
856
|
+
mediumStack_attColor_2.assign( material.attenuationColor );
|
|
857
|
+
mediumStack_attDist_2.assign( material.attenuationDistance );
|
|
858
|
+
mediumStack_sigmaA_2.assign( mSigmaA );
|
|
834
859
|
|
|
835
860
|
} ).ElseIf( mediumStackDepth.equal( int( 3 ) ), () => {
|
|
836
861
|
|
|
837
862
|
mediumStack_ior_3.assign( material.ior );
|
|
863
|
+
mediumStack_attColor_3.assign( material.attenuationColor );
|
|
864
|
+
mediumStack_attDist_3.assign( material.attenuationDistance );
|
|
865
|
+
mediumStack_sigmaA_3.assign( mSigmaA );
|
|
838
866
|
|
|
839
867
|
} );
|
|
840
868
|
|
|
@@ -908,6 +936,19 @@ export const Trace = Fn( ( [
|
|
|
908
936
|
const V = rayDirection.negate().toVar();
|
|
909
937
|
material.sheenRoughness.assign( clamp( material.sheenRoughness, MIN_ROUGHNESS, MAX_ROUGHNESS ) );
|
|
910
938
|
|
|
939
|
+
// Sync material classification cache up front — the materialCache, BRDF
|
|
940
|
+
// sample, importance sampling, and Russian roulette all consume it.
|
|
941
|
+
// getOrCreateMaterialClassification is a cache hit when materialIndex
|
|
942
|
+
// matches the previous bounce; otherwise it runs classifyMaterial once.
|
|
943
|
+
// Doing this here eliminates a redundant classifyMaterial that previously
|
|
944
|
+
// fired after generateSampledDirection to "sync" the caller's variable.
|
|
945
|
+
psCachedClassification.assign( MaterialClassification.wrap( getOrCreateMaterialClassification(
|
|
946
|
+
material, hitInfo.materialIndex,
|
|
947
|
+
psClassificationCached, psLastMaterialIndex, psCachedClassification,
|
|
948
|
+
) ) );
|
|
949
|
+
psClassificationCached.assign( tslBool( true ) );
|
|
950
|
+
psLastMaterialIndex.assign( hitInfo.materialIndex );
|
|
951
|
+
|
|
911
952
|
// Create material cache if needed
|
|
912
953
|
If( psMaterialCacheCached.not(), () => {
|
|
913
954
|
|
|
@@ -933,9 +974,12 @@ export const Trace = Fn( ( [
|
|
|
933
974
|
|
|
934
975
|
} ).Else( () => {
|
|
935
976
|
|
|
977
|
+
// Classification was already synced at the top of the bounce — pass
|
|
978
|
+
// psCachedClassification directly so generateSampledDirection doesn't
|
|
979
|
+
// have to call classifyMaterial again internally.
|
|
936
980
|
const brdfSample = DirectionSample.wrap( generateSampledDirection(
|
|
937
|
-
V, N, material,
|
|
938
|
-
|
|
981
|
+
V, N, material, randomSample, rngState,
|
|
982
|
+
psCachedClassification,
|
|
939
983
|
psWeightsComputed, psCachedBrdfWeights,
|
|
940
984
|
psMaterialCacheCached, psCachedMaterialCache,
|
|
941
985
|
) );
|
|
@@ -943,22 +987,6 @@ export const Trace = Fn( ( [
|
|
|
943
987
|
brdfValue.assign( brdfSample.value );
|
|
944
988
|
brdfPdf.assign( brdfSample.pdf );
|
|
945
989
|
|
|
946
|
-
// Sync psCachedClassification for downstream consumers (importance sampling, Russian roulette).
|
|
947
|
-
// generateSampledDirection computed the correct classification internally via materialIndex
|
|
948
|
-
// guard, but TSL Fn can't write back to the caller's variable — update it here.
|
|
949
|
-
If( psLastMaterialIndex.notEqual( hitInfo.materialIndex ).or( psClassificationCached.not() ), () => {
|
|
950
|
-
|
|
951
|
-
psCachedClassification.assign( classifyMaterial(
|
|
952
|
-
material.metalness, material.roughness,
|
|
953
|
-
material.transmission, material.clearcoat,
|
|
954
|
-
material.emissive,
|
|
955
|
-
) );
|
|
956
|
-
|
|
957
|
-
} );
|
|
958
|
-
|
|
959
|
-
// Update cache state after generateSampledDirection
|
|
960
|
-
psClassificationCached.assign( tslBool( true ) );
|
|
961
|
-
psLastMaterialIndex.assign( hitInfo.materialIndex );
|
|
962
990
|
psWeightsComputed.assign( tslBool( true ) );
|
|
963
991
|
|
|
964
992
|
} );
|
|
@@ -996,7 +1024,7 @@ export const Trace = Fn( ( [
|
|
|
996
1024
|
hitInfo.hitPoint, N, material,
|
|
997
1025
|
V,
|
|
998
1026
|
brdfDir, brdfPdf, brdfValue,
|
|
999
|
-
|
|
1027
|
+
bounceIndex, rngState,
|
|
1000
1028
|
directionalLightsBuffer, numDirectionalLights,
|
|
1001
1029
|
areaLightsBuffer, numAreaLights,
|
|
1002
1030
|
pointLightsBuffer, numPointLights,
|
|
@@ -1017,10 +1045,9 @@ export const Trace = Fn( ( [
|
|
|
1017
1045
|
// 2b. EMISSIVE TRIANGLE DIRECT LIGHTING
|
|
1018
1046
|
If( enableEmissiveTriangleSampling.equal( int( 1 ) ).and( emissiveTriangleCount.greaterThan( int( 0 ) ) ), () => {
|
|
1019
1047
|
|
|
1020
|
-
|
|
1021
|
-
const traceShadowRayWrapped = Fn( ( [ origin, dir, maxDist, rs ] ) => {
|
|
1048
|
+
const traceShadowRayWrapped = Fn( ( [ origin, dir, maxDist ] ) => {
|
|
1022
1049
|
|
|
1023
|
-
return traceShadowRay( origin, dir, maxDist,
|
|
1050
|
+
return traceShadowRay( origin, dir, maxDist, traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer );
|
|
1024
1051
|
|
|
1025
1052
|
} );
|
|
1026
1053
|
|
|
@@ -1050,12 +1077,14 @@ export const Trace = Fn( ( [
|
|
|
1050
1077
|
const rayOffset = calculateRayOffset( hitInfo.hitPoint, N, material );
|
|
1051
1078
|
const rayOrigin = hitInfo.hitPoint.add( rayOffset );
|
|
1052
1079
|
const shadowDist = emissiveSample.distance.sub( 0.001 );
|
|
1053
|
-
const visibility = traceShadowRayWrapped( rayOrigin, emissiveSample.direction, shadowDist
|
|
1080
|
+
const visibility = traceShadowRayWrapped( rayOrigin, emissiveSample.direction, shadowDist );
|
|
1054
1081
|
|
|
1055
1082
|
If( visibility.greaterThan( 0.0 ), () => {
|
|
1056
1083
|
|
|
1057
|
-
|
|
1058
|
-
const
|
|
1084
|
+
// Share H + dot products between BRDF eval and PDF eval.
|
|
1085
|
+
const emisDots = DotProducts.wrap( computeDotProducts( N, V, emissiveSample.direction ) );
|
|
1086
|
+
const brdfValue = evaluateMaterialResponseFromDots( material, emisDots );
|
|
1087
|
+
const brdfPdf = calculateMaterialPDFFromDots( material, emisDots );
|
|
1059
1088
|
const misWeight = select(
|
|
1060
1089
|
brdfPdf.greaterThan( 0.0 ),
|
|
1061
1090
|
powerHeuristic( { pdf1: emissiveSample.pdf, pdf2: brdfPdf } ),
|
|
@@ -1082,12 +1111,11 @@ export const Trace = Fn( ( [
|
|
|
1082
1111
|
// Fallback: flat CDF importance sampling
|
|
1083
1112
|
const emissiveLight = calculateEmissiveTriangleContribution(
|
|
1084
1113
|
hitInfo.hitPoint, N, V, material,
|
|
1085
|
-
|
|
1114
|
+
bounceIndex, rngState,
|
|
1086
1115
|
emissiveBoost,
|
|
1087
1116
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
1088
1117
|
triangleBuffer,
|
|
1089
1118
|
traceShadowRayWrapped,
|
|
1090
|
-
evaluateMaterialResponse,
|
|
1091
1119
|
calculateRayOffset,
|
|
1092
1120
|
);
|
|
1093
1121
|
|
|
@@ -1099,34 +1127,19 @@ export const Trace = Fn( ( [
|
|
|
1099
1127
|
|
|
1100
1128
|
} );
|
|
1101
1129
|
|
|
1102
|
-
//
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
// Update classification first
|
|
1106
|
-
psCachedClassification.assign( MaterialClassification.wrap( getOrCreateMaterialClassification(
|
|
1107
|
-
material, hitInfo.materialIndex,
|
|
1108
|
-
psClassificationCached, psLastMaterialIndex, psCachedClassification,
|
|
1109
|
-
) ) );
|
|
1110
|
-
psClassificationCached.assign( tslBool( true ) );
|
|
1111
|
-
psLastMaterialIndex.assign( hitInfo.materialIndex );
|
|
1112
|
-
|
|
1113
|
-
} );
|
|
1114
|
-
|
|
1130
|
+
// Classification was already synced at the top of the bounce loop, so
|
|
1131
|
+
// psCachedClassification is current here regardless of which BRDF sample
|
|
1132
|
+
// path ran above.
|
|
1115
1133
|
const samplingInfo = ImportanceSamplingInfo.wrap( getImportanceSamplingInfo(
|
|
1116
1134
|
material, bounceIndex, psCachedClassification,
|
|
1117
|
-
environmentIntensity, useEnvMapIS, enableEnvironmentLight,
|
|
1118
1135
|
) );
|
|
1119
1136
|
|
|
1120
1137
|
// 3. INDIRECT LIGHTING
|
|
1121
1138
|
const indirectResult = IndirectLightingResult.wrap( calculateIndirectLighting(
|
|
1122
1139
|
V, N, material,
|
|
1123
1140
|
brdfDir, brdfPdf, brdfValue,
|
|
1124
|
-
rayIndex, bounceIndex,
|
|
1125
1141
|
rngState,
|
|
1126
1142
|
samplingInfo,
|
|
1127
|
-
envTexture, environmentIntensity, envMatrix,
|
|
1128
|
-
envTotalSum, envCompensationDelta, envResolution,
|
|
1129
|
-
enableEnvironmentLight, useEnvMapIS,
|
|
1130
1143
|
) );
|
|
1131
1144
|
throughput.mulAssign( indirectResult.throughput );
|
|
1132
1145
|
|
|
@@ -1185,7 +1198,6 @@ export const Trace = Fn( ( [
|
|
|
1185
1198
|
bounceIndex, throughput, material, hitInfo.materialIndex,
|
|
1186
1199
|
rayDirection, rngState,
|
|
1187
1200
|
psClassificationCached, psLastMaterialIndex, psCachedClassification,
|
|
1188
|
-
psWeightsComputed, psPathImportance,
|
|
1189
1201
|
enableEnvironmentLight, useEnvMapIS,
|
|
1190
1202
|
);
|
|
1191
1203
|
If( rrSurvivalProb.lessThanEqual( 0.0 ), () => {
|
package/src/TSL/Struct.js
CHANGED
|
@@ -127,7 +127,6 @@ export const ImportanceSamplingInfo = struct( {
|
|
|
127
127
|
specularImportance: 'float',
|
|
128
128
|
transmissionImportance: 'float',
|
|
129
129
|
clearcoatImportance: 'float',
|
|
130
|
-
envmapImportance: 'float',
|
|
131
130
|
} );
|
|
132
131
|
|
|
133
132
|
export const DotProducts = struct( {
|
|
@@ -138,6 +137,13 @@ export const DotProducts = struct( {
|
|
|
138
137
|
LoH: 'float', // Light • Half
|
|
139
138
|
} );
|
|
140
139
|
|
|
140
|
+
// Kulla-Conty DFG approximation outputs (computed once, consumed by both
|
|
141
|
+
// the multiscatter compensation factor and the total directional albedo).
|
|
142
|
+
export const DFGResult = struct( {
|
|
143
|
+
compensation: 'vec3', // 1 + F0 * (1/Ew - 1)
|
|
144
|
+
E_total: 'vec3', // clamp(E_ss * compensation, 0, 1)
|
|
145
|
+
} );
|
|
146
|
+
|
|
141
147
|
export const MaterialSamples = struct( {
|
|
142
148
|
albedo: 'vec4',
|
|
143
149
|
emissive: 'vec3',
|
|
@@ -307,7 +307,7 @@ export const processBump = Fn( ( [ bumpMaps, currentNormal, material, uvCache ]
|
|
|
307
307
|
|
|
308
308
|
} );
|
|
309
309
|
|
|
310
|
-
export const processEmissive = Fn( ( [ emissiveMaps, material,
|
|
310
|
+
export const processEmissive = Fn( ( [ emissiveMaps, material, uvCache ] ) => {
|
|
311
311
|
|
|
312
312
|
const emissionBase = material.emissive.mul( material.emissiveIntensity ).toVar();
|
|
313
313
|
|
|
@@ -361,7 +361,7 @@ export const sampleAllMaterialTextures = Fn( ( [
|
|
|
361
361
|
const currentNormal = processNormal( normalMaps, geometryNormal, material, uvCache ).toVar();
|
|
362
362
|
normal.assign( processBump( bumpMaps, currentNormal, material, uvCache ) );
|
|
363
363
|
|
|
364
|
-
emissive.assign( processEmissive( emissiveMaps, material,
|
|
364
|
+
emissive.assign( processEmissive( emissiveMaps, material, uvCache ) );
|
|
365
365
|
|
|
366
366
|
} );
|
|
367
367
|
|
package/src/index.js
CHANGED
|
@@ -39,6 +39,8 @@ export {
|
|
|
39
39
|
export { RenderSettings } from './RenderSettings.js';
|
|
40
40
|
export { CameraManager } from './managers/CameraManager.js';
|
|
41
41
|
export { LightManager } from './managers/LightManager.js';
|
|
42
|
+
export { GoboManager } from './managers/GoboManager.js';
|
|
43
|
+
export { IESManager } from './managers/IESManager.js';
|
|
42
44
|
export { DenoisingManager } from './managers/DenoisingManager.js';
|
|
43
45
|
export { OverlayManager } from './managers/OverlayManager.js';
|
|
44
46
|
|
|
@@ -27,7 +27,6 @@ export class AnimationManager extends EventDispatcher {
|
|
|
27
27
|
this._posBuffer = null; // Float32Array(triCount * 9) — reused each frame
|
|
28
28
|
this._tempVec = new Vector3();
|
|
29
29
|
this._skinnedCache = null; // per-mesh Float32Array for skinned vertex positions
|
|
30
|
-
this._totalTriangleCount = 0;
|
|
31
30
|
this._clipsCache = null;
|
|
32
31
|
this._savedTimeScale = 1;
|
|
33
32
|
this.onFinished = null; // callback when a non-looping clip ends
|
|
@@ -45,9 +44,8 @@ export class AnimationManager extends EventDispatcher {
|
|
|
45
44
|
* @param {Object3D} mixerRoot - GLTF model root (for animation track name resolution)
|
|
46
45
|
* @param {Mesh[]} meshes - SceneProcessor.meshes (extraction order)
|
|
47
46
|
* @param {AnimationClip[]} animations - GLTF animation clips
|
|
48
|
-
* @param {number} triangleCount - Total triangle count
|
|
49
47
|
*/
|
|
50
|
-
init( scene, mixerRoot, meshes, animations
|
|
48
|
+
init( scene, mixerRoot, meshes, animations ) {
|
|
51
49
|
|
|
52
50
|
this.dispose();
|
|
53
51
|
|
|
@@ -56,7 +54,6 @@ export class AnimationManager extends EventDispatcher {
|
|
|
56
54
|
this._scene = scene;
|
|
57
55
|
this._mixerRoot = mixerRoot;
|
|
58
56
|
this._meshes = meshes;
|
|
59
|
-
this._totalTriangleCount = triangleCount;
|
|
60
57
|
|
|
61
58
|
// Try mixerRoot (GLTF model root) first for track resolution.
|
|
62
59
|
// Fall back to scene if no tracks bind successfully.
|
|
@@ -121,10 +118,10 @@ export class AnimationManager extends EventDispatcher {
|
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
// Allocate reusable output buffer
|
|
124
|
-
this._posBuffer = new Float32Array(
|
|
121
|
+
this._posBuffer = new Float32Array( offset * 9 );
|
|
125
122
|
|
|
126
123
|
const skinnedCount = meshes.filter( m => m.isSkinnedMesh ).length;
|
|
127
|
-
console.debug( `[AnimationManager] Init: ${animations.length} clips, ${meshes.length} meshes (${skinnedCount} skinned), ${
|
|
124
|
+
console.debug( `[AnimationManager] Init: ${animations.length} clips, ${meshes.length} meshes (${skinnedCount} skinned), ${offset} triangles` );
|
|
128
125
|
|
|
129
126
|
}
|
|
130
127
|
|
|
@@ -575,7 +575,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
575
575
|
*/
|
|
576
576
|
setAdaptiveSamplingParams( params ) {
|
|
577
577
|
|
|
578
|
-
if ( params.min !== undefined ) this._stages.pathTracer?.
|
|
578
|
+
if ( params.min !== undefined ) this._stages.pathTracer?.setUniform( 'adaptiveSamplingMin', params.min );
|
|
579
579
|
if ( params.adaptiveSamplingMax !== undefined ) this._settings?.set( 'adaptiveSamplingMax', params.adaptiveSamplingMax );
|
|
580
580
|
this._stages.adaptiveSampling?.setAdaptiveSamplingParameters( params );
|
|
581
581
|
|