rayzee 5.3.8 → 5.4.1

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 (48) hide show
  1. package/dist/rayzee.es.js +2933 -2888
  2. package/dist/rayzee.es.js.map +1 -1
  3. package/dist/rayzee.umd.js +53 -53
  4. package/dist/rayzee.umd.js.map +1 -1
  5. package/package.json +2 -2
  6. package/src/Passes/AIUpscaler.js +30 -6
  7. package/src/Passes/OIDNDenoiser.js +57 -15
  8. package/src/PathTracerApp.js +154 -21
  9. package/src/Pipeline/RenderPipeline.js +10 -1
  10. package/src/Processor/AssetLoader.js +40 -18
  11. package/src/Processor/EquirectHDRInfo.js +38 -29
  12. package/src/Processor/InstanceTable.js +16 -0
  13. package/src/Processor/SceneProcessor.js +22 -33
  14. package/src/Processor/ShaderBuilder.js +67 -22
  15. package/src/Processor/TLASBuilder.js +9 -4
  16. package/src/Stages/ASVGF.js +4 -4
  17. package/src/Stages/AdaptiveSampling.js +2 -2
  18. package/src/Stages/AutoExposure.js +42 -32
  19. package/src/Stages/BilateralFilter.js +2 -2
  20. package/src/Stages/Display.js +2 -1
  21. package/src/Stages/EdgeFilter.js +6 -3
  22. package/src/Stages/MotionVector.js +2 -2
  23. package/src/Stages/NormalDepth.js +1 -1
  24. package/src/Stages/PathTracer.js +88 -46
  25. package/src/Stages/SSRC.js +4 -4
  26. package/src/Stages/Variance.js +2 -2
  27. package/src/TSL/BVHTraversal.js +15 -63
  28. package/src/TSL/Clearcoat.js +1 -1
  29. package/src/TSL/Displacement.js +1 -1
  30. package/src/TSL/EmissiveSampling.js +17 -13
  31. package/src/TSL/Environment.js +12 -9
  32. package/src/TSL/LightBVHSampling.js +3 -2
  33. package/src/TSL/LightsCore.js +1 -1
  34. package/src/TSL/LightsDirect.js +1 -1
  35. package/src/TSL/LightsIndirect.js +0 -1
  36. package/src/TSL/LightsSampling.js +2 -2
  37. package/src/TSL/MaterialTransmission.js +1 -1
  38. package/src/TSL/PathTracer.js +4 -4
  39. package/src/TSL/PathTracerCore.js +6 -6
  40. package/src/TSL/Struct.js +1 -1
  41. package/src/TSL/patches.js +145 -0
  42. package/src/index.js +1 -1
  43. package/src/managers/EnvironmentManager.js +32 -56
  44. package/src/managers/LightManager.js +20 -0
  45. package/src/managers/UniformManager.js +22 -0
  46. package/src/managers/helpers/OutlineHelper.js +3 -1
  47. package/src/TSL/storageTexturePatch.js +0 -31
  48. package/src/TSL/structProxy.js +0 -87
@@ -352,7 +352,7 @@ export class NormalDepth extends RenderStage {
352
352
  this._dispatchY = Math.ceil( height / 8 );
353
353
  if ( this._computeNode ) {
354
354
 
355
- this._computeNode.setCount( [ this._dispatchX, this._dispatchY, 1 ] );
355
+ this._computeNode.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
356
356
 
357
357
  }
358
358
 
@@ -180,17 +180,20 @@ export class PathTracer extends RenderStage {
180
180
  // Blue noise
181
181
  this.blueNoiseTexture = null;
182
182
 
183
- // Emissive triangles (storage buffer)initialized with dummy data so TSL compilation never sees null
184
- this.emissiveTriangleStorageAttr = new StorageInstancedBufferAttribute( new Float32Array( 4 ), 4 );
185
- this.emissiveTriangleStorageNode = storage( this.emissiveTriangleStorageAttr, 'vec4', 1 ).toReadOnly();
183
+ // Packed light buffer — [lightBVH nodes (4 vec4s each) | emissive triangles (2 vec4s each)]
184
+ // emissiveVec4Offset uniform tracks the vec4-count offset where emissive data starts.
185
+ // Initialized with dummy data so TSL compilation never sees null.
186
+ this.lightStorageAttr = new StorageInstancedBufferAttribute( new Float32Array( 16 ), 4 );
187
+ this.lightStorageNode = storage( this.lightStorageAttr, 'vec4', 1 ).toReadOnly();
186
188
 
187
- // Light BVH storage buffer initialized with dummy data
188
- this.lightBVHStorageAttr = new StorageInstancedBufferAttribute( new Float32Array( 16 ), 4 );
189
- this.lightBVHStorageNode = storage( this.lightBVHStorageAttr, 'vec4', 1 ).toReadOnly();
189
+ // Cached CPU-side data — rebuilt into the packed buffer whenever either source changes.
190
+ this._lbvhDataCache = null;
191
+ this._emissiveDataCache = null;
190
192
 
191
- // Per-mesh visibility (storage buffer for TLAS BLAS-pointer skip)
192
- this.meshVisibilityStorageAttr = new StorageInstancedBufferAttribute( new Float32Array( [ 1, 0, 0, 0 ] ), 4 );
193
- this.meshVisibilityStorageNode = storage( this.meshVisibilityStorageAttr, 'vec4', 1 ).toReadOnly();
193
+ // Per-mesh visibility is packed into the TLAS BLAS-pointer leaf's slot [2]
194
+ // (see TLASBuilder.flatten + BVHTraversal.js). The InstanceTable holds the
195
+ // tlasLeafIndex for each mesh so we can patch visibility in place.
196
+ this._instanceTable = null;
194
197
 
195
198
  // Adaptive sampling
196
199
  this.adaptiveSamplingTexture = null;
@@ -454,6 +457,7 @@ export class PathTracer extends RenderStage {
454
457
  // Set data references
455
458
  this.setTriangleData( this.sdfs.triangleData, this.sdfs.triangleCount );
456
459
  this.setBVHData( this.sdfs.bvhData );
460
+ this.setInstanceTable( this.sdfs.instanceTable );
457
461
  this.materialData.setMaterialData( this.sdfs.materialData );
458
462
 
459
463
  // Update triangle count
@@ -768,61 +772,78 @@ export class PathTracer extends RenderStage {
768
772
  }
769
773
 
770
774
  /**
771
- * Build per-mesh visibility storage buffer from mesh world-visibility.
772
- * Each mesh gets one float (1.0 = visible, 0.0 = hidden).
773
- * Padded to vec4 alignment for GPU storage buffer compatibility.
774
- * @param {Array} meshes - Array of Three.js mesh objects
775
+ * Bind the InstanceTable used to locate each mesh's TLAS leaf for in-place
776
+ * visibility patching. Called by SceneProcessor during upload.
777
+ * @param {import('../Processor/InstanceTable.js').InstanceTable} instanceTable
775
778
  */
776
- setMeshVisibilityData( meshes ) {
779
+ setInstanceTable( instanceTable ) {
780
+
781
+ this._instanceTable = instanceTable;
782
+
783
+ }
777
784
 
778
- if ( ! meshes || meshes.length === 0 ) return;
785
+ /**
786
+ * Initialize packed visibility for each mesh from current world-visibility.
787
+ * Patches the TLAS leaf slots in the combined BVH buffer that was just uploaded.
788
+ * @param {Array} meshes - Array of Three.js mesh objects, ordered by meshIndex
789
+ */
790
+ setMeshVisibilityData( meshes ) {
779
791
 
780
- const meshCount = meshes.length;
781
- // One vec4 per mesh — visibility stored in .x (simple indexing on GPU)
782
- const data = new Float32Array( meshCount * 4 );
792
+ if ( ! meshes || meshes.length === 0 || ! this._instanceTable ) return;
783
793
 
784
- for ( let i = 0; i < meshCount; i ++ ) {
794
+ for ( let i = 0; i < meshes.length; i ++ ) {
785
795
 
786
- data[ i * 4 ] = this._isWorldVisible( meshes[ i ] ) ? 1.0 : 0.0;
796
+ this._patchTLASLeafVisibility( i, this._isWorldVisible( meshes[ i ] ) );
787
797
 
788
798
  }
789
799
 
790
- this.meshVisibilityStorageAttr = new StorageInstancedBufferAttribute( data, 4 );
791
- this.meshVisibilityStorageNode.value = this.meshVisibilityStorageAttr;
792
- this.meshVisibilityStorageNode.bufferCount = meshCount;
800
+ if ( this.bvhStorageAttr ) this.bvhStorageAttr.needsUpdate = true;
793
801
 
794
802
  }
795
803
 
796
804
  /**
797
- * Update visibility for a single mesh in the GPU buffer (no rebuild).
805
+ * Update visibility for a single mesh by patching its TLAS leaf slot [2].
798
806
  * @param {number} meshIndex
799
807
  * @param {boolean} visible
800
808
  */
801
809
  updateMeshVisibility( meshIndex, visible ) {
802
810
 
803
- if ( ! this.meshVisibilityStorageAttr ) return;
804
-
805
- this.meshVisibilityStorageAttr.array[ meshIndex * 4 ] = visible ? 1.0 : 0.0;
806
- this.meshVisibilityStorageAttr.needsUpdate = true;
811
+ if ( ! this._patchTLASLeafVisibility( meshIndex, visible ) ) return;
812
+ if ( this.bvhStorageAttr ) this.bvhStorageAttr.needsUpdate = true;
807
813
 
808
814
  }
809
815
 
810
816
  /**
811
- * Recompute world-visibility for all meshes and update the GPU buffer.
817
+ * Recompute world-visibility for all meshes and patch TLAS leaves in place.
812
818
  * Call this when group visibility changes at runtime.
813
819
  */
814
820
  updateAllMeshVisibility() {
815
821
 
816
- if ( ! this._meshRefs || ! this.meshVisibilityStorageAttr ) return;
822
+ if ( ! this._meshRefs || ! this._instanceTable ) return;
817
823
 
818
- const data = this.meshVisibilityStorageAttr.array;
819
824
  for ( let i = 0; i < this._meshRefs.length; i ++ ) {
820
825
 
821
- data[ i * 4 ] = this._isWorldVisible( this._meshRefs[ i ] ) ? 1.0 : 0.0;
826
+ this._patchTLASLeafVisibility( i, this._isWorldVisible( this._meshRefs[ i ] ) );
822
827
 
823
828
  }
824
829
 
825
- this.meshVisibilityStorageAttr.needsUpdate = true;
830
+ if ( this.bvhStorageAttr ) this.bvhStorageAttr.needsUpdate = true;
831
+
832
+ }
833
+
834
+ /**
835
+ * Patch a single TLAS leaf's visibility flag in the combined BVH buffer.
836
+ * Returns true if the patch was applied.
837
+ * @private
838
+ */
839
+ _patchTLASLeafVisibility( meshIndex, visible ) {
840
+
841
+ const entry = this._instanceTable?.entries?.[ meshIndex ];
842
+ if ( ! entry || entry.tlasLeafIndex < 0 || ! this.bvhStorageAttr ) return false;
843
+
844
+ entry.visible = visible;
845
+ this.bvhStorageAttr.array[ entry.tlasLeafIndex * 16 + 2 ] = visible ? 1.0 : 0.0;
846
+ return true;
826
847
 
827
848
  }
828
849
 
@@ -1135,9 +1156,6 @@ export class PathTracer extends RenderStage {
1135
1156
  // Update frame uniform
1136
1157
  this.frame.value = frameValue;
1137
1158
 
1138
- // Force-compile compute nodes on first frame
1139
- this.shaderBuilder.forceCompile( this.renderer );
1140
-
1141
1159
  // Set dispatch region — tile-only dispatch for tiled mode, full-screen otherwise
1142
1160
  if ( tileInfo.tileIndex >= 0 && tileInfo.tileBounds ) {
1143
1161
 
@@ -1496,18 +1514,43 @@ export class PathTracer extends RenderStage {
1496
1514
 
1497
1515
  }
1498
1516
 
1499
- setEmissiveTriangleData( emissiveData, count, totalPower = 0 ) {
1517
+ /**
1518
+ * Rebuild the packed light buffer from cached lightBVH + emissive data.
1519
+ * Layout: [ lightBVH (LBVH_STRIDE vec4s per node) | emissive (EMISSIVE_STRIDE vec4s per entry) ].
1520
+ * Also updates `emissiveVec4Offset` uniform (in vec4 elements).
1521
+ * @private
1522
+ */
1523
+ _rebuildLightBuffer() {
1500
1524
 
1501
- if ( ! emissiveData ) return;
1525
+ const LBVH_STRIDE = 4; // vec4s per LBVH node — must match LightBVHSampling.js
1526
+ const lbvh = this._lbvhDataCache;
1527
+ const emis = this._emissiveDataCache;
1528
+ const lbvhLen = lbvh ? lbvh.length : 0;
1529
+ const emisLen = emis ? emis.length : 0;
1502
1530
 
1503
- const vec4Count = emissiveData.length / 4;
1531
+ // Ensure at least a minimal non-empty buffer so GPU allocation remains valid.
1532
+ const totalLen = Math.max( lbvhLen + emisLen, 4 );
1533
+ const combined = new Float32Array( totalLen );
1534
+ if ( lbvh ) combined.set( lbvh, 0 );
1535
+ if ( emis ) combined.set( emis, lbvhLen );
1504
1536
 
1505
- this.emissiveTriangleStorageAttr = new StorageInstancedBufferAttribute( emissiveData, 4 );
1506
- this.emissiveTriangleStorageNode.value = this.emissiveTriangleStorageAttr;
1507
- this.emissiveTriangleStorageNode.bufferCount = vec4Count;
1537
+ this.lightStorageAttr = new StorageInstancedBufferAttribute( combined, 4 );
1538
+ this.lightStorageNode.value = this.lightStorageAttr;
1539
+ this.lightStorageNode.bufferCount = combined.length / 4;
1540
+
1541
+ // Offset (in vec4 elements) where emissive data starts.
1542
+ this.emissiveVec4Offset.value = ( this.lightBVHNodeCount.value || 0 ) * LBVH_STRIDE;
1543
+
1544
+ }
1545
+
1546
+ setEmissiveTriangleData( emissiveData, count, totalPower = 0 ) {
1547
+
1548
+ if ( ! emissiveData ) return;
1508
1549
 
1550
+ this._emissiveDataCache = emissiveData;
1509
1551
  this.emissiveTriangleCount.value = count;
1510
1552
  this.emissiveTotalPower.value = totalPower;
1553
+ this._rebuildLightBuffer();
1511
1554
  console.log( `PathTracer: ${count} emissive triangles, totalPower=${totalPower.toFixed( 4 )} (storage buffer)` );
1512
1555
 
1513
1556
  }
@@ -1516,11 +1559,9 @@ export class PathTracer extends RenderStage {
1516
1559
 
1517
1560
  if ( ! nodeData ) return;
1518
1561
 
1519
- const vec4Count = nodeData.length / 4;
1520
- this.lightBVHStorageAttr = new StorageInstancedBufferAttribute( nodeData, 4 );
1521
- this.lightBVHStorageNode.value = this.lightBVHStorageAttr;
1522
- this.lightBVHStorageNode.bufferCount = vec4Count;
1562
+ this._lbvhDataCache = nodeData;
1523
1563
  this.lightBVHNodeCount.value = nodeCount;
1564
+ this._rebuildLightBuffer();
1524
1565
  console.log( `PathTracer: Light BVH ${nodeCount} nodes` );
1525
1566
 
1526
1567
  }
@@ -1616,6 +1657,7 @@ export class PathTracer extends RenderStage {
1616
1657
  this.materialData?.dispose();
1617
1658
  this.environment?.dispose();
1618
1659
  this.shaderBuilder?.dispose();
1660
+ this.uniforms?.dispose();
1619
1661
 
1620
1662
  // Dispose storage textures
1621
1663
  this.storageTextures?.dispose();
@@ -175,10 +175,10 @@ export class SSRC extends RenderStage {
175
175
  this._dispatchX = Math.ceil( width / 8 );
176
176
  this._dispatchY = Math.ceil( height / 8 );
177
177
 
178
- const count = [ this._dispatchX, this._dispatchY, 1 ];
179
- if ( this._pass1NodeA ) this._pass1NodeA.setCount( count );
180
- if ( this._pass1NodeB ) this._pass1NodeB.setCount( count );
181
- if ( this._pass2Node ) this._pass2Node.setCount( count );
178
+ const dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
179
+ if ( this._pass1NodeA ) this._pass1NodeA.dispatchSize = dispatchSize;
180
+ if ( this._pass1NodeB ) this._pass1NodeB.dispatchSize = dispatchSize;
181
+ if ( this._pass2Node ) this._pass2Node.dispatchSize = dispatchSize;
182
182
 
183
183
  this._resetCache();
184
184
 
@@ -360,8 +360,8 @@ export class Variance extends RenderStage {
360
360
  // Update dispatch dimensions
361
361
  this._dispatchX = Math.ceil( width / 8 );
362
362
  this._dispatchY = Math.ceil( height / 8 );
363
- this._computeNodeA.setCount( [ this._dispatchX, this._dispatchY, 1 ] );
364
- this._computeNodeB.setCount( [ this._dispatchX, this._dispatchY, 1 ] );
363
+ this._computeNodeA.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
364
+ this._computeNodeB.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
365
365
 
366
366
  }
367
367
 
@@ -43,19 +43,8 @@ const BVH_STRIDE = 4;
43
43
  const TRI_STRIDE = 8;
44
44
  const HUGE_VAL = 1e8;
45
45
 
46
- // Per-mesh visibility buffer (set by ShaderBuilder before graph construction)
47
- let _meshVisibilityBuffer = null;
48
-
49
- /**
50
- * Set the per-mesh visibility storage buffer node.
51
- * Must be called before the shader graph is constructed (i.e., before setupCompute).
52
- * @param {StorageNode} buffer - TSL storage node indexed by meshIndex
53
- */
54
- export function setMeshVisibilityBuffer( buffer ) {
55
-
56
- _meshVisibilityBuffer = buffer;
57
-
58
- }
46
+ // Per-mesh visibility is now packed into the TLAS BLAS-pointer leaf's slot [2]
47
+ // by TLASBuilder.flatten() — eliminates the dedicated meshVisibility storage buffer.
59
48
 
60
49
  // ================================================================================
61
50
  // STACK HELPERS (Native WGSL array via TSL ArrayNode)
@@ -276,35 +265,17 @@ export const traverseBVH = Fn( ( [
276
265
 
277
266
  } ).Else( () => {
278
267
 
279
- // BLAS-pointer leaf (marker -2) — push BLAS root node onto stack
280
- // nodeData0: [blasRootNodeIndex, meshIndex, 0, -2]
268
+ // BLAS-pointer leaf (marker -2) — push BLAS root onto stack if mesh is visible
269
+ // nodeData0: [blasRootNodeIndex, meshIndex, visibility, -2]
270
+ // Visibility is free-fetched with the leaf — no extra storage read.
281
271
  const blasRoot = int( nodeData0.x ).toVar();
282
272
 
283
- if ( _meshVisibilityBuffer ) {
284
-
285
- // Per-mesh visibility check — skip entire BLAS if mesh is hidden
286
- // getDatafromStorageBuffer( buffer, stride=1, sampleIndex=meshIdx, dataOffset=0 )
287
- const meshIdx = int( nodeData0.y ).toVar();
288
- const meshVis = getDatafromStorageBuffer( _meshVisibilityBuffer, int( 1 ), meshIdx, int( 0 ) ).x;
289
-
290
- If( meshVis.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
291
-
292
- stack.element( stackPtr ).assign( blasRoot );
293
- stackPtr.addAssign( 1 );
294
-
295
- } );
296
-
297
- } else {
298
-
299
- // No visibility buffer — push unconditionally (original behavior)
300
- If( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ), () => {
301
-
302
- stack.element( stackPtr ).assign( blasRoot );
303
- stackPtr.addAssign( 1 );
273
+ If( nodeData0.z.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
304
274
 
305
- } );
275
+ stack.element( stackPtr ).assign( blasRoot );
276
+ stackPtr.addAssign( 1 );
306
277
 
307
- }
278
+ } );
308
279
 
309
280
  } );
310
281
 
@@ -466,35 +437,16 @@ export const traverseBVHShadow = Fn( ( [
466
437
 
467
438
  } ).Else( () => {
468
439
 
469
- // BLAS-pointer leaf (marker -2) — push BLAS root node onto stack
470
- // nodeData0: [blasRootNodeIndex, meshIndex, 0, -2]
440
+ // BLAS-pointer leaf (marker -2) — push BLAS root onto stack if mesh is visible
441
+ // nodeData0: [blasRootNodeIndex, meshIndex, visibility, -2]
471
442
  const blasRoot = int( nodeData0.x ).toVar();
472
443
 
473
- if ( _meshVisibilityBuffer ) {
474
-
475
- // Per-mesh visibility check — skip entire BLAS if mesh is hidden
476
- // getDatafromStorageBuffer( buffer, stride=1, sampleIndex=meshIdx, dataOffset=0 )
477
- const meshIdx = int( nodeData0.y ).toVar();
478
- const meshVis = getDatafromStorageBuffer( _meshVisibilityBuffer, int( 1 ), meshIdx, int( 0 ) ).x;
479
-
480
- If( meshVis.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
481
-
482
- stack.element( stackPtr ).assign( blasRoot );
483
- stackPtr.addAssign( 1 );
484
-
485
- } );
486
-
487
- } else {
444
+ If( nodeData0.z.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
488
445
 
489
- // No visibility buffer — push unconditionally (original behavior)
490
- If( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ), () => {
491
-
492
- stack.element( stackPtr ).assign( blasRoot );
493
- stackPtr.addAssign( 1 );
494
-
495
- } );
446
+ stack.element( stackPtr ).assign( blasRoot );
447
+ stackPtr.addAssign( 1 );
496
448
 
497
- }
449
+ } );
498
450
 
499
451
  } );
500
452
 
@@ -13,7 +13,7 @@ import {
13
13
  If,
14
14
  } from 'three/tsl';
15
15
 
16
- import { struct } from './structProxy.js';
16
+ import { struct } from './patches.js';
17
17
 
18
18
  import { Ray, HitInfo, RayTracingMaterial, DotProducts } from './Struct.js';
19
19
  import { PI, MIN_CLEARCOAT_ROUGHNESS, computeDotProducts } from './Common.js';
@@ -1,6 +1,6 @@
1
1
  import { Fn, float, vec2, int, If, Loop, abs, normalize, dot, max } from 'three/tsl';
2
2
 
3
- import { struct } from './structProxy.js';
3
+ import { struct } from './patches.js';
4
4
  import { getDatafromStorageBuffer } from './Common.js';
5
5
  import { sampleDisplacementMap } from './TextureSampling.js';
6
6
 
@@ -27,7 +27,7 @@ import {
27
27
  atan,
28
28
  } from 'three/tsl';
29
29
 
30
- import { struct } from './structProxy.js';
30
+ import { struct } from './patches.js';
31
31
  import { MIN_PDF, getDatafromStorageBuffer, powerHeuristic, MATERIAL_SLOTS, MATERIAL_SLOT } from './Common.js';
32
32
  import { RandomValue } from './Random.js';
33
33
  import { calculateMaterialPDF } from './LightsSampling.js';
@@ -361,8 +361,10 @@ export const calculateEmissiveLightPdf = Fn( ( [
361
361
  // ================================================================================
362
362
 
363
363
  // Binary search in CDF for importance-weighted triangle selection
364
- // CDF values are stored in the .b channel of the emissive buffer
365
- const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveTriangleCount, rand ] ) => {
364
+ // CDF values are stored in the .b channel of the emissive buffer.
365
+ // `emissiveOffset` is the vec4-element offset into the packed light buffer
366
+ // where emissive entries start (0 if using a non-packed buffer).
367
+ const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveOffset, emissiveTriangleCount, rand ] ) => {
366
368
 
367
369
  const lo = int( 0 ).toVar();
368
370
  const hi = emissiveTriangleCount.sub( 1 ).toVar();
@@ -370,7 +372,7 @@ const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveTriangleCount, r
370
372
  Loop( lo.lessThan( hi ), () => {
371
373
 
372
374
  const mid = lo.add( hi ).div( 2 ).toVar();
373
- const cdfVal = emissiveTriangleBuffer.element( mid.mul( EMISSIVE_STRIDE ) ).b;
375
+ const cdfVal = emissiveTriangleBuffer.element( emissiveOffset.add( mid.mul( int( EMISSIVE_STRIDE ) ) ) ).b;
374
376
 
375
377
  If( cdfVal.lessThan( rand ), () => {
376
378
 
@@ -388,11 +390,13 @@ const binarySearchCDF = Fn( ( [ emissiveTriangleBuffer, emissiveTriangleCount, r
388
390
 
389
391
  } );
390
392
 
391
- // Sample from emissive triangle index using CDF importance sampling
393
+ // Sample from emissive triangle index using CDF importance sampling.
394
+ // `emissiveTriangleBuffer` may be the shared packed light buffer; `emissiveVec4Offset`
395
+ // gives the vec4 offset where emissive entries begin.
392
396
  export const sampleEmissiveTriangle = Fn( ( [
393
397
  hitPoint, surfaceNormal, totalTriangleCount,
394
398
  rngState,
395
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
399
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
396
400
  triangleBuffer,
397
401
  ] ) => {
398
402
 
@@ -413,12 +417,12 @@ export const sampleEmissiveTriangle = Fn( ( [
413
417
 
414
418
  // CDF importance-weighted triangle selection (brighter triangles sampled more)
415
419
  const randEmissive = RandomValue( rngState );
416
- const emissiveIndex = binarySearchCDF( emissiveTriangleBuffer, emissiveTriangleCount, randEmissive ).toVar();
420
+ const emissiveIndex = binarySearchCDF( emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, randEmissive ).toVar();
417
421
 
418
- // Fetch emissive triangle data from storage buffer (2 vec4s per entry)
422
+ // Fetch emissive triangle data from packed light buffer (2 vec4s per entry)
419
423
  // vec4[0] = (triangleIndex, power, cdf, selectionPdf)
420
424
  // vec4[1] = (emission.r, emission.g, emission.b, area)
421
- const baseIdx = emissiveIndex.mul( EMISSIVE_STRIDE );
425
+ const baseIdx = emissiveVec4Offset.add( emissiveIndex.mul( int( EMISSIVE_STRIDE ) ) );
422
426
  const emissiveData0 = emissiveTriangleBuffer.element( baseIdx );
423
427
  const emissiveData1 = emissiveTriangleBuffer.element( baseIdx.add( 1 ) );
424
428
  const triangleIndex = int( emissiveData0.r );
@@ -534,7 +538,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
534
538
  hitPoint, normal, viewDir, material,
535
539
  totalTriangleCount, bounceIndex, rngState,
536
540
  emissiveBoost,
537
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
541
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
538
542
  triangleBuffer,
539
543
  // Callback functions to avoid circular deps
540
544
  traceShadowRayFn,
@@ -557,7 +561,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
557
561
  // Sample emissive triangle (CDF importance-weighted)
558
562
  const emissiveSample = EmissiveSample.wrap( sampleEmissiveTriangle(
559
563
  hitPoint, normal, totalTriangleCount, rngState,
560
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
564
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
561
565
  triangleBuffer,
562
566
  ) );
563
567
 
@@ -619,7 +623,7 @@ export const calculateEmissiveTriangleContribution = Fn( ( [
619
623
  hitPoint, normal, viewDir, material,
620
624
  totalTriangleCount, bounceIndex, rngState,
621
625
  emissiveBoost,
622
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
626
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
623
627
  triangleBuffer,
624
628
  traceShadowRayFn,
625
629
  evaluateMaterialResponseFn,
@@ -630,7 +634,7 @@ export const calculateEmissiveTriangleContribution = Fn( ( [
630
634
  hitPoint, normal, viewDir, material,
631
635
  totalTriangleCount, bounceIndex, rngState,
632
636
  emissiveBoost,
633
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower,
637
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower,
634
638
  triangleBuffer,
635
639
  traceShadowRayFn,
636
640
  evaluateMaterialResponseFn,
@@ -82,8 +82,7 @@ export const sampleEquirect = Fn( ( [ environment, direction, environmentMatrix,
82
82
  // Exact implementation from three-gpu-pathtracer
83
83
  export const sampleEquirectProbability = Fn( ( [
84
84
  environment,
85
- envMarginalWeights,
86
- envConditionalWeights,
85
+ envCDFBuffer,
87
86
  environmentMatrix,
88
87
  environmentIntensity,
89
88
  envTotalSum,
@@ -92,15 +91,19 @@ export const sampleEquirectProbability = Fn( ( [
92
91
  colorOutput
93
92
  ] ) => {
94
93
 
95
- // Sample marginal CDF for V coordinate (1D storage buffer, linear interpolation)
94
+ // Packed CDF layout: [marginal (envResolution.y floats) | conditional (envResolution.x * envResolution.y floats)]
95
+ // The conditional offset equals the marginal length, which is envResolution.y.
96
+ const condOffset = int( envResolution.y ).toVar();
97
+
98
+ // Sample marginal CDF for V coordinate (1D, linear interpolation)
96
99
  const marginalSize = envResolution.y;
97
100
  const mIdx = clamp( r.x.mul( marginalSize.sub( 1.0 ) ), 0.0, marginalSize.sub( 1.0 ) );
98
101
  const mI0 = int( floor( mIdx ) );
99
102
  const mI1 = min( mI0.add( 1 ), int( marginalSize ).sub( 1 ) );
100
103
  const mFrac = fract( mIdx );
101
- const v = mix( envMarginalWeights.element( mI0 ), envMarginalWeights.element( mI1 ), mFrac ).toVar();
104
+ const v = mix( envCDFBuffer.element( mI0 ), envCDFBuffer.element( mI1 ), mFrac ).toVar();
102
105
 
103
- // Sample conditional CDF for U coordinate (2D storage buffer, bilinear interpolation)
106
+ // Sample conditional CDF for U coordinate (2D grid, bilinear interpolation)
104
107
  const condW = envResolution.x;
105
108
  const condH = envResolution.y;
106
109
  const cxf = clamp( r.y.mul( condW.sub( 1.0 ) ), 0.0, condW.sub( 1.0 ) );
@@ -112,10 +115,10 @@ export const sampleEquirectProbability = Fn( ( [
112
115
  const fx = fract( cxf );
113
116
  const fy = fract( cyf );
114
117
  const condWi = int( condW );
115
- const v00 = envConditionalWeights.element( cy0.mul( condWi ).add( cx0 ) );
116
- const v10 = envConditionalWeights.element( cy0.mul( condWi ).add( cx1 ) );
117
- const v01 = envConditionalWeights.element( cy1.mul( condWi ).add( cx0 ) );
118
- const v11 = envConditionalWeights.element( cy1.mul( condWi ).add( cx1 ) );
118
+ const v00 = envCDFBuffer.element( condOffset.add( cy0.mul( condWi ).add( cx0 ) ) );
119
+ const v10 = envCDFBuffer.element( condOffset.add( cy0.mul( condWi ).add( cx1 ) ) );
120
+ const v01 = envCDFBuffer.element( condOffset.add( cy1.mul( condWi ).add( cx0 ) ) );
121
+ const v11 = envCDFBuffer.element( condOffset.add( cy1.mul( condWi ).add( cx1 ) ) );
119
122
  const u = mix( mix( v00, v10, fx ), mix( v01, v11, fx ), fy ).toVar();
120
123
 
121
124
  const uv = vec2( u, v ).toVar();
@@ -53,6 +53,7 @@ export const sampleLightBVHTriangle = Fn( ( [
53
53
  rngState,
54
54
  lbvhBuffer,
55
55
  emissiveTriangleBuffer,
56
+ emissiveVec4Offset,
56
57
  triangleBuffer,
57
58
  ] ) => {
58
59
 
@@ -185,7 +186,7 @@ export const sampleLightBVHTriangle = Fn( ( [
185
186
  Loop( { start: int( 0 ), end: emissiveCount }, ( { i } ) => {
186
187
 
187
188
  const entryIdx = emissiveStart.add( i );
188
- const baseIdx = entryIdx.mul( int( EMISSIVE_STRIDE ) );
189
+ const baseIdx = emissiveVec4Offset.add( entryIdx.mul( int( EMISSIVE_STRIDE ) ) );
189
190
  const emData0 = emissiveTriangleBuffer.element( baseIdx );
190
191
  const triPower = max( emData0.g, float( 0.0 ) );
191
192
  cumPower.addAssign( triPower );
@@ -204,7 +205,7 @@ export const sampleLightBVHTriangle = Fn( ( [
204
205
  selectionPdf.mulAssign( selectedPower.div( leafTotalPower ) );
205
206
 
206
207
  // Now sample the selected triangle (same path as flat CDF sampling)
207
- const baseIdx = selectedEmissiveIndex.mul( int( EMISSIVE_STRIDE ) );
208
+ const baseIdx = emissiveVec4Offset.add( selectedEmissiveIndex.mul( int( EMISSIVE_STRIDE ) ) );
208
209
  const emissiveData0 = emissiveTriangleBuffer.element( baseIdx );
209
210
  const emissiveData1 = emissiveTriangleBuffer.element( baseIdx.add( int( 1 ) ) );
210
211
 
@@ -13,7 +13,7 @@ import {
13
13
  abs,
14
14
  } from 'three/tsl';
15
15
 
16
- import { struct } from './structProxy.js';
16
+ import { struct } from './patches.js';
17
17
 
18
18
  // ================================================================================
19
19
  // LIGHT STRUCTURES
@@ -41,7 +41,7 @@ import { RandomValue } from './Random.js';
41
41
  import { getTransformedUV } from './TextureSampling.js';
42
42
 
43
43
  // Module-level state for alpha-cutout shadow testing.
44
- // Set by ShaderBuilder before graph construction (same pattern as _meshVisibilityBuffer in BVHTraversal.js).
44
+ // Set by ShaderBuilder before graph construction.
45
45
  let _shadowAlbedoMaps = null;
46
46
  let _enableAlphaShadows = null;
47
47
 
@@ -273,7 +273,6 @@ export const calculateIndirectLighting = Fn( ( [
273
273
  samplingInfo,
274
274
  // Environment resources
275
275
  envTexture, environmentIntensity, envMatrix,
276
- envMarginalWeights, envConditionalWeights,
277
276
  envTotalSum, envResolution,
278
277
  enableEnvironmentLight, useEnvMapIS,
279
278
  ] ) => {
@@ -934,7 +934,7 @@ export const calculateDirectLightingUnified = Fn( ( [
934
934
  materialBuffer,
935
935
  // Environment resources
936
936
  envTexture, environmentIntensity, envMatrix,
937
- envMarginalWeights, envConditionalWeights,
937
+ envCDFBuffer,
938
938
  envTotalSum, envResolution,
939
939
  enableEnvironmentLight,
940
940
  ] ) => {
@@ -1203,7 +1203,7 @@ export const calculateDirectLightingUnified = Fn( ( [
1203
1203
 
1204
1204
  // Sample direction + PDF + color from importance-sampled environment
1205
1205
  const envSampleResult = sampleEquirectProbability(
1206
- envTexture, envMarginalWeights, envConditionalWeights,
1206
+ envTexture, envCDFBuffer,
1207
1207
  envMatrix, environmentIntensity, envTotalSum, envResolution, envRandom, envColor
1208
1208
  ).toVar();
1209
1209
 
@@ -30,7 +30,7 @@ import {
30
30
  fract,
31
31
  } from 'three/tsl';
32
32
 
33
- import { struct } from './structProxy.js';
33
+ import { struct } from './patches.js';
34
34
  import { Ray, RayTracingMaterial, RenderState, HitInfo, DotProducts, DirectionSample } from './Struct.js';
35
35
  import { PI, EPSILON, MIN_ROUGHNESS, MIN_CLEARCOAT_ROUGHNESS, computeDotProducts } from './Common.js';
36
36
  import { iorToFresnel0, fresnelSchlickFloat } from './Fresnel.js';
@@ -137,14 +137,14 @@ export const pathTracerMain = ( params ) => {
137
137
  pointLightsBuffer, numPointLights,
138
138
  spotLightsBuffer, numSpotLights,
139
139
  envTexture, environmentIntensity, envMatrix,
140
- envMarginalWeights, envConditionalWeights,
140
+ envCDFBuffer,
141
141
  envTotalSum, envResolution,
142
142
  enableEnvironmentLight, useEnvMapIS,
143
143
  maxBounceCount, transmissiveBounces,
144
144
  showBackground, transparentBackground, backgroundIntensity,
145
145
  fireflyThreshold, globalIlluminationIntensity,
146
146
  totalTriangleCount, enableEmissiveTriangleSampling,
147
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
147
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
148
148
  lightBVHBuffer, lightBVHNodeCount,
149
149
  debugVisScale,
150
150
  enableAccumulation, hasPreviousAccumulated,
@@ -284,14 +284,14 @@ export const pathTracerMain = ( params ) => {
284
284
  pointLightsBuffer, numPointLights,
285
285
  spotLightsBuffer, numSpotLights,
286
286
  envTexture, environmentIntensity, envMatrix,
287
- envMarginalWeights, envConditionalWeights,
287
+ envCDFBuffer,
288
288
  envTotalSum, envResolution,
289
289
  enableEnvironmentLight, useEnvMapIS,
290
290
  maxBounceCount, transmissiveBounces,
291
291
  backgroundIntensity, showBackground, transparentBackground,
292
292
  fireflyThreshold, globalIlluminationIntensity,
293
293
  totalTriangleCount, enableEmissiveTriangleSampling,
294
- emissiveTriangleBuffer, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
294
+ emissiveTriangleBuffer, emissiveVec4Offset, emissiveTriangleCount, emissiveTotalPower, emissiveBoost,
295
295
  lightBVHBuffer, lightBVHNodeCount,
296
296
  pixelCoord, resolution, frame,
297
297
  ) );