rayzee 6.0.1 → 6.2.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 +2421 -2078
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +55 -52
- 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 +4 -6
- package/src/TSL/Common.js +1 -1
- package/src/TSL/Debugger.js +0 -2
- 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 +4 -37
- 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 +18 -37
- package/src/TSL/PathTracer.js +25 -7
- package/src/TSL/PathTracerCore.js +144 -139
- 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
|
|
|
@@ -312,63 +310,6 @@ export const generateSampledDirection = Fn( ( [
|
|
|
312
310
|
|
|
313
311
|
} );
|
|
314
312
|
|
|
315
|
-
// =============================================================================
|
|
316
|
-
// Path Contribution Estimation
|
|
317
|
-
// =============================================================================
|
|
318
|
-
|
|
319
|
-
export const estimatePathContribution = Fn( ( [
|
|
320
|
-
throughput, direction, material, materialIndex,
|
|
321
|
-
classificationCached, lastMaterialIndex, cachedClassification,
|
|
322
|
-
enableEnvironmentLight, useEnvMapIS,
|
|
323
|
-
] ) => {
|
|
324
|
-
|
|
325
|
-
const throughputStrength = max( maxComponent( { v: throughput } ), 0.0 ).toVar();
|
|
326
|
-
|
|
327
|
-
// Use cached material classification
|
|
328
|
-
const mc = MaterialClassification.wrap( getOrCreateMaterialClassification(
|
|
329
|
-
material, materialIndex,
|
|
330
|
-
classificationCached, lastMaterialIndex, cachedClassification,
|
|
331
|
-
) ).toVar();
|
|
332
|
-
|
|
333
|
-
// Enhanced material importance with interaction bonuses
|
|
334
|
-
const materialImportance = mc.complexityScore.toVar();
|
|
335
|
-
|
|
336
|
-
// Interaction complexity bonuses
|
|
337
|
-
If( mc.isMetallic.and( mc.isSmooth ), () => {
|
|
338
|
-
|
|
339
|
-
materialImportance.addAssign( 0.15 );
|
|
340
|
-
|
|
341
|
-
} );
|
|
342
|
-
If( mc.isTransmissive.and( mc.hasClearcoat ), () => {
|
|
343
|
-
|
|
344
|
-
materialImportance.addAssign( 0.12 );
|
|
345
|
-
|
|
346
|
-
} );
|
|
347
|
-
If( mc.isEmissive, () => {
|
|
348
|
-
|
|
349
|
-
materialImportance.addAssign( 0.1 );
|
|
350
|
-
|
|
351
|
-
} );
|
|
352
|
-
materialImportance.assign( clamp( materialImportance, 0.0, 1.0 ) );
|
|
353
|
-
|
|
354
|
-
// Direction importance calculation
|
|
355
|
-
const directionImportance = float( 0.5 ).toVar();
|
|
356
|
-
|
|
357
|
-
If( enableEnvironmentLight.and( useEnvMapIS ).and( throughputStrength.greaterThan( 0.01 ) ), () => {
|
|
358
|
-
|
|
359
|
-
const cosTheta = clamp( direction.y, 0.0, 1.0 );
|
|
360
|
-
directionImportance.assign( mix( float( 0.3 ), float( 0.8 ), cosTheta.mul( cosTheta ) ) );
|
|
361
|
-
|
|
362
|
-
} );
|
|
363
|
-
|
|
364
|
-
// Enhanced weighting
|
|
365
|
-
const throughputWeight = smoothstep( float( 0.001 ), float( 0.1 ), throughputStrength );
|
|
366
|
-
return throughputStrength.mul(
|
|
367
|
-
mix( materialImportance.mul( 0.7 ), directionImportance, 0.3 ),
|
|
368
|
-
).mul( throughputWeight );
|
|
369
|
-
|
|
370
|
-
} );
|
|
371
|
-
|
|
372
313
|
// =============================================================================
|
|
373
314
|
// Russian Roulette Path Termination
|
|
374
315
|
// =============================================================================
|
|
@@ -376,7 +317,6 @@ export const estimatePathContribution = Fn( ( [
|
|
|
376
317
|
export const handleRussianRoulette = Fn( ( [
|
|
377
318
|
depth, throughput, material, materialIndex, rayDirection, rngState,
|
|
378
319
|
classificationCached, lastMaterialIndex, cachedClassification,
|
|
379
|
-
weightsComputed, pathImportance,
|
|
380
320
|
enableEnvironmentLight, useEnvMapIS,
|
|
381
321
|
] ) => {
|
|
382
322
|
|
|
@@ -444,22 +384,37 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
444
384
|
|
|
445
385
|
} ).Else( () => {
|
|
446
386
|
|
|
447
|
-
// Path
|
|
448
|
-
const
|
|
387
|
+
// Path contribution estimate — reuses throughputStrength + mc from outer scope.
|
|
388
|
+
const estMaterialImportance = mc.complexityScore.toVar();
|
|
389
|
+
If( mc.isMetallic.and( mc.isSmooth ), () => {
|
|
449
390
|
|
|
450
|
-
|
|
391
|
+
estMaterialImportance.addAssign( 0.15 );
|
|
451
392
|
|
|
452
|
-
|
|
393
|
+
} );
|
|
394
|
+
If( mc.isTransmissive.and( mc.hasClearcoat ), () => {
|
|
453
395
|
|
|
454
|
-
|
|
396
|
+
estMaterialImportance.addAssign( 0.12 );
|
|
455
397
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
) );
|
|
398
|
+
} );
|
|
399
|
+
If( mc.isEmissive, () => {
|
|
400
|
+
|
|
401
|
+
estMaterialImportance.addAssign( 0.1 );
|
|
461
402
|
|
|
462
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();
|
|
463
418
|
|
|
464
419
|
// Smooth adaptive continuation probability (no discrete depth brackets)
|
|
465
420
|
// Early behavior: throughput + material driven, generous
|
|
@@ -516,9 +471,10 @@ export const handleRussianRoulette = Fn( ( [
|
|
|
516
471
|
// =============================================================================
|
|
517
472
|
|
|
518
473
|
export const sampleBackgroundLighting = Fn( ( [
|
|
519
|
-
isPrimaryRay, direction,
|
|
474
|
+
isPrimaryRay, rayOrigin, direction,
|
|
520
475
|
envTexture, envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
521
476
|
showBackground, backgroundIntensity,
|
|
477
|
+
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
522
478
|
] ) => {
|
|
523
479
|
|
|
524
480
|
// Only hide background for primary camera rays when showBackground is false
|
|
@@ -531,8 +487,18 @@ export const sampleBackgroundLighting = Fn( ( [
|
|
|
531
487
|
|
|
532
488
|
} ).Else( () => {
|
|
533
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
|
+
|
|
534
500
|
const sampled = sampleEnvironment( {
|
|
535
|
-
tex: envTexture, samp: sampler( envTexture ), direction, environmentMatrix: envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
501
|
+
tex: envTexture, samp: sampler( envTexture ), direction: effectiveDir, environmentMatrix: envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
536
502
|
} );
|
|
537
503
|
|
|
538
504
|
If( isPrimaryRay, () => {
|
|
@@ -567,7 +533,7 @@ export const regularizePathContribution = /*@__PURE__*/ wgslFn( `
|
|
|
567
533
|
// =============================================================================
|
|
568
534
|
|
|
569
535
|
export const Trace = Fn( ( [
|
|
570
|
-
ray, rngState, rayIndex,
|
|
536
|
+
ray, rngState, rayIndex,
|
|
571
537
|
// BVH / Scene
|
|
572
538
|
bvhBuffer,
|
|
573
539
|
triangleBuffer,
|
|
@@ -586,11 +552,12 @@ export const Trace = Fn( ( [
|
|
|
586
552
|
envCDFBuffer,
|
|
587
553
|
envTotalSum, envCompensationDelta, envResolution,
|
|
588
554
|
enableEnvironmentLight, useEnvMapIS,
|
|
555
|
+
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
589
556
|
// Rendering parameters
|
|
590
557
|
maxBounceCount, transmissiveBounces,
|
|
591
558
|
backgroundIntensity, showBackground, transparentBackground,
|
|
592
559
|
fireflyThreshold, globalIlluminationIntensity,
|
|
593
|
-
|
|
560
|
+
enableEmissiveTriangleSampling,
|
|
594
561
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
|
|
595
562
|
lightBVHBuffer, lightBVHNodeCount,
|
|
596
563
|
// Per-pixel info
|
|
@@ -614,11 +581,25 @@ export const Trace = Fn( ( [
|
|
|
614
581
|
// behind glass), not the glass surface itself.
|
|
615
582
|
const auxLocked = tslBool( false ).toVar();
|
|
616
583
|
|
|
617
|
-
// 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.
|
|
618
587
|
const mediumStackDepth = int( 0 ).toVar();
|
|
619
588
|
const mediumStack_ior_1 = float( 1.0 ).toVar();
|
|
620
589
|
const mediumStack_ior_2 = float( 1.0 ).toVar();
|
|
621
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();
|
|
622
603
|
|
|
623
604
|
// Locked at the first dispersive transmission; reused for subsequent transmissions on
|
|
624
605
|
// the path so multi-bounce dispersion doesn't collapse under repeated colorWeight ×.
|
|
@@ -635,8 +616,6 @@ export const Trace = Fn( ( [
|
|
|
635
616
|
const psWeightsComputed = tslBool( false ).toVar();
|
|
636
617
|
const psClassificationCached = tslBool( false ).toVar();
|
|
637
618
|
const psMaterialCacheCached = tslBool( false ).toVar();
|
|
638
|
-
const psTexturesLoaded = tslBool( false ).toVar();
|
|
639
|
-
const psPathImportance = float( 0.0 ).toVar();
|
|
640
619
|
const psLastMaterialIndex = int( - 1 ).toVar();
|
|
641
620
|
|
|
642
621
|
// Cached classification
|
|
@@ -693,16 +672,41 @@ export const Trace = Fn( ( [
|
|
|
693
672
|
currentRay,
|
|
694
673
|
bvhBuffer,
|
|
695
674
|
triangleBuffer,
|
|
696
|
-
materialBuffer,
|
|
697
675
|
) ).toVar();
|
|
698
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
|
+
|
|
699
702
|
If( hitInfo.didHit.not(), () => {
|
|
700
703
|
|
|
701
704
|
// ENVIRONMENT LIGHTING
|
|
702
705
|
const envColor = sampleBackgroundLighting(
|
|
703
|
-
stateIsPrimaryRay, rayDirection,
|
|
706
|
+
stateIsPrimaryRay, rayOrigin, rayDirection,
|
|
704
707
|
envTexture, envMatrix, environmentIntensity, enableEnvironmentLight,
|
|
705
708
|
showBackground, backgroundIntensity,
|
|
709
|
+
groundProjectionEnabled, groundProjectionRadius, groundProjectionHeight,
|
|
706
710
|
);
|
|
707
711
|
|
|
708
712
|
// MIS weight for implicit environment hit — prevents double-counting with NEE.
|
|
@@ -804,7 +808,7 @@ export const Trace = Fn( ( [
|
|
|
804
808
|
|
|
805
809
|
// Handle transparent materials
|
|
806
810
|
const interaction = MaterialInteractionResult.wrap( handleMaterialTransparency(
|
|
807
|
-
currentRay,
|
|
811
|
+
currentRay, N, material, rngState,
|
|
808
812
|
stateTransmissiveTraversals,
|
|
809
813
|
currentMediumIOR, previousMediumIOR,
|
|
810
814
|
pathWavelength,
|
|
@@ -826,22 +830,39 @@ export const Trace = Fn( ( [
|
|
|
826
830
|
|
|
827
831
|
If( interaction.entering, () => {
|
|
828
832
|
|
|
829
|
-
// Push new medium onto stack
|
|
833
|
+
// Push new medium onto stack (IOR + KHR_materials_volume attenuation)
|
|
830
834
|
If( mediumStackDepth.lessThan( int( 3 ) ), () => {
|
|
831
835
|
|
|
832
836
|
mediumStackDepth.addAssign( 1 );
|
|
833
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
|
+
|
|
834
846
|
If( mediumStackDepth.equal( int( 1 ) ), () => {
|
|
835
847
|
|
|
836
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 );
|
|
837
852
|
|
|
838
853
|
} ).ElseIf( mediumStackDepth.equal( int( 2 ) ), () => {
|
|
839
854
|
|
|
840
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 );
|
|
841
859
|
|
|
842
860
|
} ).ElseIf( mediumStackDepth.equal( int( 3 ) ), () => {
|
|
843
861
|
|
|
844
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 );
|
|
845
866
|
|
|
846
867
|
} );
|
|
847
868
|
|
|
@@ -915,6 +936,19 @@ export const Trace = Fn( ( [
|
|
|
915
936
|
const V = rayDirection.negate().toVar();
|
|
916
937
|
material.sheenRoughness.assign( clamp( material.sheenRoughness, MIN_ROUGHNESS, MAX_ROUGHNESS ) );
|
|
917
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
|
+
|
|
918
952
|
// Create material cache if needed
|
|
919
953
|
If( psMaterialCacheCached.not(), () => {
|
|
920
954
|
|
|
@@ -940,9 +974,12 @@ export const Trace = Fn( ( [
|
|
|
940
974
|
|
|
941
975
|
} ).Else( () => {
|
|
942
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.
|
|
943
980
|
const brdfSample = DirectionSample.wrap( generateSampledDirection(
|
|
944
|
-
V, N, material,
|
|
945
|
-
|
|
981
|
+
V, N, material, randomSample, rngState,
|
|
982
|
+
psCachedClassification,
|
|
946
983
|
psWeightsComputed, psCachedBrdfWeights,
|
|
947
984
|
psMaterialCacheCached, psCachedMaterialCache,
|
|
948
985
|
) );
|
|
@@ -950,22 +987,6 @@ export const Trace = Fn( ( [
|
|
|
950
987
|
brdfValue.assign( brdfSample.value );
|
|
951
988
|
brdfPdf.assign( brdfSample.pdf );
|
|
952
989
|
|
|
953
|
-
// Sync psCachedClassification for downstream consumers (importance sampling, Russian roulette).
|
|
954
|
-
// generateSampledDirection computed the correct classification internally via materialIndex
|
|
955
|
-
// guard, but TSL Fn can't write back to the caller's variable — update it here.
|
|
956
|
-
If( psLastMaterialIndex.notEqual( hitInfo.materialIndex ).or( psClassificationCached.not() ), () => {
|
|
957
|
-
|
|
958
|
-
psCachedClassification.assign( classifyMaterial(
|
|
959
|
-
material.metalness, material.roughness,
|
|
960
|
-
material.transmission, material.clearcoat,
|
|
961
|
-
material.emissive,
|
|
962
|
-
) );
|
|
963
|
-
|
|
964
|
-
} );
|
|
965
|
-
|
|
966
|
-
// Update cache state after generateSampledDirection
|
|
967
|
-
psClassificationCached.assign( tslBool( true ) );
|
|
968
|
-
psLastMaterialIndex.assign( hitInfo.materialIndex );
|
|
969
990
|
psWeightsComputed.assign( tslBool( true ) );
|
|
970
991
|
|
|
971
992
|
} );
|
|
@@ -1003,7 +1024,7 @@ export const Trace = Fn( ( [
|
|
|
1003
1024
|
hitInfo.hitPoint, N, material,
|
|
1004
1025
|
V,
|
|
1005
1026
|
brdfDir, brdfPdf, brdfValue,
|
|
1006
|
-
|
|
1027
|
+
bounceIndex, rngState,
|
|
1007
1028
|
directionalLightsBuffer, numDirectionalLights,
|
|
1008
1029
|
areaLightsBuffer, numAreaLights,
|
|
1009
1030
|
pointLightsBuffer, numPointLights,
|
|
@@ -1024,10 +1045,9 @@ export const Trace = Fn( ( [
|
|
|
1024
1045
|
// 2b. EMISSIVE TRIANGLE DIRECT LIGHTING
|
|
1025
1046
|
If( enableEmissiveTriangleSampling.equal( int( 1 ) ).and( emissiveTriangleCount.greaterThan( int( 0 ) ) ), () => {
|
|
1026
1047
|
|
|
1027
|
-
|
|
1028
|
-
const traceShadowRayWrapped = Fn( ( [ origin, dir, maxDist, rs ] ) => {
|
|
1048
|
+
const traceShadowRayWrapped = Fn( ( [ origin, dir, maxDist ] ) => {
|
|
1029
1049
|
|
|
1030
|
-
return traceShadowRay( origin, dir, maxDist,
|
|
1050
|
+
return traceShadowRay( origin, dir, maxDist, traverseBVHShadow, bvhBuffer, triangleBuffer, materialBuffer );
|
|
1031
1051
|
|
|
1032
1052
|
} );
|
|
1033
1053
|
|
|
@@ -1057,12 +1077,14 @@ export const Trace = Fn( ( [
|
|
|
1057
1077
|
const rayOffset = calculateRayOffset( hitInfo.hitPoint, N, material );
|
|
1058
1078
|
const rayOrigin = hitInfo.hitPoint.add( rayOffset );
|
|
1059
1079
|
const shadowDist = emissiveSample.distance.sub( 0.001 );
|
|
1060
|
-
const visibility = traceShadowRayWrapped( rayOrigin, emissiveSample.direction, shadowDist
|
|
1080
|
+
const visibility = traceShadowRayWrapped( rayOrigin, emissiveSample.direction, shadowDist );
|
|
1061
1081
|
|
|
1062
1082
|
If( visibility.greaterThan( 0.0 ), () => {
|
|
1063
1083
|
|
|
1064
|
-
|
|
1065
|
-
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 );
|
|
1066
1088
|
const misWeight = select(
|
|
1067
1089
|
brdfPdf.greaterThan( 0.0 ),
|
|
1068
1090
|
powerHeuristic( { pdf1: emissiveSample.pdf, pdf2: brdfPdf } ),
|
|
@@ -1089,12 +1111,11 @@ export const Trace = Fn( ( [
|
|
|
1089
1111
|
// Fallback: flat CDF importance sampling
|
|
1090
1112
|
const emissiveLight = calculateEmissiveTriangleContribution(
|
|
1091
1113
|
hitInfo.hitPoint, N, V, material,
|
|
1092
|
-
|
|
1114
|
+
bounceIndex, rngState,
|
|
1093
1115
|
emissiveBoost,
|
|
1094
1116
|
emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
|
|
1095
1117
|
triangleBuffer,
|
|
1096
1118
|
traceShadowRayWrapped,
|
|
1097
|
-
evaluateMaterialResponse,
|
|
1098
1119
|
calculateRayOffset,
|
|
1099
1120
|
);
|
|
1100
1121
|
|
|
@@ -1106,34 +1127,19 @@ export const Trace = Fn( ( [
|
|
|
1106
1127
|
|
|
1107
1128
|
} );
|
|
1108
1129
|
|
|
1109
|
-
//
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
// Update classification first
|
|
1113
|
-
psCachedClassification.assign( MaterialClassification.wrap( getOrCreateMaterialClassification(
|
|
1114
|
-
material, hitInfo.materialIndex,
|
|
1115
|
-
psClassificationCached, psLastMaterialIndex, psCachedClassification,
|
|
1116
|
-
) ) );
|
|
1117
|
-
psClassificationCached.assign( tslBool( true ) );
|
|
1118
|
-
psLastMaterialIndex.assign( hitInfo.materialIndex );
|
|
1119
|
-
|
|
1120
|
-
} );
|
|
1121
|
-
|
|
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.
|
|
1122
1133
|
const samplingInfo = ImportanceSamplingInfo.wrap( getImportanceSamplingInfo(
|
|
1123
1134
|
material, bounceIndex, psCachedClassification,
|
|
1124
|
-
environmentIntensity, useEnvMapIS, enableEnvironmentLight,
|
|
1125
1135
|
) );
|
|
1126
1136
|
|
|
1127
1137
|
// 3. INDIRECT LIGHTING
|
|
1128
1138
|
const indirectResult = IndirectLightingResult.wrap( calculateIndirectLighting(
|
|
1129
1139
|
V, N, material,
|
|
1130
1140
|
brdfDir, brdfPdf, brdfValue,
|
|
1131
|
-
rayIndex, bounceIndex,
|
|
1132
1141
|
rngState,
|
|
1133
1142
|
samplingInfo,
|
|
1134
|
-
envTexture, environmentIntensity, envMatrix,
|
|
1135
|
-
envTotalSum, envCompensationDelta, envResolution,
|
|
1136
|
-
enableEnvironmentLight, useEnvMapIS,
|
|
1137
1143
|
) );
|
|
1138
1144
|
throughput.mulAssign( indirectResult.throughput );
|
|
1139
1145
|
|
|
@@ -1192,7 +1198,6 @@ export const Trace = Fn( ( [
|
|
|
1192
1198
|
bounceIndex, throughput, material, hitInfo.materialIndex,
|
|
1193
1199
|
rayDirection, rngState,
|
|
1194
1200
|
psClassificationCached, psLastMaterialIndex, psCachedClassification,
|
|
1195
|
-
psWeightsComputed, psPathImportance,
|
|
1196
1201
|
enableEnvironmentLight, useEnvMapIS,
|
|
1197
1202
|
);
|
|
1198
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
|
|