rayzee 5.0.2 → 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/README.md +5 -0
- package/dist/rayzee.es.js +737 -714
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +31 -40
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/PathTracerApp.js +30 -1
- package/src/Processor/EmissiveTriangleBuilder.js +3 -5
- package/src/Processor/GeometryExtractor.js +5 -7
- package/src/Processor/SceneProcessor.js +1 -2
- package/src/Processor/ShaderBuilder.js +4 -0
- package/src/Processor/TLASBuilder.js +1 -1
- package/src/Stages/PathTracer.js +111 -0
- package/src/TSL/BVHTraversal.js +99 -92
- package/src/TSL/Common.js +0 -1
- package/src/TSL/Struct.js +0 -1
- package/src/managers/MaterialDataManager.js +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rayzee",
|
|
3
|
-
"version": "5.0
|
|
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.
|
|
37
|
+
"three": ">=0.183.2"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"stats-gl": "^4.0.2"
|
package/src/PathTracerApp.js
CHANGED
|
@@ -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'
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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'
|
|
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
|
-
|
|
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
|
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -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
|
/**
|
package/src/TSL/BVHTraversal.js
CHANGED
|
@@ -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
|
-
//
|
|
131
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
//
|
|
273
|
-
|
|
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
|
-
|
|
276
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
467
|
-
|
|
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
|
-
|
|
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,
|
package/src/TSL/Struct.js
CHANGED
|
@@ -229,7 +229,6 @@ export class MaterialDataManager {
|
|
|
229
229
|
break;
|
|
230
230
|
case 'attenuationDistance': data[ stride + 15 ] = value; break;
|
|
231
231
|
case 'dispersion': data[ stride + 16 ] = value; break;
|
|
232
|
-
case 'visible': data[ stride + 17 ] = value; break;
|
|
233
232
|
case 'sheen': data[ stride + 18 ] = value; break;
|
|
234
233
|
case 'sheenRoughness': data[ stride + 19 ] = value; break;
|
|
235
234
|
case 'sheenColor':
|
|
@@ -378,7 +377,7 @@ export class MaterialDataManager {
|
|
|
378
377
|
|
|
379
378
|
data[ stride + 15 ] = materialData.attenuationDistance ?? Infinity;
|
|
380
379
|
data[ stride + 16 ] = materialData.dispersion ?? 0;
|
|
381
|
-
data[ stride + 17 ] =
|
|
380
|
+
data[ stride + 17 ] = 1; // Reserved slot (per-mesh visibility handled at BLAS-pointer level)
|
|
382
381
|
data[ stride + 18 ] = materialData.sheen ?? 0;
|
|
383
382
|
data[ stride + 19 ] = materialData.sheenRoughness ?? 1;
|
|
384
383
|
|