rayzee 5.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "5.0.1",
3
+ "version": "5.1.0",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -34,7 +34,7 @@
34
34
  "prepublishOnly": "npm run build"
35
35
  },
36
36
  "peerDependencies": {
37
- "three": ">=0.170.0"
37
+ "three": ">=0.183.2"
38
38
  },
39
39
  "dependencies": {
40
40
  "stats-gl": "^4.0.2"
@@ -533,6 +533,11 @@ export class PathTracerApp extends EventDispatcher {
533
533
 
534
534
  if ( ! this._sdf.uploadToPathTracer( this.stages.pathTracer, this.lightManager, this.meshScene, environmentTexture ) ) return false;
535
535
 
536
+ // Build per-mesh visibility buffer (must happen before setupMaterial so the
537
+ // shader graph captures the storage node during compilation)
538
+ this.stages.pathTracer._meshRefs = this.stages.pathTracer._collectMeshRefs( this.meshScene );
539
+ this.stages.pathTracer.setMeshVisibilityData( this.stages.pathTracer._meshRefs );
540
+
536
541
  timer.end( 'GPU data transfer' );
537
542
 
538
543
  // Compile shaders
@@ -877,7 +882,7 @@ export class PathTracerApp extends EventDispatcher {
877
882
 
878
883
  this.stages.pathTracer?.materialData.updateMaterialProperty( materialIndex, property, value );
879
884
 
880
- const emissiveAffectingProps = [ 'emissive', 'emissiveIntensity', 'visible' ];
885
+ const emissiveAffectingProps = [ 'emissive', 'emissiveIntensity' ];
881
886
  if ( emissiveAffectingProps.includes( property )
882
887
  && this.stages.pathTracer?.enableEmissiveTriangleSampling?.value ) {
883
888
 
@@ -896,6 +901,30 @@ export class PathTracerApp extends EventDispatcher {
896
901
 
897
902
  }
898
903
 
904
+ /**
905
+ * Update per-mesh visibility without rebuilding the scene.
906
+ * Walks the parent chain to resolve world-space visibility.
907
+ * @param {number} meshIndex
908
+ * @param {boolean} visible
909
+ */
910
+ setMeshVisibility( meshIndex, visible ) {
911
+
912
+ this.stages.pathTracer?.updateMeshVisibility( meshIndex, visible );
913
+ this.reset();
914
+
915
+ }
916
+
917
+ /**
918
+ * Recompute world-visibility for all meshes.
919
+ * Call after changing visibility on groups or parent objects.
920
+ */
921
+ updateAllMeshVisibility() {
922
+
923
+ this.stages.pathTracer?.updateAllMeshVisibility();
924
+ this.reset();
925
+
926
+ }
927
+
899
928
  /**
900
929
  * Updates a material's texture transform (offset, repeat, rotation).
901
930
  * @param {number} materialIndex
@@ -50,12 +50,11 @@ export class EmissiveTriangleBuilder {
50
50
  const material = materials[ materialIndex ];
51
51
  if ( ! material ) continue;
52
52
 
53
- // Check if emissive and visible
53
+ // Check if emissive
54
54
  const emissive = material.emissive || { r: 0, g: 0, b: 0 };
55
55
  const emissiveIntensity = material.emissiveIntensity || 0;
56
- const isVisible = material.visible === undefined || material.visible !== 0;
57
56
 
58
- const isEmissive = isVisible && emissiveIntensity > 0 && (
57
+ const isEmissive = emissiveIntensity > 0 && (
59
58
  emissive.r > 0 || emissive.g > 0 || emissive.b > 0
60
59
  );
61
60
 
@@ -410,8 +409,7 @@ export class EmissiveTriangleBuilder {
410
409
 
411
410
  const emissive = material.emissive || { r: 0, g: 0, b: 0 };
412
411
  const emissiveIntensity = material.emissiveIntensity || 0;
413
- const isVisible = material.visible === undefined || material.visible !== 0;
414
- const isNowEmissive = isVisible && emissiveIntensity > 0 && ( emissive.r > 0 || emissive.g > 0 || emissive.b > 0 );
412
+ const isNowEmissive = emissiveIntensity > 0 && ( emissive.r > 0 || emissive.g > 0 || emissive.b > 0 );
415
413
 
416
414
  // Check if this material had any emissive triangles before
417
415
  const hadEmissive = this.emissiveTriangles.some( t => t.materialIndex === materialIndex );
@@ -114,7 +114,7 @@ export class GeometryExtractor {
114
114
  }
115
115
 
116
116
  // Process material and get its index
117
- const materialIndex = this.processMaterial( mesh.material, mesh );
117
+ const materialIndex = this.processMaterial( mesh.material );
118
118
  mesh.userData.materialIndex = materialIndex;
119
119
 
120
120
  // Assign mesh index and store mesh reference
@@ -133,7 +133,7 @@ export class GeometryExtractor {
133
133
 
134
134
  }
135
135
 
136
- processMaterial( material, mesh = null ) {
136
+ processMaterial( material ) {
137
137
 
138
138
  // Check if material already exists in our array (O(1) Map lookup)
139
139
  let materialIndex = this._materialUuidMap.get( material.uuid ) ?? - 1;
@@ -148,7 +148,7 @@ export class GeometryExtractor {
148
148
  }
149
149
 
150
150
  // Create a new material object and add it to the array
151
- const newMaterial = this.createMaterialObject( material, mesh );
151
+ const newMaterial = this.createMaterialObject( material );
152
152
  this.materials.push( newMaterial );
153
153
  materialIndex = this.materials.length - 1;
154
154
  this._materialUuidMap.set( material.uuid, materialIndex );
@@ -327,7 +327,7 @@ export class GeometryExtractor {
327
327
 
328
328
  }
329
329
 
330
- createMaterialObject( material, mesh = null ) {
330
+ createMaterialObject( material ) {
331
331
 
332
332
  const defaults = this.getPhysicalDefaults();
333
333
  const materialType = this.getMaterialType( material );
@@ -407,8 +407,6 @@ export class GeometryExtractor {
407
407
  // Rendering properties
408
408
  side: this.getMaterialSide( material ),
409
409
  depthWrite: material.depthWrite ?? true ? 1 : 0,
410
- // Use mesh visibility if available, otherwise fall back to material or default to true
411
- visible: mesh ? ( mesh.visible ? 1 : 0 ) : ( material.visible ?? true ? 1 : 0 ),
412
410
 
413
411
  // Texture processing
414
412
  map: this.processTexture( material.map, this.maps ),
@@ -757,7 +755,7 @@ export class GeometryExtractor {
757
755
 
758
756
  if ( object.isMesh && object.geometry && object.material ) {
759
757
 
760
- const materialIndex = this.processMaterial( object.material, object );
758
+ const materialIndex = this.processMaterial( object.material );
761
759
  object.userData.materialIndex = materialIndex;
762
760
 
763
761
  const meshIndex = this.meshes.length;
@@ -1413,7 +1413,7 @@ export class SceneProcessor {
1413
1413
  * Returns null if no change, or the updated emissive data for GPU upload.
1414
1414
  *
1415
1415
  * @param {number} materialIndex
1416
- * @param {string} property - 'emissive' | 'emissiveIntensity' | 'visible'
1416
+ * @param {string} property - 'emissive' | 'emissiveIntensity'
1417
1417
  * @param {*} value
1418
1418
  * @returns {{ rawData: Float32Array, emissiveCount: number, totalPower: number }|null}
1419
1419
  */
@@ -1426,7 +1426,6 @@ export class SceneProcessor {
1426
1426
 
1427
1427
  if ( property === 'emissive' ) mat.emissive = value;
1428
1428
  else if ( property === 'emissiveIntensity' ) mat.emissiveIntensity = value;
1429
- else if ( property === 'visible' ) mat.visible = value;
1430
1429
 
1431
1430
  const changed = this.emissiveTriangleBuilder.updateMaterialEmissive(
1432
1431
  materialIndex, mat,
@@ -16,6 +16,7 @@ import { Fn, texture, vec2, float, int, uniform, If,
16
16
  import { TextureNode } from 'three/webgpu';
17
17
  import { LinearFilter, DataArrayTexture } from 'three';
18
18
  import { pathTracerMain } from '../TSL/PathTracer.js';
19
+ import { setMeshVisibilityBuffer } from '../TSL/BVHTraversal.js';
19
20
  import { BuildTimer } from './BuildTimer.js';
20
21
 
21
22
  const WG_SIZE = 8;
@@ -188,6 +189,9 @@ export class ShaderBuilder {
188
189
  const emissiveTriStorage = stage.emissiveTriangleStorageNode;
189
190
  const lightBVHStorage = stage.lightBVHStorageNode;
190
191
 
192
+ // Set per-mesh visibility buffer (module-level in BVHTraversal.js, read during graph construction)
193
+ setMeshVisibilityBuffer( stage.meshVisibilityStorageNode );
194
+
191
195
  const envTex = texture( stage.environment.environmentTexture );
192
196
 
193
197
  // Adaptive sampling texture
@@ -268,7 +268,7 @@ export class TLASBuilder {
268
268
  // Leaf node — BLAS pointer
269
269
  const entry = entries[ n.entryIndex ];
270
270
  data[ o ] = entry.blasOffset; // Absolute node index of BLAS root in combined buffer
271
- // data[o+1] = 0
271
+ data[ o + 1 ] = n.entryIndex; // meshIndex for per-mesh visibility check
272
272
  // data[o+2] = 0
273
273
  data[ o + 3 ] = BVH_LEAF_MARKERS.BLAS_POINTER_LEAF; // -2 marker
274
274
 
@@ -188,6 +188,10 @@ export class PathTracer extends RenderStage {
188
188
  this.lightBVHStorageAttr = new StorageInstancedBufferAttribute( new Float32Array( 16 ), 4 );
189
189
  this.lightBVHStorageNode = storage( this.lightBVHStorageAttr, 'vec4', 1 ).toReadOnly();
190
190
 
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();
194
+
191
195
  // Adaptive sampling
192
196
  this.adaptiveSamplingTexture = null;
193
197
 
@@ -480,6 +484,10 @@ export class PathTracer extends RenderStage {
480
484
 
481
485
  }
482
486
 
487
+ // Per-mesh visibility — collect meshes from scene ordered by meshIndex
488
+ this._meshRefs = this._collectMeshRefs( this.scene );
489
+ this.setMeshVisibilityData( this._meshRefs );
490
+
483
491
  // Spheres
484
492
  this.spheres = this.sdfs.spheres || [];
485
493
 
@@ -759,6 +767,109 @@ export class PathTracer extends RenderStage {
759
767
 
760
768
  }
761
769
 
770
+ /**
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
+ */
776
+ setMeshVisibilityData( meshes ) {
777
+
778
+ if ( ! meshes || meshes.length === 0 ) return;
779
+
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 );
783
+
784
+ for ( let i = 0; i < meshCount; i ++ ) {
785
+
786
+ data[ i * 4 ] = this._isWorldVisible( meshes[ i ] ) ? 1.0 : 0.0;
787
+
788
+ }
789
+
790
+ this.meshVisibilityStorageAttr = new StorageInstancedBufferAttribute( data, 4 );
791
+ this.meshVisibilityStorageNode.value = this.meshVisibilityStorageAttr;
792
+ this.meshVisibilityStorageNode.bufferCount = meshCount;
793
+
794
+ }
795
+
796
+ /**
797
+ * Update visibility for a single mesh in the GPU buffer (no rebuild).
798
+ * @param {number} meshIndex
799
+ * @param {boolean} visible
800
+ */
801
+ updateMeshVisibility( meshIndex, visible ) {
802
+
803
+ if ( ! this.meshVisibilityStorageAttr ) return;
804
+
805
+ this.meshVisibilityStorageAttr.array[ meshIndex * 4 ] = visible ? 1.0 : 0.0;
806
+ this.meshVisibilityStorageAttr.needsUpdate = true;
807
+
808
+ }
809
+
810
+ /**
811
+ * Recompute world-visibility for all meshes and update the GPU buffer.
812
+ * Call this when group visibility changes at runtime.
813
+ */
814
+ updateAllMeshVisibility() {
815
+
816
+ if ( ! this._meshRefs || ! this.meshVisibilityStorageAttr ) return;
817
+
818
+ const data = this.meshVisibilityStorageAttr.array;
819
+ for ( let i = 0; i < this._meshRefs.length; i ++ ) {
820
+
821
+ data[ i * 4 ] = this._isWorldVisible( this._meshRefs[ i ] ) ? 1.0 : 0.0;
822
+
823
+ }
824
+
825
+ this.meshVisibilityStorageAttr.needsUpdate = true;
826
+
827
+ }
828
+
829
+ /**
830
+ * Collect mesh references from scene, ordered by meshIndex (assigned during extraction).
831
+ * @param {Object3D} scene
832
+ * @returns {Array}
833
+ * @private
834
+ */
835
+ _collectMeshRefs( scene ) {
836
+
837
+ if ( ! scene ) return [];
838
+
839
+ const meshes = [];
840
+ scene.traverse( obj => {
841
+
842
+ if ( obj.isMesh && obj.userData.meshIndex !== undefined ) {
843
+
844
+ meshes[ obj.userData.meshIndex ] = obj;
845
+
846
+ }
847
+
848
+ } );
849
+
850
+ return meshes;
851
+
852
+ }
853
+
854
+ /**
855
+ * Walk the parent chain to determine world-space visibility.
856
+ * @param {Object3D} object
857
+ * @returns {boolean}
858
+ * @private
859
+ */
860
+ _isWorldVisible( object ) {
861
+
862
+ while ( object ) {
863
+
864
+ if ( ! object.visible ) return false;
865
+ object = object.parent;
866
+
867
+ }
868
+
869
+ return true;
870
+
871
+ }
872
+
762
873
  // ===== FAST BUFFER UPDATES (BVH Refit / Animation) =====
763
874
 
764
875
  /**
@@ -25,7 +25,6 @@ import {
25
25
  array,
26
26
  } from 'three/tsl';
27
27
 
28
- import { struct } from './structProxy.js';
29
28
  import { Ray, HitInfo } from './Struct.js';
30
29
  import { getDatafromStorageBuffer, MATERIAL_SLOTS } from './Common.js';
31
30
  import { RandomPointInCircle } from './Random.js';
@@ -34,14 +33,6 @@ import { RandomPointInCircle } from './Random.js';
34
33
  // STRUCTS
35
34
  // ================================================================================
36
35
 
37
- // Combined visibility data structure
38
- export const VisibilityData = struct( {
39
- visible: 'bool',
40
- side: 'int',
41
- transparent: 'bool',
42
- opacity: 'float'
43
- } );
44
-
45
36
  // ================================================================================
46
37
  // CONSTANTS
47
38
  // ================================================================================
@@ -52,6 +43,20 @@ const BVH_STRIDE = 4;
52
43
  const TRI_STRIDE = 8;
53
44
  const HUGE_VAL = 1e8;
54
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
+ }
59
+
55
60
  // ================================================================================
56
61
  // STACK HELPERS (Native WGSL array via TSL ArrayNode)
57
62
  // ================================================================================
@@ -127,48 +132,17 @@ const fastRayAABBDst = wgslFn( `
127
132
  // VISIBILITY FUNCTIONS
128
133
  // ================================================================================
129
134
 
130
- // Fetch all visibility data in 2 reads
131
- export const getVisibilityData = Fn( ( [ materialIndex, materialBuffer ] ) => {
135
+ // Side culling 1 buffer read (slot 10 only)
136
+ // Per-mesh visibility handled at BLAS-pointer level; material visibility always 1.
137
+ export const passesSideCulling = Fn( ( [ materialIndex, rayDirection, normal, materialBuffer ] ) => {
132
138
 
133
- // Read visibility flag from slot 4
134
- const visData = getDatafromStorageBuffer( materialBuffer, materialIndex, int( 4 ), int( MATERIAL_SLOTS ) );
135
- // Read side and transparency data from slot 10
136
139
  const sideData = getDatafromStorageBuffer( materialBuffer, materialIndex, int( 10 ), int( MATERIAL_SLOTS ) );
137
-
138
- return VisibilityData( {
139
- visible: visData.g.greaterThan( 0.5 ),
140
- opacity: sideData.r,
141
- side: int( sideData.g ),
142
- transparent: sideData.b.greaterThan( 0.5 ),
143
- } );
144
-
145
- } );
146
-
147
- // Fast visibility check using material texture
148
- export const isTriangleVisible = Fn( ( [ materialIndex, materialBuffer ] ) => {
149
-
150
- const visData = getDatafromStorageBuffer( materialBuffer, materialIndex, int( 4 ), int( MATERIAL_SLOTS ) );
151
- return visData.g.greaterThan( 0.5 );
152
-
153
- } );
154
-
155
- // Complete visibility check with side culling
156
- export const isMaterialVisibleOptimized = wgslFn( `
157
- fn isMaterialVisibleOptimized( visible: bool, side: i32, rayDirection: vec3f, normal: vec3f ) -> bool {
158
- if ( !visible ) { return false; }
159
- let rayDotNormal = dot( rayDirection, normal );
160
- let doubleSide = side == 2;
161
- let frontSide = side == 0 && rayDotNormal < -0.0001f;
162
- let backSide = side == 1 && rayDotNormal > 0.0001f;
163
- return doubleSide || frontSide || backSide;
164
- }
165
- ` );
166
-
167
- // Single visibility check with combined data fetch
168
- export const isMaterialVisible = Fn( ( [ materialIndex, rayDirection, normal, materialBuffer ] ) => {
169
-
170
- const vis = VisibilityData.wrap( getVisibilityData( materialIndex, materialBuffer ) );
171
- return isMaterialVisibleOptimized( { visible: vis.visible, side: vis.side, rayDirection, normal } );
140
+ const side = int( sideData.g );
141
+ const rayDotNormal = rayDirection.dot( normal );
142
+ const doubleSide = side.equal( int( 2 ) );
143
+ const frontSide = side.equal( int( 0 ) ).and( rayDotNormal.lessThan( - 0.0001 ) );
144
+ const backSide = side.equal( int( 1 ) ).and( rayDotNormal.greaterThan( 0.0001 ) );
145
+ return doubleSide.or( frontSide ).or( backSide );
172
146
 
173
147
  } );
174
148
 
@@ -269,28 +243,23 @@ export const traverseBVH = Fn( ( [
269
243
 
270
244
  const matIdx = int( uvData2.z );
271
245
 
272
- // Early material rejection
273
- If( isTriangleVisible( matIdx, materialBuffer ), () => {
246
+ // Interpolate normal
247
+ const w = float( 1.0 ).sub( u ).sub( v );
248
+ const normal = normalize( nA.mul( w ).add( nB.mul( u ) ).add( nC.mul( v ) ) ).toVar();
274
249
 
275
- // Interpolate normal
276
- const w = float( 1.0 ).sub( u ).sub( v );
277
- const normal = normalize( nA.mul( w ).add( nB.mul( u ) ).add( nC.mul( v ) ) ).toVar();
250
+ // Side culling check (per-mesh visibility handled at BLAS-pointer level)
251
+ If( passesSideCulling( matIdx, rayDirection, normal, materialBuffer ), () => {
278
252
 
279
- // Full material visibility check (culling etc)
280
- If( isMaterialVisible( matIdx, rayDirection, normal, materialBuffer ), () => {
281
-
282
- closestHit.didHit.assign( true );
283
- closestHit.dst.assign( t );
284
- closestHit.normal.assign( normal );
285
- closestHit.materialIndex.assign( matIdx );
286
- closestHit.meshIndex.assign( int( uvData2.w ) );
287
-
288
- // Defer hitPoint + UV computation to post-traversal
289
- closestTriIdx.assign( triIndex );
290
- closestU.assign( u );
291
- closestV.assign( v );
253
+ closestHit.didHit.assign( true );
254
+ closestHit.dst.assign( t );
255
+ closestHit.normal.assign( normal );
256
+ closestHit.materialIndex.assign( matIdx );
257
+ closestHit.meshIndex.assign( int( uvData2.w ) );
292
258
 
293
- } );
259
+ // Defer hitPoint + UV computation to post-traversal
260
+ closestTriIdx.assign( triIndex );
261
+ closestU.assign( u );
262
+ closestV.assign( v );
294
263
 
295
264
  } );
296
265
 
@@ -308,13 +277,34 @@ export const traverseBVH = Fn( ( [
308
277
  } ).Else( () => {
309
278
 
310
279
  // BLAS-pointer leaf (marker -2) — push BLAS root node onto stack
280
+ // nodeData0: [blasRootNodeIndex, meshIndex, 0, -2]
311
281
  const blasRoot = int( nodeData0.x ).toVar();
312
- If( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ), () => {
313
282
 
314
- stack.element( stackPtr ).assign( blasRoot );
315
- stackPtr.addAssign( 1 );
283
+ if ( _meshVisibilityBuffer ) {
316
284
 
317
- } );
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 );
304
+
305
+ } );
306
+
307
+ }
318
308
 
319
309
  } );
320
310
 
@@ -390,7 +380,7 @@ export const traverseBVHShadow = Fn( ( [
390
380
  ray,
391
381
  bvhBuffer,
392
382
  triangleBuffer,
393
- materialBuffer,
383
+ _materialBuffer, // eslint-disable-line no-unused-vars -- kept for call-site compatibility
394
384
  maxShadowDist,
395
385
  ] ) => {
396
386
 
@@ -448,25 +438,21 @@ export const traverseBVHShadow = Fn( ( [
448
438
 
449
439
  If( triResult.w.greaterThan( 0.5 ), () => {
450
440
 
441
+ // Per-mesh visibility handled at BLAS-pointer level — accept any hit
451
442
  const uvData2 = getDatafromStorageBuffer( triangleBuffer, triIndex, int( 7 ), int( TRI_STRIDE ) );
452
- const matIdx = int( uvData2.z );
453
-
454
- If( isTriangleVisible( matIdx, materialBuffer ), () => {
455
-
456
- closestHit.didHit.assign( true );
457
- closestHit.dst.assign( triResult.x );
458
- closestHit.materialIndex.assign( matIdx );
459
- closestHit.meshIndex.assign( int( uvData2.w ) );
460
443
 
461
- // Compute hit point and geometric normal -- required for transmissive
462
- // Fresnel in traceShadowRay (cosThetaI needs a real normal, not vec3(0))
463
- closestHit.hitPoint.assign( ray.origin.add( ray.direction.mul( triResult.x ) ) );
464
- closestHit.normal.assign( normalize( cross( pB.sub( pA ), pC.sub( pA ) ) ) );
444
+ closestHit.didHit.assign( true );
445
+ closestHit.dst.assign( triResult.x );
446
+ closestHit.materialIndex.assign( int( uvData2.z ) );
447
+ closestHit.meshIndex.assign( int( uvData2.w ) );
465
448
 
466
- // Shadow ray only needs any hit skip remaining triangles in leaf
467
- Break();
449
+ // Compute hit point and geometric normal -- required for transmissive
450
+ // Fresnel in traceShadowRay (cosThetaI needs a real normal, not vec3(0))
451
+ closestHit.hitPoint.assign( ray.origin.add( ray.direction.mul( triResult.x ) ) );
452
+ closestHit.normal.assign( normalize( cross( pB.sub( pA ), pC.sub( pA ) ) ) );
468
453
 
469
- } );
454
+ // Shadow ray only needs any hit — skip remaining triangles in leaf
455
+ Break();
470
456
 
471
457
  } );
472
458
 
@@ -475,13 +461,34 @@ export const traverseBVHShadow = Fn( ( [
475
461
  } ).Else( () => {
476
462
 
477
463
  // BLAS-pointer leaf (marker -2) — push BLAS root node onto stack
464
+ // nodeData0: [blasRootNodeIndex, meshIndex, 0, -2]
478
465
  const blasRoot = int( nodeData0.x ).toVar();
479
- If( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ), () => {
480
466
 
481
- stack.element( stackPtr ).assign( blasRoot );
482
- stackPtr.addAssign( 1 );
467
+ if ( _meshVisibilityBuffer ) {
483
468
 
484
- } );
469
+ // Per-mesh visibility check — skip entire BLAS if mesh is hidden
470
+ // getDatafromStorageBuffer( buffer, stride=1, sampleIndex=meshIdx, dataOffset=0 )
471
+ const meshIdx = int( nodeData0.y ).toVar();
472
+ const meshVis = getDatafromStorageBuffer( _meshVisibilityBuffer, int( 1 ), meshIdx, int( 0 ) ).x;
473
+
474
+ If( meshVis.greaterThan( 0.5 ).and( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ) ), () => {
475
+
476
+ stack.element( stackPtr ).assign( blasRoot );
477
+ stackPtr.addAssign( 1 );
478
+
479
+ } );
480
+
481
+ } else {
482
+
483
+ // No visibility buffer — push unconditionally (original behavior)
484
+ If( stackPtr.lessThan( int( MAX_STACK_DEPTH ) ), () => {
485
+
486
+ stack.element( stackPtr ).assign( blasRoot );
487
+ stackPtr.addAssign( 1 );
488
+
489
+ } );
490
+
491
+ }
485
492
 
486
493
  } );
487
494
 
package/src/TSL/Common.js CHANGED
@@ -353,7 +353,6 @@ export const getMaterial = Fn( ( [ materialIndex, materialBuffer ] ) => {
353
353
  attenuationColor: data3.rgb,
354
354
  attenuationDistance: data3.a,
355
355
  dispersion: data4.r,
356
- visible: data4.g,
357
356
  sheen: data4.b,
358
357
  sheenRoughness: data4.a,
359
358
  sheenColor: data5.rgb,
@@ -22,12 +22,9 @@ import {
22
22
  vec3,
23
23
  vec4,
24
24
  int,
25
- uint,
26
25
  bool as tslBool,
27
26
  max,
28
27
  min,
29
- abs,
30
- sqrt,
31
28
  exp,
32
29
  clamp,
33
30
  mix,
@@ -47,15 +44,12 @@ import {
47
44
  import { struct } from './structProxy.js';
48
45
 
49
46
  import {
50
- PI,
51
47
  PI_INV,
52
- EPSILON,
53
48
  MIN_ROUGHNESS,
54
49
  MAX_ROUGHNESS,
55
50
  MIN_CLEARCOAT_ROUGHNESS,
56
51
  MIN_PDF,
57
52
  maxComponent,
58
- minComponent,
59
53
  classifyMaterial,
60
54
  constructTBN,
61
55
  calculateFireflyThreshold,
@@ -66,8 +60,6 @@ import {
66
60
  import {
67
61
  DirectionSample,
68
62
  MaterialClassification,
69
- PathState,
70
- RenderState,
71
63
  MaterialCache,
72
64
  BRDFWeights,
73
65
  Ray,
@@ -83,7 +75,6 @@ import { sampleAllMaterialTextures } from './TextureSampling.js';
83
75
  import { refineDisplacedIntersection, DisplacementResult } from './Displacement.js';
84
76
  import { handleMaterialTransparency, MaterialInteractionResult, sampleMicrofacetTransmission, MicrofacetTransmissionResult } from './MaterialTransmission.js';
85
77
  import {
86
- DistributionGGX,
87
78
  SheenDistribution,
88
79
  calculateVNDFPDF,
89
80
  calculateBRDFWeights,
@@ -115,7 +106,6 @@ const RAY_TYPE_CAMERA = 0;
115
106
  const RAY_TYPE_REFLECTION = 1;
116
107
  const RAY_TYPE_TRANSMISSION = 2;
117
108
  const RAY_TYPE_DIFFUSE = 3;
118
- const RAY_TYPE_SHADOW = 4;
119
109
 
120
110
  // Trace result struct
121
111
  export const TraceResult = struct( {
package/src/TSL/Random.js CHANGED
@@ -24,7 +24,6 @@ const blueNoiseTextureSize = vec2( textureSize( blueNoiseTextureNode ) );
24
24
 
25
25
  // Golden ratio constants for dimension decorrelation
26
26
 
27
- const PHI = float( 1.61803398875 );
28
27
  const INV_PHI = float( 0.61803398875 );
29
28
  const INV_PHI2 = float( 0.38196601125 );
30
29
 
@@ -1,10 +1,7 @@
1
1
  import { Fn, float, vec2, vec3, int, If, dot, cross, abs, normalize, sqrt, min, max, select } from 'three/tsl';
2
2
 
3
3
  import {
4
- Ray,
5
4
  HitInfo,
6
- Triangle,
7
- Sphere,
8
5
  } from './Struct.js';
9
6
 
10
7
  // Optimized Intersection with Geometry only (no attributes)