rayzee 5.4.0 → 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.
- package/dist/rayzee.es.js +2723 -2726
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +53 -53
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/PathTracerApp.js +1 -2
- package/src/Processor/AssetLoader.js +40 -18
- package/src/Processor/EquirectHDRInfo.js +38 -29
- package/src/Processor/InstanceTable.js +16 -0
- package/src/Processor/SceneProcessor.js +22 -33
- package/src/Processor/ShaderBuilder.js +14 -17
- package/src/Processor/TLASBuilder.js +9 -4
- package/src/Stages/PathTracer.js +87 -43
- package/src/TSL/BVHTraversal.js +15 -63
- package/src/TSL/Clearcoat.js +1 -1
- package/src/TSL/Displacement.js +1 -1
- package/src/TSL/EmissiveSampling.js +17 -13
- package/src/TSL/Environment.js +12 -9
- package/src/TSL/LightBVHSampling.js +3 -2
- package/src/TSL/LightsCore.js +1 -1
- package/src/TSL/LightsDirect.js +1 -1
- package/src/TSL/LightsIndirect.js +0 -1
- package/src/TSL/LightsSampling.js +2 -2
- package/src/TSL/MaterialTransmission.js +1 -1
- package/src/TSL/PathTracer.js +4 -4
- package/src/TSL/PathTracerCore.js +6 -6
- package/src/TSL/Struct.js +1 -1
- package/src/TSL/patches.js +145 -0
- package/src/index.js +1 -1
- package/src/managers/EnvironmentManager.js +32 -56
- package/src/managers/UniformManager.js +3 -0
- package/src/TSL/structProxy.js +0 -87
- package/src/TSL/wgslGlobalVarsPatch.js +0 -60
package/package.json
CHANGED
package/src/PathTracerApp.js
CHANGED
|
@@ -639,8 +639,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
639
639
|
|
|
640
640
|
if ( ! this._sdf.uploadToPathTracer( this.stages.pathTracer, this.lightManager, this.meshScene, environmentTexture ) ) return false;
|
|
641
641
|
|
|
642
|
-
//
|
|
643
|
-
// shader graph captures the storage node during compilation)
|
|
642
|
+
// Patch per-mesh visibility into the TLAS leaves we just uploaded
|
|
644
643
|
this.stages.pathTracer._meshRefs = this.stages.pathTracer._collectMeshRefs( this.meshScene );
|
|
645
644
|
this.stages.pathTracer.setMeshVisibilityData( this.stages.pathTracer._meshRefs );
|
|
646
645
|
|
|
@@ -472,20 +472,28 @@ export class AssetLoader extends EventDispatcher {
|
|
|
472
472
|
const loader = await this.createGLTFLoader();
|
|
473
473
|
loader.manager = manager;
|
|
474
474
|
|
|
475
|
-
|
|
475
|
+
try {
|
|
476
476
|
|
|
477
|
-
|
|
478
|
-
gltf => {
|
|
477
|
+
return await new Promise( ( resolve, reject ) => {
|
|
479
478
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
this.onModelLoad( this.targetModel ).then( () => resolve( gltf ) );
|
|
479
|
+
loader.parse( gltfContent, '',
|
|
480
|
+
gltf => {
|
|
483
481
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
482
|
+
if ( this.targetModel ) disposeObjectFromMemory( this.targetModel );
|
|
483
|
+
this.targetModel = gltf.scene;
|
|
484
|
+
this.onModelLoad( this.targetModel ).then( () => resolve( gltf ) );
|
|
487
485
|
|
|
488
|
-
|
|
486
|
+
},
|
|
487
|
+
error => reject( error )
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
} );
|
|
491
|
+
|
|
492
|
+
} finally {
|
|
493
|
+
|
|
494
|
+
this._disposeGLTFLoader( loader );
|
|
495
|
+
|
|
496
|
+
}
|
|
489
497
|
|
|
490
498
|
} else {
|
|
491
499
|
|
|
@@ -697,11 +705,10 @@ export class AssetLoader extends EventDispatcher {
|
|
|
697
705
|
|
|
698
706
|
}
|
|
699
707
|
|
|
700
|
-
//
|
|
708
|
+
// Returns a fresh loader each call — DRACOLoader/KTX2Loader hold persistent
|
|
709
|
+
// worker pools. Callers must invoke _disposeGLTFLoader() to terminate them.
|
|
701
710
|
async createGLTFLoader() {
|
|
702
711
|
|
|
703
|
-
if ( this.loaderCache.gltf ) return this.loaderCache.gltf;
|
|
704
|
-
|
|
705
712
|
const dracoLoader = new DRACOLoader();
|
|
706
713
|
dracoLoader.setDecoderConfig( { type: 'js' } );
|
|
707
714
|
dracoLoader.setDecoderPath( 'https://www.gstatic.com/draco/v1/decoders/' );
|
|
@@ -726,18 +733,23 @@ export class AssetLoader extends EventDispatcher {
|
|
|
726
733
|
|
|
727
734
|
}
|
|
728
735
|
|
|
729
|
-
this.loaderCache.ktx2 = ktx2Loader;
|
|
730
|
-
|
|
731
736
|
const loader = new GLTFLoader();
|
|
732
737
|
loader.setDRACOLoader( dracoLoader );
|
|
733
738
|
loader.setKTX2Loader( ktx2Loader );
|
|
734
739
|
loader.setMeshoptDecoder( MeshoptDecoder );
|
|
735
740
|
|
|
736
|
-
this.loaderCache.gltf = loader;
|
|
737
741
|
return loader;
|
|
738
742
|
|
|
739
743
|
}
|
|
740
744
|
|
|
745
|
+
_disposeGLTFLoader( loader ) {
|
|
746
|
+
|
|
747
|
+
if ( ! loader ) return;
|
|
748
|
+
loader.dracoLoader?.dispose();
|
|
749
|
+
loader.ktx2Loader?.dispose();
|
|
750
|
+
|
|
751
|
+
}
|
|
752
|
+
|
|
741
753
|
async loadExampleModels( index, modelFiles ) {
|
|
742
754
|
|
|
743
755
|
if ( ! modelFiles || ! modelFiles[ index ] ) {
|
|
@@ -753,9 +765,10 @@ export class AssetLoader extends EventDispatcher {
|
|
|
753
765
|
|
|
754
766
|
async loadModel( modelUrl ) {
|
|
755
767
|
|
|
768
|
+
const loader = await this.createGLTFLoader();
|
|
769
|
+
|
|
756
770
|
try {
|
|
757
771
|
|
|
758
|
-
const loader = await this.createGLTFLoader();
|
|
759
772
|
updateLoading( { status: "Loading Model...", progress: 2 } );
|
|
760
773
|
const data = await loader.loadAsync( modelUrl );
|
|
761
774
|
updateLoading( { status: "Processing Data...", progress: 10 } );
|
|
@@ -774,15 +787,20 @@ export class AssetLoader extends EventDispatcher {
|
|
|
774
787
|
this.dispatchEvent( { type: 'error', message: error.message, filename: modelUrl } );
|
|
775
788
|
throw error;
|
|
776
789
|
|
|
790
|
+
} finally {
|
|
791
|
+
|
|
792
|
+
this._disposeGLTFLoader( loader );
|
|
793
|
+
|
|
777
794
|
}
|
|
778
795
|
|
|
779
796
|
}
|
|
780
797
|
|
|
781
798
|
async loadGLBFromArrayBuffer( arrayBuffer, filename = 'model.glb' ) {
|
|
782
799
|
|
|
800
|
+
const loader = await this.createGLTFLoader();
|
|
801
|
+
|
|
783
802
|
try {
|
|
784
803
|
|
|
785
|
-
const loader = await this.createGLTFLoader();
|
|
786
804
|
updateLoading( { isLoading: true, status: "Processing GLB Data...", progress: 5 } );
|
|
787
805
|
await new Promise( r => setTimeout( r, 0 ) );
|
|
788
806
|
|
|
@@ -804,6 +822,10 @@ export class AssetLoader extends EventDispatcher {
|
|
|
804
822
|
this.dispatchEvent( { type: 'error', message: error.message, filename } );
|
|
805
823
|
throw error;
|
|
806
824
|
|
|
825
|
+
} finally {
|
|
826
|
+
|
|
827
|
+
this._disposeGLTFLoader( loader );
|
|
828
|
+
|
|
807
829
|
}
|
|
808
830
|
|
|
809
831
|
}
|
|
@@ -220,53 +220,62 @@ export class EquirectHDRInfo {
|
|
|
220
220
|
|
|
221
221
|
const { floatData, width, height } = extractFloatData( hdr );
|
|
222
222
|
|
|
223
|
-
//
|
|
224
|
-
|
|
223
|
+
// Fresh worker per call — terminated in finally to avoid ~30 MB residency.
|
|
224
|
+
try {
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
this._worker = new Worker( CDF_WORKER_URL, { type: 'module' } );
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
} catch ( e ) {
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
if ( e.name !== 'SecurityError' ) throw e;
|
|
231
|
+
this._worker = await fetchAsWorker( CDF_WORKER_URL );
|
|
231
232
|
|
|
232
|
-
|
|
233
|
-
this._worker = await fetchAsWorker( CDF_WORKER_URL );
|
|
233
|
+
}
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
try {
|
|
236
236
|
|
|
237
|
-
|
|
237
|
+
const result = await new Promise( ( resolve, reject ) => {
|
|
238
238
|
|
|
239
|
-
|
|
239
|
+
this._worker.onmessage = ( e ) => {
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
if ( e.data.error ) {
|
|
242
242
|
|
|
243
|
-
|
|
243
|
+
reject( new Error( e.data.error ) );
|
|
244
244
|
|
|
245
|
-
|
|
245
|
+
} else {
|
|
246
246
|
|
|
247
|
-
|
|
247
|
+
resolve( e.data );
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
}
|
|
250
250
|
|
|
251
|
-
}
|
|
251
|
+
};
|
|
252
252
|
|
|
253
|
-
|
|
253
|
+
this._worker.onerror = reject;
|
|
254
254
|
|
|
255
|
-
|
|
255
|
+
// Transfer floatData to worker (zero-copy)
|
|
256
|
+
this._worker.postMessage(
|
|
257
|
+
{ floatData, width, height },
|
|
258
|
+
[ floatData.buffer ]
|
|
259
|
+
);
|
|
256
260
|
|
|
257
|
-
|
|
258
|
-
this._worker.postMessage(
|
|
259
|
-
{ floatData, width, height },
|
|
260
|
-
[ floatData.buffer ]
|
|
261
|
-
);
|
|
261
|
+
} );
|
|
262
262
|
|
|
263
|
-
|
|
263
|
+
this.marginalData = result.marginalData;
|
|
264
|
+
this.conditionalData = result.conditionalData;
|
|
265
|
+
this.totalSum = result.totalSum;
|
|
266
|
+
this.width = result.width;
|
|
267
|
+
this.height = result.height;
|
|
264
268
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
269
|
+
} finally {
|
|
270
|
+
|
|
271
|
+
if ( this._worker ) {
|
|
272
|
+
|
|
273
|
+
this._worker.terminate();
|
|
274
|
+
this._worker = null;
|
|
275
|
+
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
}
|
|
270
279
|
|
|
271
280
|
}
|
|
272
281
|
|
|
@@ -57,10 +57,26 @@ export class InstanceTable {
|
|
|
57
57
|
worldAABB: null, // Computed from triangle data
|
|
58
58
|
originalToBvhMap,
|
|
59
59
|
bvhData,
|
|
60
|
+
visible: true, // Per-mesh visibility (baked into TLAS leaf slot [2])
|
|
61
|
+
tlasLeafIndex: - 1, // Set by TLASBuilder.flatten() — enables in-place visibility patching
|
|
60
62
|
};
|
|
61
63
|
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Set per-mesh visibility flag. Does NOT update the GPU buffer —
|
|
68
|
+
* caller must patch combinedBvhData[tlasLeafIndex*16 + 2] and mark bvh attr dirty.
|
|
69
|
+
*
|
|
70
|
+
* @param {number} meshIndex
|
|
71
|
+
* @param {boolean} visible
|
|
72
|
+
*/
|
|
73
|
+
setVisibility( meshIndex, visible ) {
|
|
74
|
+
|
|
75
|
+
const entry = this.entries[ meshIndex ];
|
|
76
|
+
if ( entry ) entry.visible = visible;
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
64
80
|
/**
|
|
65
81
|
* Compute world-space AABBs for all entries from their BLAS root node data.
|
|
66
82
|
* O(1) per mesh for inner roots; falls back to triangle scan for leaf roots (rare).
|
|
@@ -476,48 +476,36 @@ export class SceneProcessor {
|
|
|
476
476
|
|
|
477
477
|
const validEntries = this.instanceTable.entries.filter( e => e !== null );
|
|
478
478
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
this.instanceTable.assignOffsets( 0 ); // BLAS at offset 0
|
|
486
|
-
this._buildGlobalOriginalToBvhMap();
|
|
487
|
-
entry.originalToBvhMap = null;
|
|
488
|
-
entry.bvhData = null;
|
|
489
|
-
|
|
490
|
-
} else {
|
|
491
|
-
|
|
492
|
-
// Multi-mesh — build TLAS over mesh AABBs
|
|
493
|
-
this.instanceTable.computeAABBs( this.triangleData );
|
|
494
|
-
const { root: tlasRoot, nodeCount: tlasNodeCount } = this.tlasBuilder.build( validEntries );
|
|
479
|
+
// Always build a TLAS — even for a single mesh — so the BLAS-pointer leaf
|
|
480
|
+
// carries packed per-mesh visibility in its slot [2]. The 1-node TLAS
|
|
481
|
+
// overhead (one extra leaf fetch per ray) is negligible and eliminates
|
|
482
|
+
// a dedicated visibility storage buffer binding.
|
|
483
|
+
this.instanceTable.computeAABBs( this.triangleData );
|
|
484
|
+
const { root: tlasRoot, nodeCount: tlasNodeCount } = this.tlasBuilder.build( validEntries );
|
|
495
485
|
|
|
496
|
-
|
|
497
|
-
|
|
486
|
+
this.instanceTable.assignOffsets( tlasNodeCount );
|
|
487
|
+
const totalNodes = this.instanceTable.totalNodeCount;
|
|
498
488
|
|
|
499
|
-
|
|
489
|
+
const tlasData = this.tlasBuilder.flatten( tlasRoot, validEntries );
|
|
500
490
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
491
|
+
// Assemble combined buffer: [TLAS][BLAS_0][BLAS_1]...[BLAS_M]
|
|
492
|
+
this.bvhData = new Float32Array( totalNodes * 16 );
|
|
493
|
+
this.bvhData.set( tlasData );
|
|
504
494
|
|
|
505
|
-
|
|
495
|
+
for ( const entry of validEntries ) {
|
|
506
496
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
497
|
+
const destOffset = entry.blasOffset * 16;
|
|
498
|
+
this.bvhData.set( entry.bvhData, destOffset );
|
|
499
|
+
this._offsetBLASInPlace( destOffset, entry.bvhData.length / 16, entry.blasOffset, entry.triOffset );
|
|
510
500
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
this._buildGlobalOriginalToBvhMap();
|
|
501
|
+
}
|
|
514
502
|
|
|
515
|
-
|
|
503
|
+
this._buildGlobalOriginalToBvhMap();
|
|
516
504
|
|
|
517
|
-
|
|
518
|
-
entry.bvhData = null;
|
|
505
|
+
for ( const entry of validEntries ) {
|
|
519
506
|
|
|
520
|
-
|
|
507
|
+
entry.originalToBvhMap = null;
|
|
508
|
+
entry.bvhData = null;
|
|
521
509
|
|
|
522
510
|
}
|
|
523
511
|
|
|
@@ -1384,6 +1372,7 @@ export class SceneProcessor {
|
|
|
1384
1372
|
}
|
|
1385
1373
|
|
|
1386
1374
|
pathTracer.setBVHData( this.bvhData );
|
|
1375
|
+
pathTracer.setInstanceTable( this.instanceTable );
|
|
1387
1376
|
|
|
1388
1377
|
if ( this.materialData ) {
|
|
1389
1378
|
|
|
@@ -16,7 +16,6 @@ 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';
|
|
20
19
|
import { setShadowAlbedoMaps, setAlphaShadowsUniform } from '../TSL/LightsDirect.js';
|
|
21
20
|
import { BuildTimer } from './BuildTimer.js';
|
|
22
21
|
|
|
@@ -234,11 +233,9 @@ export class ShaderBuilder {
|
|
|
234
233
|
const triStorage = stage.triangleStorageNode;
|
|
235
234
|
const bvhStorage = stage.bvhStorageNode;
|
|
236
235
|
const matStorage = stage.materialData.materialStorageNode;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// Set per-mesh visibility buffer (module-level in BVHTraversal.js, read during graph construction)
|
|
241
|
-
setMeshVisibilityBuffer( stage.meshVisibilityStorageNode );
|
|
236
|
+
// Packed light buffer — [lightBVH | emissive triangles]. One node fed to both
|
|
237
|
+
// TSL params; emissive reads offset by stage.emissiveVec4Offset.
|
|
238
|
+
const lightBufferStorage = stage.lightStorageNode;
|
|
242
239
|
|
|
243
240
|
// Set alpha-shadow uniform (module-level in LightsDirect.js, read at runtime)
|
|
244
241
|
setAlphaShadowsUniform( stage.uniforms.get( 'enableAlphaShadows' ) );
|
|
@@ -249,9 +246,9 @@ export class ShaderBuilder {
|
|
|
249
246
|
const adaptiveSamplingTex = new TextureNode();
|
|
250
247
|
this.adaptiveSamplingTexNode = adaptiveSamplingTex;
|
|
251
248
|
|
|
252
|
-
// Environment importance sampling CDF
|
|
253
|
-
|
|
254
|
-
const
|
|
249
|
+
// Environment importance sampling CDF — packed storage buffer
|
|
250
|
+
// Layout: [marginal (envResolution.y floats) | conditional (envResolution.x * envResolution.y floats)]
|
|
251
|
+
const envCDFStorage = stage.environment.envCDFStorageNode;
|
|
255
252
|
|
|
256
253
|
// Previous-frame texture nodes — initialized from readTarget textures
|
|
257
254
|
const readTextures = storageTextures.getReadTextures();
|
|
@@ -285,8 +282,8 @@ export class ShaderBuilder {
|
|
|
285
282
|
setShadowAlbedoMaps( albedoMapsTex );
|
|
286
283
|
|
|
287
284
|
const result = {
|
|
288
|
-
triStorage, bvhStorage, matStorage,
|
|
289
|
-
envTex, adaptiveSamplingTex,
|
|
285
|
+
triStorage, bvhStorage, matStorage, lightBufferStorage,
|
|
286
|
+
envTex, adaptiveSamplingTex, envCDFStorage,
|
|
290
287
|
albedoMapsTex, normalMapsTex, bumpMapsTex,
|
|
291
288
|
metalnessMapsTex, roughnessMapsTex, emissiveMapsTex, displacementMapsTex,
|
|
292
289
|
};
|
|
@@ -304,8 +301,8 @@ export class ShaderBuilder {
|
|
|
304
301
|
writeColorTex, writeNDTex, writeAlbedoTex ) {
|
|
305
302
|
|
|
306
303
|
const {
|
|
307
|
-
triStorage, bvhStorage, matStorage,
|
|
308
|
-
envTex, adaptiveSamplingTex,
|
|
304
|
+
triStorage, bvhStorage, matStorage, lightBufferStorage,
|
|
305
|
+
envTex, adaptiveSamplingTex, envCDFStorage,
|
|
309
306
|
albedoMapsTex, normalMapsTex, bumpMapsTex,
|
|
310
307
|
metalnessMapsTex, roughnessMapsTex, emissiveMapsTex, displacementMapsTex,
|
|
311
308
|
} = textureNodes;
|
|
@@ -366,8 +363,7 @@ export class ShaderBuilder {
|
|
|
366
363
|
envTexture: envTex,
|
|
367
364
|
environmentIntensity: stage.environmentIntensity,
|
|
368
365
|
envMatrix: stage.environmentMatrix,
|
|
369
|
-
|
|
370
|
-
envConditionalWeights: conditionalCDFStorage,
|
|
366
|
+
envCDFBuffer: envCDFStorage,
|
|
371
367
|
envTotalSum: stage.envTotalSum,
|
|
372
368
|
envResolution: stage.envResolution,
|
|
373
369
|
enableEnvironmentLight: stage.enableEnvironment,
|
|
@@ -381,11 +377,12 @@ export class ShaderBuilder {
|
|
|
381
377
|
globalIlluminationIntensity: stage.globalIlluminationIntensity,
|
|
382
378
|
totalTriangleCount: stage.totalTriangleCount,
|
|
383
379
|
enableEmissiveTriangleSampling: stage.enableEmissiveTriangleSampling,
|
|
384
|
-
emissiveTriangleBuffer:
|
|
380
|
+
emissiveTriangleBuffer: lightBufferStorage,
|
|
385
381
|
emissiveTriangleCount: stage.emissiveTriangleCount,
|
|
386
382
|
emissiveTotalPower: stage.emissiveTotalPower,
|
|
387
383
|
emissiveBoost: stage.emissiveBoost,
|
|
388
|
-
|
|
384
|
+
emissiveVec4Offset: stage.emissiveVec4Offset,
|
|
385
|
+
lightBVHBuffer: lightBufferStorage,
|
|
389
386
|
lightBVHNodeCount: stage.lightBVHNodeCount,
|
|
390
387
|
debugVisScale: stage.debugVisScale,
|
|
391
388
|
enableAccumulation: stage.enableAccumulation,
|
|
@@ -199,10 +199,13 @@ export class TLASBuilder {
|
|
|
199
199
|
/**
|
|
200
200
|
* Flatten TLAS tree into Float32Array.
|
|
201
201
|
* Inner nodes: same format as BVH.
|
|
202
|
-
* Leaf nodes: [blasRootNodeIndex,
|
|
202
|
+
* Leaf nodes: [blasRootNodeIndex, meshIndex, visibility, -2] (BLAS-pointer marker).
|
|
203
|
+
*
|
|
204
|
+
* Side effect: records each entry's flat leaf index on `entry.tlasLeafIndex` so that
|
|
205
|
+
* visibility can later be patched in place (combinedBvhData[tlasLeafIndex*16 + 2]).
|
|
203
206
|
*
|
|
204
207
|
* @param {TLASNode} root
|
|
205
|
-
* @param {Array<{blasOffset: number
|
|
208
|
+
* @param {Array<{blasOffset: number, visible: boolean, tlasLeafIndex: number}>} entries
|
|
206
209
|
* @returns {Float32Array}
|
|
207
210
|
*/
|
|
208
211
|
flatten( root, entries ) {
|
|
@@ -268,10 +271,12 @@ export class TLASBuilder {
|
|
|
268
271
|
// Leaf node — BLAS pointer
|
|
269
272
|
const entry = entries[ n.entryIndex ];
|
|
270
273
|
data[ o ] = entry.blasOffset; // Absolute node index of BLAS root in combined buffer
|
|
271
|
-
data[ o + 1 ] = n.entryIndex; // meshIndex for
|
|
272
|
-
|
|
274
|
+
data[ o + 1 ] = n.entryIndex; // meshIndex (kept for debug/ID — traversal uses slot [2])
|
|
275
|
+
data[ o + 2 ] = entry.visible === false ? 0.0 : 1.0; // Per-mesh visibility (packed — frees a binding)
|
|
273
276
|
data[ o + 3 ] = BVH_LEAF_MARKERS.BLAS_POINTER_LEAF; // -2 marker
|
|
274
277
|
|
|
278
|
+
entry.tlasLeafIndex = i;
|
|
279
|
+
|
|
275
280
|
}
|
|
276
281
|
|
|
277
282
|
}
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -180,17 +180,20 @@ export class PathTracer extends RenderStage {
|
|
|
180
180
|
// Blue noise
|
|
181
181
|
this.blueNoiseTexture = null;
|
|
182
182
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
//
|
|
188
|
-
this.
|
|
189
|
-
this.
|
|
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
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
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
|
-
|
|
779
|
+
setInstanceTable( instanceTable ) {
|
|
780
|
+
|
|
781
|
+
this._instanceTable = instanceTable;
|
|
782
|
+
|
|
783
|
+
}
|
|
777
784
|
|
|
778
|
-
|
|
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
|
-
|
|
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 <
|
|
794
|
+
for ( let i = 0; i < meshes.length; i ++ ) {
|
|
785
795
|
|
|
786
|
-
|
|
796
|
+
this._patchTLASLeafVisibility( i, this._isWorldVisible( meshes[ i ] ) );
|
|
787
797
|
|
|
788
798
|
}
|
|
789
799
|
|
|
790
|
-
this.
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
826
|
+
this._patchTLASLeafVisibility( i, this._isWorldVisible( this._meshRefs[ i ] ) );
|
|
822
827
|
|
|
823
828
|
}
|
|
824
829
|
|
|
825
|
-
this.
|
|
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
|
|
|
@@ -1493,18 +1514,43 @@ export class PathTracer extends RenderStage {
|
|
|
1493
1514
|
|
|
1494
1515
|
}
|
|
1495
1516
|
|
|
1496
|
-
|
|
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() {
|
|
1497
1524
|
|
|
1498
|
-
|
|
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;
|
|
1499
1530
|
|
|
1500
|
-
|
|
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 );
|
|
1501
1536
|
|
|
1502
|
-
this.
|
|
1503
|
-
this.
|
|
1504
|
-
this.
|
|
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;
|
|
1505
1549
|
|
|
1550
|
+
this._emissiveDataCache = emissiveData;
|
|
1506
1551
|
this.emissiveTriangleCount.value = count;
|
|
1507
1552
|
this.emissiveTotalPower.value = totalPower;
|
|
1553
|
+
this._rebuildLightBuffer();
|
|
1508
1554
|
console.log( `PathTracer: ${count} emissive triangles, totalPower=${totalPower.toFixed( 4 )} (storage buffer)` );
|
|
1509
1555
|
|
|
1510
1556
|
}
|
|
@@ -1513,11 +1559,9 @@ export class PathTracer extends RenderStage {
|
|
|
1513
1559
|
|
|
1514
1560
|
if ( ! nodeData ) return;
|
|
1515
1561
|
|
|
1516
|
-
|
|
1517
|
-
this.lightBVHStorageAttr = new StorageInstancedBufferAttribute( nodeData, 4 );
|
|
1518
|
-
this.lightBVHStorageNode.value = this.lightBVHStorageAttr;
|
|
1519
|
-
this.lightBVHStorageNode.bufferCount = vec4Count;
|
|
1562
|
+
this._lbvhDataCache = nodeData;
|
|
1520
1563
|
this.lightBVHNodeCount.value = nodeCount;
|
|
1564
|
+
this._rebuildLightBuffer();
|
|
1521
1565
|
console.log( `PathTracer: Light BVH ${nodeCount} nodes` );
|
|
1522
1566
|
|
|
1523
1567
|
}
|