rayzee 9.5.0 → 11.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 (78) hide show
  1. package/README.md +22 -20
  2. package/dist/{ArchiveImporter-xuUJiklP.js → ArchiveImporter-BRQvLggb.js} +12 -13
  3. package/dist/{BidirectionalIntegrator-C5PKjJoT.js → BidirectionalIntegrator-9lZQxFVH.js} +87 -87
  4. package/dist/BufferLayout-D_IBo2ci.js +150 -0
  5. package/dist/{ColorManagement-CheBCPaW.js → ColorManagement-oeEk7oSu.js} +171 -171
  6. package/dist/{NeuralRetouch-CW11yLZ_.js → NeuralRetouch-BCXylThH.js} +3 -3
  7. package/dist/{NeuralSuperRes-BekaecYg.js → NeuralSuperRes-sZAeS142.js} +3 -3
  8. package/dist/{PathTracer-Ccl9KOuF.js → PathTracer-CQPW06Mm.js} +2911 -2669
  9. package/dist/{PhysicalSky-Dv2PMFGW.js → PhysicalSky-BvqCw_dy.js} +2 -2
  10. package/dist/{RayzeeRenderer-BQxnzCTD.js → RayzeeRenderer-COpwotpU.js} +338 -329
  11. package/dist/{SolarLimb-ken-JbDK.js → SolarLimb-CPaeZVwz.js} +10 -10
  12. package/dist/{ToneMapGPU-BHi5JzIq.js → ToneMapGPU-CjU3osDd.js} +1 -1
  13. package/dist/{ViewTransforms-D0rTLBsX.js → ViewTransforms-CYN7bq2w.js} +2 -2
  14. package/dist/addons/archives.es.js +1 -1
  15. package/dist/addons/bidirectional.es.js +1 -1
  16. package/dist/addons/color.es.js +1 -1
  17. package/dist/addons/physical-sky.es.js +1 -1
  18. package/dist/rayzee-core.es.js +11 -11
  19. package/dist/rayzee.es.js +734 -599
  20. package/dist/rayzee.umd.js +102 -102
  21. package/dist/sceneCachePolicy-BSSsKY98.js +443 -0
  22. package/package.json +1 -1
  23. package/src/Color/ViewTransforms.js +5 -1
  24. package/src/EngineDefaults.js +59 -1032
  25. package/src/Headless.js +6 -5
  26. package/src/Passes/OIDNTemporalHistory.js +1 -1
  27. package/src/PathTracerApp.js +5 -3
  28. package/src/Processor/AssetLoader.js +6 -5
  29. package/src/Processor/BVHBuilder.js +10 -31
  30. package/src/Processor/BVHLeafFold.js +4 -6
  31. package/src/Processor/BVHRefitter.js +3 -46
  32. package/src/Processor/BufferLayout.js +291 -0
  33. package/src/Processor/EmissiveTriangleBuilder.js +1 -1
  34. package/src/Processor/GeometryExtractor.js +3 -3
  35. package/src/Processor/HostMemory.js +7 -1
  36. package/src/Processor/InstanceTable.js +1 -1
  37. package/src/Processor/MaterialPacking.js +2 -1
  38. package/src/Processor/PBRT/PBRTSceneBuilder.js +1 -1
  39. package/src/Processor/QueueManager.js +28 -3
  40. package/src/Processor/SceneProcessor.js +4 -2
  41. package/src/Processor/SplitBLAS.js +1 -1
  42. package/src/Processor/StorageTexturePool.js +22 -1
  43. package/src/Processor/TLASBuilder.js +1 -3
  44. package/src/Processor/TextureBuckets.js +237 -0
  45. package/src/Processor/TextureCreator.js +10 -1
  46. package/src/Processor/Workers/TexturesWorker.js +5 -6
  47. package/src/RayzeeRenderer.js +29 -32
  48. package/src/RenderSettings.js +26 -28
  49. package/src/Stages/ASVGF.js +8 -7
  50. package/src/Stages/AutoExposure.js +14 -5
  51. package/src/Stages/BilateralFilter.js +2 -1
  52. package/src/Stages/DenoiserSettings.js +182 -0
  53. package/src/Stages/EdgeFilter.js +2 -1
  54. package/src/Stages/MotionVector.js +1 -1
  55. package/src/Stages/NRD.js +2 -3
  56. package/src/Stages/NormalDepth.js +3 -2
  57. package/src/Stages/PathTracer.js +72 -4
  58. package/src/Stages/PathTracerStage.js +2 -1
  59. package/src/Stages/Variance.js +1 -1
  60. package/src/TSL/BVHTraversal.js +4 -1
  61. package/src/TSL/Common.js +1 -1
  62. package/src/TSL/EmissiveSampling.js +1 -1
  63. package/src/TSL/LightVisibility.js +111 -0
  64. package/src/TSL/LightsSampling.js +151 -15
  65. package/src/TSL/Random.js +1 -1
  66. package/src/TSL/ShadeKernel.js +88 -3
  67. package/src/TSL/TextureSampling.js +1 -1
  68. package/src/core.js +5 -9
  69. package/src/index.js +10 -21
  70. package/src/integrators/BidirectionalIntegrator.js +2 -1
  71. package/src/managers/CameraManager.js +17 -7
  72. package/src/managers/DenoisingManager.js +14 -10
  73. package/src/managers/EnvironmentManager.js +32 -13
  74. package/src/managers/MaterialDataManager.js +3 -3
  75. package/src/managers/UniformManager.js +2 -2
  76. package/dist/EngineDefaults-DH3bI5t-.js +0 -844
  77. package/dist/sceneCachePolicy-BmwYOYDu.js +0 -183
  78. /package/dist/{Logger-BnHghprG.js → Logger-dDDGj9Av.js} +0 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Where each kind of light gets through, learned from the shadow-ray pick's own rays (LightsSampling lightPick):
3
+ * tries and visible counts per cell of space and facing. Shade reads the learned half, which no kernel writes during
4
+ * a frame, so a render repeats; it adds to the fresh half, folded into the learned one after each frame.
5
+ */
6
+
7
+ import { Fn, float, int, uint, If, Return, instanceIndex, abs, ceil, exp2, floor, length, log2, max, select, sqrt, atomicAdd, atomicLoad, atomicStore } from 'three/tsl';
8
+
9
+ import { VISIBILITY_BASE, VISIBILITY_CELLS, VISIBILITY_KINDS, VISIBILITY_COUNTER_WORDS } from '../Processor/QueueManager.js';
10
+ import { pcgHash } from './Random.js';
11
+
12
+ export const VISIBILITY_KIND = { LAMPS: 0, ENVIRONMENT: 1, SUN: 2, EMITTERS: 3 };
13
+
14
+ const LEARNED = VISIBILITY_BASE;
15
+ const FRESH = VISIBILITY_BASE + VISIBILITY_COUNTER_WORDS;
16
+ const SLOTS = VISIBILITY_CELLS * VISIBILITY_KINDS;
17
+ // A cell is this share of its distance from the camera, rounded up to a power of two.
18
+ const CELL_SHARE = 1 / 32;
19
+ // Below it a kind is still picked now and then, so a light that comes into view is found again.
20
+ const MIN_SHARE = 1 / 64;
21
+ const HALVE_AT = 1 << 16;
22
+
23
+ const hashIn = ( h, v ) => pcgHash( { state: h.bitXor( v ) } );
24
+
25
+ /** The cell of a point and the side its surface faces. */
26
+ export const visibilityCell = ( p, n, cameraPosition ) => {
27
+
28
+ const level = ceil( log2( max( length( p.sub( cameraPosition ) ).mul( CELL_SHARE ), 1e-6 ) ) ).toVar();
29
+ const c = floor( p.div( exp2( level ) ) ).toVar();
30
+ const a = abs( n );
31
+ const facing = select( a.x.greaterThan( max( a.y, a.z ) ), select( n.x.greaterThan( 0.0 ), uint( 0 ), uint( 1 ) ),
32
+ select( a.y.greaterThan( a.z ), select( n.y.greaterThan( 0.0 ), uint( 2 ), uint( 3 ) ), select( n.z.greaterThan( 0.0 ), uint( 4 ), uint( 5 ) ) ) );
33
+
34
+ let h = pcgHash( { state: uint( int( level ).add( 1024 ) ) } );
35
+ h = hashIn( h, uint( int( c.x ) ) );
36
+ h = hashIn( h, uint( int( c.y ) ) );
37
+ h = hashIn( h, uint( int( c.z ) ) );
38
+ h = hashIn( h, facing );
39
+ return h.bitAnd( uint( VISIBILITY_CELLS - 1 ) ).toVar();
40
+
41
+ };
42
+
43
+ const slot = ( cell, kind ) => cell.mul( uint( VISIBILITY_KINDS ) ).add( uint( kind ) ).mul( uint( 2 ) );
44
+
45
+ /** √ of the share of rays that got through: the pick's best weight against a light that is on or off. */
46
+ export const visibilityShare = ( counters, cell, kind ) => {
47
+
48
+ const at = uint( LEARNED ).add( slot( cell, kind ) ).toVar();
49
+ const tries = float( atomicLoad( counters.element( at ) ) );
50
+ const seen = float( atomicLoad( counters.element( at.add( uint( 1 ) ) ) ) );
51
+ return sqrt( max( seen.add( 0.5 ).div( tries.add( 1.0 ) ), MIN_SHARE ) );
52
+
53
+ };
54
+
55
+ export const recordVisibility = ( counters, cell, kind, visible ) => {
56
+
57
+ const at = uint( FRESH ).add( slot( cell, kind ) ).toVar();
58
+ atomicAdd( counters.element( at ), uint( 1 ) );
59
+ If( visible, () => {
60
+
61
+ atomicAdd( counters.element( at.add( uint( 1 ) ) ), uint( 1 ) );
62
+
63
+ } );
64
+
65
+ };
66
+
67
+ /** Adds the frame's counts to the learned ones, halving both past HALVE_AT so the table keeps up with a change. */
68
+ export function buildVisibilityFoldKernel( { counters } ) {
69
+
70
+ return Fn( () => {
71
+
72
+ If( instanceIndex.greaterThanEqual( uint( SLOTS ) ), () => {
73
+
74
+ Return();
75
+
76
+ } );
77
+
78
+ const learned = uint( LEARNED ).add( instanceIndex.mul( uint( 2 ) ) ).toVar();
79
+ const fresh = uint( FRESH ).add( instanceIndex.mul( uint( 2 ) ) ).toVar();
80
+ const tries = atomicLoad( counters.element( learned ) ).add( atomicLoad( counters.element( fresh ) ) ).toVar();
81
+ const seen = atomicLoad( counters.element( learned.add( uint( 1 ) ) ) ).add( atomicLoad( counters.element( fresh.add( uint( 1 ) ) ) ) ).toVar();
82
+ If( tries.greaterThan( uint( HALVE_AT ) ), () => {
83
+
84
+ tries.assign( tries.shiftRight( uint( 1 ) ) );
85
+ seen.assign( seen.shiftRight( uint( 1 ) ) );
86
+
87
+ } );
88
+ atomicStore( counters.element( learned ), tries );
89
+ atomicStore( counters.element( learned.add( uint( 1 ) ) ), seen );
90
+ atomicStore( counters.element( fresh ), uint( 0 ) );
91
+ atomicStore( counters.element( fresh.add( uint( 1 ) ) ), uint( 0 ) );
92
+
93
+ } )().compute( SLOTS, [ 256 ] );
94
+
95
+ }
96
+
97
+ /** Forgets everything learned: a reset may have moved what blocks what. */
98
+ export function buildVisibilityClearKernel( { counters } ) {
99
+
100
+ const words = 2 * VISIBILITY_COUNTER_WORDS;
101
+ return Fn( () => {
102
+
103
+ If( instanceIndex.lessThan( uint( words ) ), () => {
104
+
105
+ atomicStore( counters.element( uint( LEARNED ).add( instanceIndex ) ), uint( 0 ) );
106
+
107
+ } );
108
+
109
+ } )().compute( words, [ 256 ] );
110
+
111
+ }
@@ -35,6 +35,7 @@ import {
35
35
  length,
36
36
  mix,
37
37
  select,
38
+ min,
38
39
  If,
39
40
  Loop,
40
41
  sampler,
@@ -84,10 +85,12 @@ import {
84
85
  computeDotProductsAniso,
85
86
  offsetRayOrigin,
86
87
  SHADOW_END,
88
+ REC709_LUMINANCE_COEFFICIENTS,
87
89
  } from './Common.js';
88
90
  import { sampleEnvironment, sampleEnvironmentExact } from './Environment.js';
89
91
  import { shadowTerminatorOrigin } from './ShadowTerminator.js';
90
92
  import { sunRadianceToward, sampleSunDisc } from './Sun.js';
93
+ import { VISIBILITY_KIND, visibilityShare, recordVisibility } from './LightVisibility.js';
91
94
 
92
95
  const TWO_PI = 2.0 * PI;
93
96
 
@@ -773,13 +776,97 @@ export const calculateMaterialPDF = Fn( ( [ viewDir, lightDir, normal, material
773
776
 
774
777
  } );
775
778
 
779
+ // Fewer shadow rays a hit (setting shadowRays). Each light kind offers its sample unshadowed; `variant`:
780
+ // 'one' — keeps one with chance ∝ luminance × √(its kind's learned visibility), traced and divided by that chance;
781
+ // 'two' — always traces the strongest as well, and picks one of the rest.
782
+ const ONE_BELOW = 0.99999994;
783
+
784
+ const slotOf = () => ( {
785
+ kind: int( 0 ).toVar(), weight: float( 0.0 ).toVar(), value: vec3( 0.0 ).toVar(),
786
+ origin: vec3( 0.0 ).toVar(), direction: vec3( 0.0, 1.0, 0.0 ).toVar(), distance: float( 0.0 ).toVar(),
787
+ } );
788
+
789
+ const FIELDS = [ 'kind', 'weight', 'value', 'origin', 'direction', 'distance' ];
790
+ const assignSlot = ( slot, from ) => FIELDS.forEach( ( f ) => slot[ f ].assign( from[ f ] ) );
791
+
792
+ // `learned` (LightVisibility.js) gives `share( kind )` and `record( kind, visible )`.
793
+ export const lightPick = ( u0, learned = null, variant = 'one' ) => {
794
+
795
+ const u = float( u0 ).toVar();
796
+ const total = float( 0.0 ).toVar();
797
+ const kept = slotOf();
798
+ const best = variant === 'two' ? slotOf() : null;
799
+
800
+ const keep = ( c ) => {
801
+
802
+ If( c.weight.greaterThan( 0.0 ), () => {
803
+
804
+ total.addAssign( c.weight );
805
+ const p = c.weight.div( total ).toVar();
806
+ If( u.lessThan( p ), () => {
807
+
808
+ u.assign( min( u.div( p ), ONE_BELOW ) );
809
+ assignSlot( kept, c );
810
+
811
+ } ).Else( () => {
812
+
813
+ u.assign( min( u.sub( p ).div( float( 1.0 ).sub( p ) ), ONE_BELOW ) );
814
+
815
+ } );
816
+
817
+ } );
818
+
819
+ };
820
+
821
+ const offer = ( contribution, from, dir, maxDist, lightKind = 0 ) => {
822
+
823
+ const luminance = dot( contribution, REC709_LUMINANCE_COEFFICIENTS );
824
+ const c = {
825
+ kind: int( lightKind ), value: contribution, origin: from, direction: dir, distance: maxDist,
826
+ weight: ( learned ? luminance.mul( learned.share( lightKind ) ) : luminance ).toVar(),
827
+ };
828
+ if ( ! best ) return keep( c );
829
+
830
+ const better = c.weight.greaterThan( best.weight ).toVar();
831
+ const loser = Object.fromEntries( FIELDS.map( ( f ) => [ f, select( better, best[ f ], c[ f ] ).toVar() ] ) );
832
+ keep( loser );
833
+ If( better, () => assignSlot( best, c ) );
834
+
835
+ };
836
+
837
+ // The direct light, tracing what the variant keeps through `shadow( origin, direction, distance )`.
838
+ const trace = ( shadow ) => {
839
+
840
+ const result = vec3( 0.0 ).toVar();
841
+ Loop( { start: int( 0 ), end: int( best ? 2 : 1 ), type: 'int', condition: '<' }, ( { i } ) => {
842
+
843
+ const isBest = best ? i.equal( int( 0 ) ) : tslBool( false );
844
+ const pickOf = ( f ) => ( best ? select( isBest, best[ f ], kept[ f ] ) : kept[ f ] );
845
+ const weight = pickOf( 'weight' ).toVar();
846
+ If( weight.greaterThan( 0.0 ), () => {
847
+
848
+ const visibility = shadow( pickOf( 'origin' ), pickOf( 'direction' ), pickOf( 'distance' ) ).toVar();
849
+ if ( learned ) learned.record( pickOf( 'kind' ), visibility.greaterThan( 0.0 ) );
850
+ result.addAssign( pickOf( 'value' ).mul( visibility ).mul( select( isBest, float( 1.0 ), total.div( weight ) ) ) );
851
+
852
+ } );
853
+
854
+ } );
855
+ return result;
856
+
857
+ };
858
+
859
+ return { offer, total, trace };
860
+
861
+ };
862
+
776
863
  // =============================================================================
777
864
  // Unified Direct Lighting System
778
865
  // =============================================================================
779
866
 
780
867
  // Optimized direct lighting function with importance-based sampling and better MIS. `throughSurfaces` (compile
781
868
  // time) adds light from behind a surface that passes it through diffusely; without it the code is exactly as before.
782
- const makeDirectLighting = ( throughSurfaces ) => Fn( ( [
869
+ const makeDirectLighting = ( throughSurfaces, picked = null ) => Fn( ( [
783
870
  // Surface hit data
784
871
  hitPoint, hitNormal, geomNormal, material,
785
872
  // View direction
@@ -813,6 +900,9 @@ const makeDirectLighting = ( throughSurfaces ) => Fn( ( [
813
900
  terminatorLift, facetNormal, terminatorCutoff,
814
901
  // The physical sky's sun (Sun.js); its BSDF-sampled partner is the miss branch in ShadeKernel.
815
902
  hasSun, sunDirection, sunRadiance, sunParams,
903
+ // picked: the caller's emitter sample, unshadowed, and the learned visibility (LightVisibility.js) at this point
904
+ emissiveValue, emissiveOrigin, emissiveDirection, emissiveDistance,
905
+ visibilityCounters, visibilityCell,
816
906
  ] ) => {
817
907
 
818
908
  const totalContribution = vec3( 0.0 ).toVar();
@@ -880,6 +970,13 @@ const makeDirectLighting = ( throughSurfaces ) => Fn( ( [
880
970
  // rather than re-walking every light buffer to rebuild the same sum.
881
971
  const neeTotalWeight = float( 0.0 ).toVar();
882
972
 
973
+ const learned = picked ? {
974
+ share: ( kind ) => visibilityShare( visibilityCounters, visibilityCell, kind ),
975
+ record: ( kind, visible ) => recordVisibility( visibilityCounters, visibilityCell, kind, visible ),
976
+ } : null;
977
+ const pick = picked ? lightPick( getRandomSample1D( pixelCoord, int( 0 ), dimBase, rngState, resolution, frame ), learned, picked ) : null;
978
+ if ( pick ) pick.offer( emissiveValue, emissiveOrigin, emissiveDirection, emissiveDistance, VISIBILITY_KIND.EMITTERS );
979
+
883
980
  // =====================================================================
884
981
  // LIGHT SAMPLING PATH
885
982
  // =====================================================================
@@ -911,9 +1008,7 @@ const makeDirectLighting = ( throughSurfaces ) => Fn( ( [
911
1008
  const lightOrigin = originFor( back, lightShadowOrigin( lightSample.direction ) ).toVar();
912
1009
  const toSample = hitPoint.add( lightSample.direction.mul( lightSample.distance ) ).sub( lightOrigin ).toVar();
913
1010
  const shadowDistance = length( toSample ).toVar();
914
- const visibility = shadow( lightOrigin, toSample.div( shadowDistance ), shadowDistance.mul( SHADOW_END ) );
915
-
916
- If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
1011
+ const lampLight = () => {
917
1012
 
918
1013
  const { value: brdfValue, pdf: bPdf } = bsdfToward( lightSample.direction, back );
919
1014
 
@@ -927,17 +1022,33 @@ const makeDirectLighting = ( throughSurfaces ) => Fn( ( [
927
1022
 
928
1023
  } );
929
1024
 
930
- // Base contribution WITHOUT visibility; shadowed = base × visibility (identical to the
931
- // pre-dual-sum math), unoccluded = base × 1 (shadow-catcher reference only).
932
- const baseContribution = lightSample.emission.mul( brdfValue ).mul( NoL ).mul( misW ).div( max( lightSample.pdf, 1e-10 ) );
933
- totalContribution.addAssign( baseContribution.mul( visibility ) );
934
- If( wantUnoccluded, () => {
1025
+ return lightSample.emission.mul( brdfValue ).mul( NoL ).mul( misW ).div( max( lightSample.pdf, 1e-10 ) );
1026
+
1027
+ };
1028
+
1029
+ if ( pick ) {
1030
+
1031
+ pick.offer( lampLight(), lightOrigin, toSample.div( shadowDistance ), shadowDistance.mul( SHADOW_END ), VISIBILITY_KIND.LAMPS );
1032
+
1033
+ } else {
1034
+
1035
+ const visibility = shadow( lightOrigin, toSample.div( shadowDistance ), shadowDistance.mul( SHADOW_END ) );
1036
+
1037
+ If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
1038
+
1039
+ // Base contribution WITHOUT visibility; shadowed = base × visibility (identical to the
1040
+ // pre-dual-sum math), unoccluded = base × 1 (shadow-catcher reference only).
1041
+ const baseContribution = lampLight();
1042
+ totalContribution.addAssign( baseContribution.mul( visibility ) );
1043
+ If( wantUnoccluded, () => {
1044
+
1045
+ unoccludedContribution.addAssign( baseContribution );
935
1046
 
936
- unoccludedContribution.addAssign( baseContribution );
1047
+ } );
937
1048
 
938
1049
  } );
939
1050
 
940
- } );
1051
+ }
941
1052
 
942
1053
  } );
943
1054
 
@@ -1082,9 +1193,7 @@ const makeDirectLighting = ( throughSurfaces ) => Fn( ( [
1082
1193
 
1083
1194
  If( reachesSurface( direction, back ), () => {
1084
1195
 
1085
- const visibility = shadow( originFor( back, lightShadowOrigin( direction ) ), direction, float( 1e20 ) );
1086
-
1087
- If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
1196
+ const skyLight = () => {
1088
1197
 
1089
1198
  const { value: brdfValue, pdf: bPdf } = bsdfToward( direction, back );
1090
1199
 
@@ -1096,8 +1205,24 @@ const makeDirectLighting = ( throughSurfaces ) => Fn( ( [
1096
1205
  float( 1.0 )
1097
1206
  ).toVar();
1098
1207
 
1208
+ return radiance.mul( brdfValue ).mul( NoL ).mul( misW ).div( max( lightPdf, 1e-10 ) );
1209
+
1210
+ };
1211
+
1212
+ if ( pick ) {
1213
+
1214
+ pick.offer( skyLight(), originFor( back, lightShadowOrigin( direction ) ), direction, float( 1e20 ),
1215
+ select( isSun, int( VISIBILITY_KIND.SUN ), int( VISIBILITY_KIND.ENVIRONMENT ) ) );
1216
+ return;
1217
+
1218
+ }
1219
+
1220
+ const visibility = shadow( originFor( back, lightShadowOrigin( direction ) ), direction, float( 1e20 ) );
1221
+
1222
+ If( visibility.greaterThan( 0.0 ).or( wantUnoccluded ), () => {
1223
+
1099
1224
  // Base contribution WITHOUT visibility (deterministic estimator; no stochastic scaling).
1100
- const baseContribution = radiance.mul( brdfValue ).mul( NoL ).mul( misW ).div( max( lightPdf, 1e-10 ) );
1225
+ const baseContribution = skyLight();
1101
1226
  totalContribution.addAssign( baseContribution.mul( visibility ) );
1102
1227
  If( wantUnoccluded, () => {
1103
1228
 
@@ -1115,6 +1240,8 @@ const makeDirectLighting = ( throughSurfaces ) => Fn( ( [
1115
1240
 
1116
1241
  } );
1117
1242
 
1243
+ if ( pick ) totalContribution.addAssign( pick.trace( shadow ) );
1244
+
1118
1245
  // EMISSIVE TRIANGLE DIRECT LIGHTING
1119
1246
  // NOTE: Emissive triangle sampling is handled separately in pathtracer_core.fs
1120
1247
  // to bypass firefly suppression. Do not add it here to avoid double-counting.
@@ -1125,3 +1252,12 @@ const makeDirectLighting = ( throughSurfaces ) => Fn( ( [
1125
1252
 
1126
1253
  export const calculateDirectLightingUnified = /*@__PURE__*/ makeDirectLighting( false );
1127
1254
  export const calculateDirectLightingThroughSurfaces = /*@__PURE__*/ makeDirectLighting( true );
1255
+ const pickedLighting = new Map();
1256
+ /** The direct light with fewer shadow rays a hit; `variant` as lightPick takes it. */
1257
+ export const calculateDirectLightingPicked = ( throughSurfaces, variant ) => {
1258
+
1259
+ const key = `${throughSurfaces}:${variant}`;
1260
+ if ( ! pickedLighting.has( key ) ) pickedLighting.set( key, makeDirectLighting( throughSurfaces, variant ) );
1261
+ return pickedLighting.get( key );
1262
+
1263
+ };
package/src/TSL/Random.js CHANGED
@@ -107,7 +107,7 @@ export const RandomPointInCircle = ( rngState ) => {
107
107
  // +7 ground-catcher cosine BSDF (ShadeKernel)
108
108
  // +8 indirect-strategy direction (LightsIndirect)
109
109
  // +9 physical-sky sun disc (LightsSampling)
110
- // 1D +0 (free — was the {lights,BRDF} stochastic strategy pick)
110
+ // 1D +0 light kind for the picked shadow ray (LightsSampling lightPick, setting shadowRays)
111
111
  // +1 emissive-triangle pick (EmissiveSampling / LightBVHSampling)
112
112
  // +2 BSDF lobe selection (ShadeKernel)
113
113
  // +3 Russian roulette (PathTracerCore)
@@ -21,6 +21,7 @@ import { sampleAllMaterialTextures, processAnisotropyMap, applyExtensionMaps, ge
21
21
  import { evaluateMaterialResponse } from './MaterialEvaluation.js';
22
22
  import {
23
23
  calculateDirectLightingUnified, calculateDirectLightingThroughSurfaces, calculateMaterialPDF,
24
+ calculateDirectLightingPicked,
24
25
  sampleDirectionalLight, sampleRectAreaLight, samplePointLightWithAttenuation, sampleSpotLightWithRadius,
25
26
  areaLightRadiance, areaLightSpreadAttenuation,
26
27
  } from './LightsSampling.js';
@@ -28,6 +29,7 @@ import { traceShadowRay, traceShadowRayRefractiveOpaque, estimateLightImportance
28
29
  import { shadowTerminatorOrigin } from './ShadowTerminator.js';
29
30
  import { hitFacet, unpackHitFacet, windingNormal } from './HitFacet.js';
30
31
  import { traverseBVHShadow, triangleSide, sideAccepts } from './BVHTraversal.js';
32
+ import { visibilityCell } from './LightVisibility.js';
31
33
  import { handleMaterialTransparency, MaterialInteractionResult } from './MaterialTransmission.js';
32
34
  import { sampleChromaticCollision, sampleHenyeyGreenstein, subsurfaceCoefficients, CollisionSample, MediumCoeffs } from './Subsurface.js';
33
35
  import { ALL_MATERIAL_LAYERS } from './SceneResources.js';
@@ -44,7 +46,7 @@ import { sampleClearcoat, ClearcoatResult } from './Clearcoat.js';
44
46
  import { refineDisplacedIntersection, DisplacementResult } from './Displacement.js';
45
47
  import {
46
48
  calculateEmissiveTriangleContribution, calculateEmissiveLightPdf, EmissiveSample, EmissiveSampleIndexed,
47
- sampleEmissiveTriangleIndexed, fetchTriangleData, TriangleData,
49
+ sampleEmissiveTriangle, sampleEmissiveTriangleIndexed, fetchTriangleData, TriangleData,
48
50
  } from './EmissiveSampling.js';
49
51
  import { sampleLightBVHTriangle, sampleLightBVHTriangleIndexed, calculateLightBVHPdf } from './LightBVHSampling.js';
50
52
  import {
@@ -134,6 +136,9 @@ export function buildShadeKernel( params ) {
134
136
  // The material layers some material uses (MaterialDataManager.materialLayers); the rest compile out. Bidirectional
135
137
  // always leaves diffuse transmission out: its connections and light tracing do not cross surfaces yet.
136
138
  materialLayers: layers = ALL_MATERIAL_LAYERS,
139
+ // Fewer shadow rays a hit, emitters included: null (one per light kind) or a lightPick variant; path tracing only.
140
+ shadowRays = null,
141
+ cameraWorldMatrix = null,
137
142
  } = params;
138
143
  const diffuseTransmission = layers.diffuseTransmission;
139
144
 
@@ -145,6 +150,7 @@ export function buildShadeKernel( params ) {
145
150
  const auxOn = gBuffer ? auxGBufferEnabled.greaterThan( uint( 0 ) ) : null;
146
151
 
147
152
  const useEmissiveNEE = lightBuffer !== undefined;
153
+ const picksLights = shadowRays && ! bdpt;
148
154
  const lamps = {
149
155
  directional: directionalLightsBuffer, numDirectional: numDirectionalLights, area: areaLightsBuffer, numArea: numAreaLights,
150
156
  point: pointLightsBuffer, numPoint: numPointLights, spot: spotLightsBuffer, numSpot: numSpotLights,
@@ -1607,7 +1613,85 @@ export function buildShadeKernel( params ) {
1607
1613
 
1608
1614
  } ).Else( sampleLobes );
1609
1615
 
1610
- const directLighting = () => DirectLightingDual.wrap( ( transmitting ? calculateDirectLightingThroughSurfaces : calculateDirectLightingUnified )(
1616
+ const emitterCandidate = () => {
1617
+
1618
+ const value = vec3( 0.0 ).toVar();
1619
+ const from = vec3( 0.0 ).toVar();
1620
+ const dir = vec3( 0.0, 1.0, 0.0 ).toVar();
1621
+ const dist = float( 0.0 ).toVar();
1622
+ if ( ! useEmissiveNEE ) return [ value, from, dir, dist ];
1623
+
1624
+ If( enableEmissiveTriangleSampling.equal( int( 1 ) ).and( emissiveTriangleCount.greaterThan( int( 0 ) ) ), () => {
1625
+
1626
+ const position = vec3( 0.0 ).toVar();
1627
+ const direction = vec3( 0.0, 1.0, 0.0 ).toVar();
1628
+ const emission = vec3( 0.0 ).toVar();
1629
+ const pdf = float( 0.0 ).toVar();
1630
+ const valid = tslBool( false ).toVar();
1631
+ const take = ( sample ) => {
1632
+
1633
+ const e = EmissiveSample.wrap( sample );
1634
+ position.assign( e.position );
1635
+ direction.assign( e.direction );
1636
+ emission.assign( e.emission );
1637
+ pdf.assign( e.pdf );
1638
+ valid.assign( e.valid );
1639
+
1640
+ };
1641
+
1642
+ If( lightBVHNodeCount.greaterThan( int( 0 ) ), () => {
1643
+
1644
+ take( sampleLightBVHTriangle(
1645
+ hitPoint, N, rngState, _pixelCoord, resolution, frame, dimBase,
1646
+ lightBuffer, lightBuffer, emissiveVec4Offset, triangleBuffer, bvhBuffer, transmitsDiffusely,
1647
+ ) );
1648
+
1649
+ } ).Else( () => {
1650
+
1651
+ take( sampleEmissiveTriangle(
1652
+ hitPoint, N, rngState, _pixelCoord, resolution, frame, dimBase,
1653
+ lightBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, triangleBuffer, bvhBuffer, transmitsDiffusely,
1654
+ ) );
1655
+
1656
+ } );
1657
+
1658
+ If( valid.and( pdf.greaterThan( 0.0 ) ), () => {
1659
+
1660
+ const back = throughSurface( direction ).toVar();
1661
+ const NoL = transmitting ? abs( dot( N, direction ) ) : max( float( 0.0 ), dot( N, direction ) );
1662
+ const inFront = NoL.greaterThan( 0.0 ).and( dot( N, direction ).greaterThan( 0.0 ) )
1663
+ .and( dot( direction, NgeoFF ).greaterThan( 0.0 ) );
1664
+
1665
+ If( transmitting ? back.or( inFront ) : inFront, () => {
1666
+
1667
+ from.assign( originFor( direction, lightShadowOrigin( direction ) ) );
1668
+ const toSample = position.sub( from ).toVar();
1669
+ const span = length( toSample ).toVar();
1670
+ dir.assign( toSample.div( span ) );
1671
+ dist.assign( span.mul( SHADOW_END ) );
1672
+
1673
+ const bPdf = calculateMaterialPDF( V, direction, N, material );
1674
+ const misW = select( bPdf.greaterThan( 0.0 ), powerHeuristic( { pdf1: pdf, pdf2: bPdf } ), float( 1.0 ) );
1675
+ value.assign( emission.mul( evaluateMaterialResponse( V, direction, N, material ) ).mul( NoL )
1676
+ .div( pdf ).mul( emissiveBoost ).mul( misW ) );
1677
+
1678
+ } );
1679
+
1680
+ } );
1681
+
1682
+ } );
1683
+
1684
+ return [ value, from, dir, dist ];
1685
+
1686
+ };
1687
+
1688
+ const pickArgs = picksLights
1689
+ ? [ ...emitterCandidate(), counters, visibilityCell( hitPoint, NgeoFF, vec3( cameraWorldMatrix[ 3 ] ) ) ]
1690
+ : [];
1691
+ const directLightingFn = picksLights
1692
+ ? calculateDirectLightingPicked( transmitting, shadowRays )
1693
+ : ( transmitting ? calculateDirectLightingThroughSurfaces : calculateDirectLightingUnified );
1694
+ const directLighting = () => DirectLightingDual.wrap( directLightingFn(
1611
1695
  hitPoint, N, NgeoFF, material, V,
1612
1696
  brdfDir, brdfPdf, brdfValue,
1613
1697
  bounceIndex, rngState,
@@ -1624,6 +1708,7 @@ export function buildShadeKernel( params ) {
1624
1708
  tslBool( false ), // wantUnoccluded: false on real surfaces — dead-codes the unoccluded sum
1625
1709
  terminatorLift, facetN, shadowTerminatorOffset,
1626
1710
  hasSun, sunDirection, sunRadiance, sunParams,
1711
+ ...pickArgs,
1627
1712
  ) ).shadowed;
1628
1713
  // Bidirectional mode samples every light itself, below.
1629
1714
  const directLight = bdpt ? null : directLighting().toVar();
@@ -1954,7 +2039,7 @@ export function buildShadeKernel( params ) {
1954
2039
 
1955
2040
  } );
1956
2041
 
1957
- } else if ( useEmissiveNEE ) {
2042
+ } else if ( useEmissiveNEE && ! picksLights ) {
1958
2043
 
1959
2044
  If(
1960
2045
  enableEmissiveTriangleSampling.equal( int( 1 ) )
@@ -8,7 +8,7 @@ import {
8
8
  ExtMapResult,
9
9
  } from './Struct.js';
10
10
  import { instanceRows, instanceDirToWorld, triangleRow } from './Common.js';
11
- import { TEXTURE_CONSTANTS } from '../EngineDefaults.js';
11
+ import { TEXTURE_CONSTANTS } from '../Processor/TextureBuckets.js';
12
12
 
13
13
  // ================================================================================
14
14
  // CONSOLIDATED SHAPE-BUCKETED MATERIAL TEXTURES
package/src/core.js CHANGED
@@ -21,15 +21,11 @@ export { RenderSettings, SETTING_SOURCE } from './RenderSettings.js';
21
21
  export { configureAssets, getAssetConfig } from './AssetConfig.js';
22
22
  export { configurePlatform, getPlatform } from './Platform.js';
23
23
  export { LightManager } from './managers/LightManager.js';
24
- export { listViewTransforms, getViewTransform, onRegistryChange } from './Color/ViewTransforms.js';
24
+ export { listViewTransforms, getViewTransform, onRegistryChange, DEFAULT_VIEW } from './Color/ViewTransforms.js';
25
25
  export { ARCHIVE_FORMATS } from './Processor/archiveFormats.js';
26
- export {
27
- ENGINE_DEFAULTS,
28
- MATERIAL_DEFAULTS,
29
- RENDER_PROFILES,
30
- getRenderProfile,
31
- MAX_RESERVABLE_RENDER_SIZE,
32
- TRIANGLE_DATA_LAYOUT,
33
- } from './EngineDefaults.js';
26
+ export { ENGINE_DEFAULTS, MATERIAL_DEFAULTS } from './EngineDefaults.js';
27
+ export { SKY_DEFAULTS } from './managers/EnvironmentManager.js';
28
+ export { MAX_RESERVABLE_RENDER_SIZE } from './Processor/StorageTexturePool.js';
29
+ export { TRIANGLE_DATA_LAYOUT } from './Processor/BufferLayout.js';
34
30
  export { Logger, createLogger, fmt, LOG_LEVELS } from './utils/Logger.js';
35
31
  export { VERSION } from './version.js';
package/src/index.js CHANGED
@@ -57,30 +57,19 @@ export { Logger, createLogger, fmt, LOG_LEVELS } from './utils/Logger.js';
57
57
  // Configuration defaults and presets
58
58
  export {
59
59
  ENGINE_DEFAULTS,
60
- ASVGF_QUALITY_PRESETS,
61
- NRD_DEFAULTS,
62
- NRD_QUALITY_PRESETS,
63
- NRD_PRESET_KEYS,
64
- NRD_HIT_DIST_A,
65
- NRD_HIT_DIST_B,
66
- CAMERA_PRESETS,
67
- CAMERA_RANGES,
68
- SKY_PRESETS,
69
- DEFAULT_SUN_PATH,
70
- AUTO_FOCUS_MODES,
71
- AF_DEFAULTS,
72
- TRIANGLE_DATA_LAYOUT,
73
- BVH_LEAF_MARKERS,
74
- TEXTURE_CONSTANTS,
75
- DEFAULT_TEXTURE_MATRIX,
76
60
  MATERIAL_DEFAULTS,
77
- MEMORY_CONSTANTS,
78
61
  PRODUCTION_RENDER_CONFIG,
79
62
  INTERACTIVE_RENDER_CONFIG,
80
- MAX_RESERVABLE_RENDER_SIZE,
81
- RENDER_PROFILES,
82
- getRenderProfile,
83
63
  } from './EngineDefaults.js';
64
+ export { MAX_RESERVABLE_RENDER_SIZE } from './Processor/StorageTexturePool.js';
65
+ export {
66
+ DENOISER_DEFAULTS, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS, NRD_HIT_DIST_A,
67
+ NRD_HIT_DIST_B,
68
+ } from './Stages/DenoiserSettings.js';
69
+ export { SKY_DEFAULTS, DEFAULT_SUN_PATH } from './managers/EnvironmentManager.js';
70
+ export { AUTO_EXPOSURE_DEFAULTS } from './Stages/AutoExposure.js';
71
+ export { AUTO_FOCUS_DEFAULTS } from './managers/CameraManager.js';
72
+ export { TRIANGLE_DATA_LAYOUT, BVH_LEAF_MARKERS } from './Processor/BufferLayout.js';
84
73
  export { sunPosition, timeForSunElevation, dayOfYearForMonth } from './Processor/SunPosition.js';
85
74
 
86
75
  // Settings & managers (for advanced consumers)
@@ -141,7 +130,7 @@ export { displayCanvasFit } from './Color/Displays.js';
141
130
  export {
142
131
  VIEW_TRANSFORMS, listViewTransforms, getViewTransform, addViewTransform, removeViewTransform,
143
132
  onRegistryChange, getRegistryVersion, buildToneMapWGSL,
144
- OCIO_VIEW_BASE, MAX_TABLE_TRANSFORMS,
133
+ OCIO_VIEW_BASE, MAX_TABLE_TRANSFORMS, DEFAULT_VIEW,
145
134
  } from './Color/ViewTransforms.js';
146
135
  export {
147
136
  buildOcioView, addOcioView, addAllOcioViews,
@@ -12,7 +12,8 @@ import { gpuOnlyStorageAttribute } from '../TSL/patches.js';
12
12
  import { LIGHT_FLOATS } from '../managers/UniformManager.js';
13
13
  import { HIT_STRIDE_BIDIRECTIONAL, LIGHT_VERTEX_STRIDE, freeStorageAttribute } from '../Processor/PackedRayBuffer.js';
14
14
  import { COUNTER, ENERGY_SCALE } from '../Processor/QueueManager.js';
15
- import { CAMERA_PROJECTION_IDS, BVH_MAX_INDEX } from '../EngineDefaults.js';
15
+ import { CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
16
+ import { BVH_MAX_INDEX } from '../Processor/BufferLayout.js';
16
17
  import { BOUNCE_KERNELS, LIST_WG_SIZE } from '../Stages/PathTracer.js';
17
18
  import { buildLightGenerateKernel, LIGHT_GENERATE_WG_SIZE } from '../TSL/LightGenerateKernel.js';
18
19
  import { buildGuideKernel, buildGuideClearKernel, guidedDiscPdf, recordEscape, GUIDE_BINS, GUIDE_ROW_STRIDE, GUIDE_TEXTURE_WIDTH } from '../TSL/LightGuide.js';
@@ -1,12 +1,22 @@
1
1
  import { EventDispatcher, MathUtils, Matrix4, OrthographicCamera, PerspectiveCamera, Quaternion, Vector3 } from 'three';
2
2
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
3
3
  import { EngineEvents, } from '../EngineEvents.js';
4
- import { AF_DEFAULTS, CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
4
+ import { CAMERA_PROJECTION_IDS } from '../EngineDefaults.js';
5
5
  import { viewDepth } from './InteractionManager.js';
6
6
  import { ViewCamera } from './ViewCamera.js';
7
7
  import { WalkControls } from './WalkControls.js';
8
8
  import { toPortable, fromPortable } from '../SceneState/portable.js';
9
9
 
10
+ export const AUTO_FOCUS_DEFAULTS = {
11
+ autoFocusMode: 'auto', // 'manual' | 'auto'
12
+ afScreenPoint: { x: 0.5, y: 0.5 },
13
+ afSmoothingFactor: 0.15,
14
+ };
15
+
16
+ const AF_RESET_THRESHOLD = 0.05;
17
+ const AF_FALLBACK_DISTANCE = 10.0;
18
+ const AF_SNAP_THRESHOLD = 0.5;
19
+
10
20
  const DEFAULT_CAMERA_SCALE = Object.freeze( new Vector3( 1, 1, 1 ) );
11
21
 
12
22
  /** The height of an orthographic camera's view in world units, or null for any other camera. */
@@ -55,9 +65,9 @@ export class CameraManager extends EventDispatcher {
55
65
  this._userCameraCounter = 0;
56
66
 
57
67
  // Auto-focus state
58
- this.autoFocusMode = AF_DEFAULTS.SMOOTHING_FACTOR ? 'auto' : 'manual';
59
- this.afScreenPoint = { x: 0.5, y: 0.5 };
60
- this.afSmoothingFactor = AF_DEFAULTS.SMOOTHING_FACTOR;
68
+ this.autoFocusMode = AUTO_FOCUS_DEFAULTS.autoFocusMode;
69
+ this.afScreenPoint = { ...AUTO_FOCUS_DEFAULTS.afScreenPoint };
70
+ this.afSmoothingFactor = AUTO_FOCUS_DEFAULTS.afSmoothingFactor;
61
71
  this._lastValidFocusDistance = null;
62
72
  this._smoothedFocusDistance = null;
63
73
  this._afPointDirty = false;
@@ -873,7 +883,7 @@ export class CameraManager extends EventDispatcher {
873
883
 
874
884
  // Nothing under the AF point on a fresh view: focus where the camera orbits.
875
885
  const depth = this.controls ? viewDepth( this.controls.target, this.camera ) : 0;
876
- rawDistance = depth > 0 ? depth : AF_DEFAULTS.FALLBACK_DISTANCE * ( assetLoader?.getSceneScale() || 1.0 );
886
+ rawDistance = depth > 0 ? depth : AF_FALLBACK_DISTANCE * ( assetLoader?.getSceneScale() || 1.0 );
877
887
  this._lastValidFocusDistance = rawDistance;
878
888
 
879
889
  }
@@ -893,7 +903,7 @@ export class CameraManager extends EventDispatcher {
893
903
  const changeFraction = Math.abs( rawDistance - this._smoothedFocusDistance )
894
904
  / this._smoothedFocusDistance;
895
905
 
896
- if ( changeFraction > AF_DEFAULTS.SNAP_THRESHOLD ) {
906
+ if ( changeFraction > AF_SNAP_THRESHOLD ) {
897
907
 
898
908
  this._smoothedFocusDistance = rawDistance;
899
909
 
@@ -921,7 +931,7 @@ export class CameraManager extends EventDispatcher {
921
931
 
922
932
  hardReset?.();
923
933
 
924
- } else if ( changeRatio > AF_DEFAULTS.RESET_THRESHOLD ) {
934
+ } else if ( changeRatio > AF_RESET_THRESHOLD ) {
925
935
 
926
936
  softReset?.();
927
937