rayzee 5.10.2 → 6.0.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.
Files changed (43) hide show
  1. package/README.md +82 -22
  2. package/dist/assets/AIUpscalerWorker-AXN-lKWN.js +2 -0
  3. package/dist/assets/AIUpscalerWorker-AXN-lKWN.js.map +1 -0
  4. package/dist/rayzee.es.js +1299 -1843
  5. package/dist/rayzee.es.js.map +1 -1
  6. package/dist/rayzee.umd.js +50 -74
  7. package/dist/rayzee.umd.js.map +1 -1
  8. package/package.json +1 -4
  9. package/src/AssetConfig.js +56 -0
  10. package/src/EngineDefaults.js +5 -3
  11. package/src/EngineEvents.js +1 -0
  12. package/src/Passes/AIUpscaler.js +44 -22
  13. package/src/Passes/OIDNDenoiser.js +4 -104
  14. package/src/PathTracerApp.js +59 -63
  15. package/src/Processor/AssetLoader.js +5 -2
  16. package/src/Processor/TextureCreator.js +42 -15
  17. package/src/Processor/Workers/AIUpscalerWorker.js +21 -6
  18. package/src/Stages/ASVGF.js +6 -27
  19. package/src/Stages/AdaptiveSampling.js +10 -26
  20. package/src/Stages/PathTracer.js +4 -5
  21. package/src/TSL/BVHTraversal.js +2 -18
  22. package/src/TSL/Clearcoat.js +1 -2
  23. package/src/TSL/Common.js +0 -13
  24. package/src/TSL/EmissiveSampling.js +0 -16
  25. package/src/TSL/Environment.js +0 -7
  26. package/src/TSL/LightsDirect.js +3 -379
  27. package/src/TSL/LightsSampling.js +0 -171
  28. package/src/TSL/MaterialEvaluation.js +3 -103
  29. package/src/TSL/MaterialProperties.js +1 -56
  30. package/src/TSL/MaterialSampling.js +2 -284
  31. package/src/TSL/MaterialTransmission.js +0 -93
  32. package/src/TSL/Random.js +0 -23
  33. package/src/TSL/Struct.js +0 -21
  34. package/src/TSL/TextureSampling.js +0 -69
  35. package/src/index.js +5 -2
  36. package/src/managers/DenoisingManager.js +13 -5
  37. package/src/managers/OverlayManager.js +14 -2
  38. package/src/managers/VideoRenderManager.js +4 -4
  39. package/dist/assets/AIUpscalerWorker-D58dcMrY.js +0 -2
  40. package/dist/assets/AIUpscalerWorker-D58dcMrY.js.map +0 -1
  41. package/src/Processor/createRenderTargetHelper.js +0 -521
  42. package/src/TSL/RayIntersection.js +0 -162
  43. package/src/managers/helpers/StatsHelper.js +0 -45
@@ -4,9 +4,6 @@
4
4
 
5
5
  import {
6
6
  Fn,
7
- vec2,
8
- vec3,
9
- vec4,
10
7
  float,
11
8
  int,
12
9
  bool as tslBool,
@@ -14,13 +11,11 @@ import {
14
11
  Loop,
15
12
  Break,
16
13
  dot,
17
- normalize,
18
14
  abs,
19
15
  max,
20
16
  min,
21
17
  length,
22
18
  sqrt,
23
- pow,
24
19
  cos,
25
20
  clamp,
26
21
  smoothstep,
@@ -29,14 +24,9 @@ import {
29
24
  } from 'three/tsl';
30
25
 
31
26
  import { Ray, ShadowMaterial, HitInfo } from './Struct.js';
32
- import { PI, TWO_PI, EPSILON, REC709_LUMINANCE_COEFFICIENTS, powerHeuristic, getShadowMaterial, getDatafromStorageBuffer } from './Common.js';
33
- import { fresnelSchlickFloat } from './Fresnel.js';
34
- import { iorToFresnel0 } from './Fresnel.js';
35
- import {
36
- sampleCone, intersectAreaLight,
37
- } from './LightsCore.js';
38
- import { calculateBeerLawAbsorption, calculateShadowTransmittance } from './MaterialTransmission.js';
39
- import { RandomValue } from './Random.js';
27
+ import { REC709_LUMINANCE_COEFFICIENTS, getShadowMaterial, getDatafromStorageBuffer } from './Common.js';
28
+ import { fresnelSchlickFloat, iorToFresnel0 } from './Fresnel.js';
29
+ import { calculateBeerLawAbsorption } from './MaterialTransmission.js';
40
30
  import { getTransformedUV } from './TextureSampling.js';
41
31
 
42
32
  // Module-level state for alpha-cutout shadow testing.
@@ -65,31 +55,6 @@ export function setAlphaShadowsUniform( node ) {
65
55
 
66
56
  }
67
57
 
68
- // ================================================================================
69
- // SHADOW RAY MATERIAL TRANSPARENCY
70
- // ================================================================================
71
-
72
- export const getMaterialTransparency = Fn( ( [ shadowHit, shadowRayDir, rngState ] ) => {
73
-
74
- const result = float( 1.0 ).toVar();
75
-
76
- // Check if the material has transmission (like glass)
77
- If( shadowHit.material.transmission.greaterThan( 0.0 ), () => {
78
-
79
- const isEntering = dot( shadowRayDir, shadowHit.normal ).lessThan( 0.0 );
80
- const transmittance = calculateShadowTransmittance( shadowRayDir, shadowHit.normal, shadowHit.material, isEntering );
81
- result.assign( float( 1.0 ).sub( transmittance ) );
82
-
83
- } ).ElseIf( shadowHit.material.transparent, () => {
84
-
85
- result.assign( shadowHit.material.opacity );
86
-
87
- } );
88
-
89
- return result;
90
-
91
- } );
92
-
93
58
  // ================================================================================
94
59
  // SHADOW RAY TRACING
95
60
  // ================================================================================
@@ -499,344 +464,3 @@ export const calculateSpotLightImportance = Fn( ( [ light, hitPoint, normal, mat
499
464
 
500
465
  } );
501
466
 
502
- // ================================================================================
503
- // DIRECTIONAL LIGHT CONTRIBUTION
504
- // ================================================================================
505
-
506
- export const shouldSkipDirectionalLight = Fn( ( [ light, normal, material, bounceIndex ] ) => {
507
-
508
- const NoL = max( float( 0.0 ), dot( normal, light.direction ) );
509
- const shouldSkip = tslBool( false ).toVar();
510
-
511
- If( light.intensity.lessThanEqual( 0.001 ).or( NoL.lessThanEqual( 0.001 ) ), () => {
512
-
513
- shouldSkip.assign( true );
514
-
515
- } );
516
-
517
- If( shouldSkip.not().and( bounceIndex.greaterThan( int( 0 ) ) ), () => {
518
-
519
- If( light.intensity.lessThan( 0.01 ), () => {
520
-
521
- shouldSkip.assign( true );
522
-
523
- } );
524
-
525
- If( shouldSkip.not().and( material.metalness.greaterThan( 0.9 ) ).and( NoL.lessThan( 0.1 ) ), () => {
526
-
527
- shouldSkip.assign( true );
528
-
529
- } );
530
-
531
- If( shouldSkip.not().and( material.metalness.lessThan( 0.1 ) ).and( material.roughness.greaterThan( 0.9 ) ).and( light.intensity.lessThan( 0.1 ) ), () => {
532
-
533
- shouldSkip.assign( true );
534
-
535
- } );
536
-
537
- } );
538
-
539
- return shouldSkip;
540
-
541
- } );
542
-
543
- export const calculateDirectionalLightContribution = Fn( ( [
544
- light, hitPoint, normal, viewDir, material, matCache, brdfSample, bounceIndex,
545
- rngState,
546
- // Shadow tracing callback + textures
547
- traceShadowRayFn,
548
- evaluateMaterialResponseCachedFn,
549
- ] ) => {
550
-
551
- const result = vec3( 0.0 ).toVar();
552
-
553
- If( shouldSkipDirectionalLight( light, normal, material, bounceIndex ).not(), () => {
554
-
555
- const rayOffset = calculateRayOffset( hitPoint, normal, material );
556
- const rayOrigin = hitPoint.add( rayOffset );
557
-
558
- // Determine shadow sampling strategy based on light angle
559
- const shadowDirection = vec3( 0.0 ).toVar();
560
- const lightPdf = float( 1e6 ).toVar();
561
-
562
- If( light.angle.greaterThan( 0.001 ), () => {
563
-
564
- // Soft shadows: sample direction within cone
565
- const xi_r1 = RandomValue( rngState ).toVar();
566
- const xi_r2 = RandomValue( rngState ).toVar();
567
- const xi = vec2( xi_r1, xi_r2 );
568
- const halfAngle = light.angle.mul( 0.5 );
569
- shadowDirection.assign( sampleCone( { direction: light.direction, halfAngle, xi } ) );
570
-
571
- // Calculate PDF for cone sampling
572
- const cosHalfAngle = cos( halfAngle );
573
- lightPdf.assign( float( 1.0 ).div( TWO_PI.mul( float( 1.0 ).sub( cosHalfAngle ) ) ) );
574
-
575
- } ).Else( () => {
576
-
577
- shadowDirection.assign( light.direction );
578
-
579
- } );
580
-
581
- const NoL = max( float( 0.0 ), dot( normal, shadowDirection ) );
582
-
583
- If( NoL.greaterThan( 0.0 ), () => {
584
-
585
- const maxShadowDistance = float( 1e6 );
586
- const visibility = traceShadowRayFn( rayOrigin, shadowDirection, maxShadowDistance, rngState );
587
-
588
- If( visibility.greaterThan( 0.0 ), () => {
589
-
590
- const brdfValue = evaluateMaterialResponseCachedFn( viewDir, shadowDirection, normal, material, matCache );
591
- const lightRadiance = light.color.mul( light.intensity );
592
- const contribution = lightRadiance.mul( brdfValue ).mul( NoL ).mul( visibility );
593
-
594
- // MIS for directional lights (only on primary rays)
595
- If( bounceIndex.equal( int( 0 ) ).and( brdfSample.pdf.greaterThan( 0.0 ) ), () => {
596
-
597
- const alignment = max( float( 0.0 ), dot( normalize( brdfSample.direction ), shadowDirection ) );
598
-
599
- If( alignment.greaterThan( 0.996 ), () => {
600
-
601
- const misWeight = powerHeuristic( { pdf1: lightPdf, pdf2: brdfSample.pdf } );
602
- result.assign( contribution.mul( misWeight ) );
603
-
604
- } ).Else( () => {
605
-
606
- result.assign( contribution );
607
-
608
- } );
609
-
610
- } ).Else( () => {
611
-
612
- result.assign( contribution );
613
-
614
- } );
615
-
616
- } );
617
-
618
- } );
619
-
620
- } );
621
-
622
- return result;
623
-
624
- } );
625
-
626
- // ================================================================================
627
- // AREA LIGHT CONTRIBUTION
628
- // ================================================================================
629
-
630
- export const calculateAreaLightContribution = Fn( ( [
631
- light, hitPoint, normal, viewDir, material, matCache, brdfSample,
632
- sampleIndex, bounceIndex, rngState,
633
- traceShadowRayFn,
634
- evaluateMaterialResponseCachedFn,
635
- getRandomSampleFn,
636
- ] ) => {
637
-
638
- const contribution = vec3( 0.0 ).toVar();
639
-
640
- const lightImportance = estimateLightImportance( light, hitPoint, normal, material );
641
-
642
- If( lightImportance.greaterThanEqual( 0.001 ), () => {
643
-
644
- const rayOffset = calculateRayOffset( hitPoint, normal, material );
645
- const rayOrigin = hitPoint.add( rayOffset );
646
-
647
- const isDiffuse = material.roughness.greaterThan( 0.7 ).and( material.metalness.lessThan( 0.3 ) );
648
- const isSpecular = material.roughness.lessThan( 0.3 ).or( material.metalness.greaterThan( 0.7 ) );
649
- const isFirstBounce = bounceIndex.equal( int( 0 ) );
650
-
651
- // LIGHT SAMPLING STRATEGY
652
- If( isFirstBounce.or( isDiffuse ).or( lightImportance.greaterThan( 0.1 ).and( isSpecular.not() ) ), () => {
653
-
654
- const ruv = getRandomSampleFn( sampleIndex, bounceIndex, rngState );
655
-
656
- // Generate position on light surface
657
- const lightPos = light.position
658
- .add( light.u.mul( ruv.x.sub( 0.5 ) ) )
659
- .add( light.v.mul( ruv.y.sub( 0.5 ) ) );
660
-
661
- const toLight = lightPos.sub( hitPoint );
662
- const lightDistSq = dot( toLight, toLight );
663
- const lightDist = sqrt( lightDistSq );
664
- const lightDir = toLight.div( lightDist );
665
-
666
- const NoL = max( float( 0.0 ), dot( normal, lightDir ) );
667
- const lightFacing = max( float( 0.0 ), dot( lightDir, light.normal ).negate() );
668
-
669
- If( NoL.greaterThan( 0.0 ).and( lightFacing.greaterThan( 0.0 ) ), () => {
670
-
671
- const visibility = traceShadowRayFn( rayOrigin, lightDir, lightDist, rngState );
672
-
673
- If( visibility.greaterThan( 0.0 ), () => {
674
-
675
- const brdfValue = evaluateMaterialResponseCachedFn( viewDir, lightDir, normal, material, matCache );
676
-
677
- // Calculate PDFs for MIS
678
- const lightPdf = lightDistSq.div( max( light.area.mul( lightFacing ), EPSILON ) );
679
- const brdfPdf = brdfSample.pdf;
680
-
681
- // Light contribution with inverse-square falloff
682
- const falloff = light.area.div( float( 4.0 ).mul( PI ).mul( lightDistSq ) );
683
- const lightContribution = light.color.mul( light.intensity ).mul( falloff ).mul( lightFacing );
684
-
685
- // MIS weight
686
- const misWeight = select(
687
- brdfPdf.greaterThan( 0.0 ).and( isFirstBounce ),
688
- powerHeuristic( { pdf1: lightPdf, pdf2: brdfPdf } ),
689
- float( 1.0 ),
690
- );
691
-
692
- contribution.addAssign( lightContribution.mul( brdfValue ).mul( NoL ).mul( visibility ).mul( misWeight ) );
693
-
694
- } );
695
-
696
- } );
697
-
698
- } );
699
-
700
- // BRDF SAMPLING STRATEGY
701
- If( isFirstBounce.or( isSpecular ).and( brdfSample.pdf.greaterThan( 0.0 ) ), () => {
702
-
703
- const toLight = light.position.sub( rayOrigin );
704
- const rayToLightDot = dot( toLight, brdfSample.direction );
705
-
706
- If( rayToLightDot.greaterThan( 0.0 ), () => {
707
-
708
- const hitDistance = intersectAreaLight( light, rayOrigin, brdfSample.direction );
709
-
710
- If( hitDistance.greaterThan( 0.0 ), () => {
711
-
712
- const visibility = traceShadowRayFn( rayOrigin, brdfSample.direction, hitDistance, rngState );
713
-
714
- If( visibility.greaterThan( 0.0 ), () => {
715
-
716
- const lightFacing = max( float( 0.0 ), dot( brdfSample.direction, light.normal ).negate() );
717
-
718
- If( lightFacing.greaterThan( 0.0 ), () => {
719
-
720
- const lightPdf = hitDistance.mul( hitDistance ).div( max( light.area.mul( lightFacing ), EPSILON ) );
721
- const misWeight = powerHeuristic( { pdf1: brdfSample.pdf, pdf2: lightPdf } );
722
-
723
- const lightEmission = light.color.mul( light.intensity );
724
- const NoL = max( float( 0.0 ), dot( normal, brdfSample.direction ) );
725
-
726
- contribution.addAssign( lightEmission.mul( brdfSample.value ).mul( NoL ).mul( visibility ).mul( misWeight ) );
727
-
728
- } );
729
-
730
- } );
731
-
732
- } );
733
-
734
- } );
735
-
736
- } );
737
-
738
- } );
739
-
740
- return contribution;
741
-
742
- } );
743
-
744
- // ================================================================================
745
- // POINT LIGHT CONTRIBUTION
746
- // ================================================================================
747
-
748
- export const calculatePointLightContribution = Fn( ( [
749
- light, hitPoint, normal, viewDir, material, matCache, brdfSample, bounceIndex,
750
- rngState,
751
- traceShadowRayFn,
752
- evaluateMaterialResponseFn,
753
- ] ) => {
754
-
755
- const result = vec3( 0.0 ).toVar();
756
-
757
- const toLight = light.position.sub( hitPoint );
758
- const distance = length( toLight );
759
-
760
- If( distance.lessThanEqual( 1000.0 ), () => {
761
-
762
- const lightDir = toLight.div( distance );
763
- const NdotL = dot( normal, lightDir );
764
-
765
- If( NdotL.greaterThan( 0.0 ), () => {
766
-
767
- const attenuation = float( 1.0 ).div( distance.mul( distance ) );
768
- const lightRadiance = light.color.mul( light.intensity ).mul( attenuation );
769
-
770
- const rayOffset = calculateRayOffset( hitPoint, normal, material );
771
- const rayOrigin = hitPoint.add( rayOffset );
772
-
773
- const visibility = traceShadowRayFn( rayOrigin, lightDir, distance.sub( 0.001 ), rngState );
774
-
775
- If( visibility.greaterThan( 0.0 ), () => {
776
-
777
- const brdfValue = evaluateMaterialResponseFn( viewDir, lightDir, normal, material );
778
- result.assign( brdfValue.mul( lightRadiance ).mul( NdotL ).mul( visibility ) );
779
-
780
- } );
781
-
782
- } );
783
-
784
- } );
785
-
786
- return result;
787
-
788
- } );
789
-
790
- // ================================================================================
791
- // SPOT LIGHT CONTRIBUTION
792
- // ================================================================================
793
-
794
- export const calculateSpotLightContribution = Fn( ( [
795
- light, hitPoint, normal, viewDir, material, matCache, brdfSample, bounceIndex,
796
- rngState,
797
- traceShadowRayFn,
798
- evaluateMaterialResponseFn,
799
- ] ) => {
800
-
801
- const result = vec3( 0.0 ).toVar();
802
-
803
- const toLight = light.position.sub( hitPoint );
804
- const distance = length( toLight );
805
-
806
- If( distance.lessThanEqual( 1000.0 ), () => {
807
-
808
- const lightDir = toLight.div( distance );
809
- const NdotL = dot( normal, lightDir );
810
-
811
- If( NdotL.greaterThan( 0.0 ), () => {
812
-
813
- const spotCosAngle = dot( lightDir.negate(), light.direction );
814
- const coneCosAngle = cos( light.angle );
815
-
816
- If( spotCosAngle.greaterThanEqual( coneCosAngle ), () => {
817
-
818
- const coneAttenuation = smoothstep( coneCosAngle, coneCosAngle.add( 0.1 ), spotCosAngle );
819
- const distanceAttenuation = float( 1.0 ).div( distance.mul( distance ) );
820
- const lightRadiance = light.color.mul( light.intensity ).mul( distanceAttenuation ).mul( coneAttenuation );
821
-
822
- const rayOffset = calculateRayOffset( hitPoint, normal, material );
823
- const rayOrigin = hitPoint.add( rayOffset );
824
-
825
- const visibility = traceShadowRayFn( rayOrigin, lightDir, distance.sub( 0.001 ), rngState );
826
-
827
- If( visibility.greaterThan( 0.0 ), () => {
828
-
829
- const brdfValue = evaluateMaterialResponseFn( viewDir, lightDir, normal, material );
830
- result.assign( brdfValue.mul( lightRadiance ).mul( NdotL ).mul( visibility ) );
831
-
832
- } );
833
-
834
- } );
835
-
836
- } );
837
-
838
- } );
839
-
840
- return result;
841
-
842
- } );
@@ -8,14 +8,11 @@
8
8
  * - Deterministic environment NEE (always runs, two-strategy Veach MIS with implicit miss)
9
9
  *
10
10
  * Contains:
11
- * - initLightSample — fully initialize LightSample
12
11
  * - sampleRectAreaLight — rectangle area light sampling
13
- * - sampleCircAreaLight — circle area light sampling
14
12
  * - sampleSpotLightWithRadius — spot light sampling with radius
15
13
  * - samplePointLightWithAttenuation — point light sampling with attenuation
16
14
  * - sampleLightWithImportance — importance-weighted light selection (3-pass)
17
15
  * - calculateMaterialPDF — material PDF for MIS
18
- * - sampleAreaLightContribution — area light with MIS
19
16
  * - calculateDirectLightingUnified — unified direct lighting (main entry)
20
17
  */
21
18
 
@@ -93,23 +90,6 @@ import {
93
90
 
94
91
  const TWO_PI = 2.0 * PI;
95
92
 
96
- // =============================================================================
97
- // Helper: Fully Initialize LightSample
98
- // =============================================================================
99
-
100
- export const initLightSample = Fn( () => {
101
-
102
- return LightSample( {
103
- valid: tslBool( false ),
104
- direction: vec3( 0.0, 1.0, 0.0 ),
105
- emission: vec3( 0.0 ),
106
- distance: float( 0.0 ),
107
- pdf: float( 0.0 ),
108
- lightType: int( LIGHT_TYPE_POINT ),
109
- } );
110
-
111
- } );
112
-
113
93
  // =============================================================================
114
94
  // Light Sampling Functions
115
95
  // =============================================================================
@@ -170,66 +150,6 @@ export const sampleRectAreaLight = Fn( ( [ light, rayOrigin, ruv, lightSelection
170
150
 
171
151
  } );
172
152
 
173
- // Enhanced area light sampling - circle
174
- export const sampleCircAreaLight = Fn( ( [ light, rayOrigin, ruv, lightSelectionPdf ] ) => {
175
-
176
- const ls_valid = tslBool( false ).toVar();
177
- const ls_direction = vec3( 0.0, 1.0, 0.0 ).toVar();
178
- const ls_emission = vec3( 0.0 ).toVar();
179
- const ls_distance = float( 0.0 ).toVar();
180
- const ls_pdf = float( 0.0 ).toVar();
181
- const ls_lightType = int( LIGHT_TYPE_POINT ).toVar();
182
-
183
- // Validate light area to prevent NaN
184
- If( light.area.greaterThan( 0.0 ), () => {
185
-
186
- // Sample random position on circle
187
- const r = float( 0.5 ).mul( sqrt( ruv.x ) ).toVar();
188
- const theta = ruv.y.mul( TWO_PI ).toVar();
189
- const x = r.mul( cos( theta ) ).toVar();
190
- const y = r.mul( sin( theta ) ).toVar();
191
-
192
- const randomPos = light.position
193
- .add( light.u.mul( x ) )
194
- .add( light.v.mul( y ) )
195
- .toVar();
196
-
197
- const toLight = randomPos.sub( rayOrigin ).toVar();
198
- const lightDistSq = dot( toLight, toLight ).toVar();
199
-
200
- // Guard against zero distance
201
- If( lightDistSq.greaterThanEqual( 1e-10 ), () => {
202
-
203
- const dist = sqrt( lightDistSq ).toVar();
204
- const direction = toLight.div( dist ).toVar();
205
- const lightNormal = normalize( cross( light.u, light.v ) ).toVar();
206
- const cosAngle = dot( direction.negate(), lightNormal ).toVar();
207
-
208
- ls_lightType.assign( int( LIGHT_TYPE_AREA ) );
209
- ls_emission.assign( light.color.mul( light.intensity ) );
210
- ls_distance.assign( dist );
211
- ls_direction.assign( direction );
212
- // Guard division
213
- ls_pdf.assign(
214
- lightDistSq.div( max( light.area.mul( max( cosAngle, 0.001 ) ), 1e-10 ) ).mul( lightSelectionPdf )
215
- );
216
- ls_valid.assign( cosAngle.greaterThan( 0.0 ) );
217
-
218
- } );
219
-
220
- } );
221
-
222
- return LightSample( {
223
- valid: ls_valid,
224
- direction: ls_direction,
225
- emission: ls_emission,
226
- distance: ls_distance,
227
- pdf: ls_pdf,
228
- lightType: ls_lightType,
229
- } );
230
-
231
- } );
232
-
233
153
  // Enhanced spot light sampling with radius support
234
154
  export const sampleSpotLightWithRadius = Fn( ( [ light, rayOrigin, ruv, lightSelectionPdf ] ) => {
235
155
 
@@ -819,97 +739,6 @@ export const calculateMaterialPDF = Fn( ( [ viewDir, lightDir, normal, material
819
739
 
820
740
  } );
821
741
 
822
- // =============================================================================
823
- // Enhanced Area Light Sampling with MIS
824
- // =============================================================================
825
-
826
- export const sampleAreaLightContribution = Fn( ( [
827
- light,
828
- worldWo,
829
- hitPoint, hitNormal, material,
830
- rayOrigin,
831
- bounceIndex,
832
- rngState,
833
- // Shadow ray resources
834
- bvhBuffer,
835
- triangleBuffer,
836
- materialBuffer,
837
- ] ) => {
838
-
839
- const result = vec3( 0.0 ).toVar();
840
-
841
- // Sample random position on light surface
842
- const ruv_r1 = RandomValue( rngState ).toVar();
843
- const ruv_r2 = RandomValue( rngState ).toVar();
844
- const ruv = vec2( ruv_r1, ruv_r2 ).toVar();
845
- const lightPos = light.position.add( light.u.mul( ruv.x.mul( 2.0 ).sub( 1.0 ) ) ).add( light.v.mul( ruv.y.mul( 2.0 ).sub( 1.0 ) ) ).toVar();
846
-
847
- const toLight = lightPos.sub( rayOrigin ).toVar();
848
- const lightDistSq = dot( toLight, toLight ).toVar();
849
-
850
- // Guard against zero distance
851
- If( lightDistSq.greaterThanEqual( 1e-10 ), () => {
852
-
853
- const lightDist = sqrt( lightDistSq ).toVar();
854
- const lightDir = toLight.div( lightDist ).toVar();
855
-
856
- // Check if light is facing the surface
857
- const lightNormal = normalize( cross( light.u, light.v ) ).toVar();
858
- const lightFacing = dot( lightDir.negate(), lightNormal ).toVar();
859
-
860
- If( lightFacing.greaterThan( 0.0 ), () => {
861
-
862
- // Check if surface is facing the light
863
- const surfaceFacing = dot( hitNormal, lightDir ).toVar();
864
-
865
- If( surfaceFacing.greaterThan( 0.0 ), () => {
866
-
867
- // Validate direction
868
- If( isDirectionValid( { direction: lightDir, surfaceNormal: hitNormal } ), () => {
869
-
870
- // Test for occlusion
871
- const visibility = traceShadowRay(
872
- rayOrigin, lightDir, lightDist.sub( 0.001 ), rngState,
873
- traverseBVHShadow,
874
- bvhBuffer,
875
- triangleBuffer,
876
- materialBuffer,
877
- );
878
-
879
- If( visibility.greaterThan( 0.0 ), () => {
880
-
881
- // Calculate BRDF
882
- const brdfColor = evaluateMaterialResponse( worldWo, lightDir, hitNormal, material );
883
-
884
- // Calculate light PDF - guard division
885
- const lightPdf = lightDistSq.div( max( light.area.mul( lightFacing ), EPSILON ) ).toVar();
886
-
887
- // Calculate BRDF PDF for MIS
888
- const brdfPdf = calculateMaterialPDF( worldWo, lightDir, hitNormal, material ).toVar();
889
-
890
- // Apply MIS weighting
891
- const misWeight = select( brdfPdf.greaterThan( 0.0 ), powerHeuristic( { pdf1: lightPdf, pdf2: brdfPdf } ), float( 1.0 ) ).toVar();
892
-
893
- // Calculate final contribution - guard division
894
- const lightEmission = light.color.mul( light.intensity ).toVar();
895
- result.assign(
896
- lightEmission.mul( brdfColor ).mul( surfaceFacing ).mul( visibility ).mul( misWeight ).div( max( lightPdf, MIN_PDF ) )
897
- );
898
-
899
- } );
900
-
901
- } );
902
-
903
- } );
904
-
905
- } );
906
-
907
- } );
908
-
909
- return result;
910
-
911
- } );
912
-
913
742
  // =============================================================================
914
743
  // Unified Direct Lighting System
915
744
  // =============================================================================