rayzee 5.0.2 → 5.1.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/README.md +6 -1
- package/dist/rayzee.es.js +948 -916
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +41 -50
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/PathTracerApp.js +30 -1
- package/src/Pipeline/RenderStage.js +0 -1
- package/src/Processor/AssetLoader.js +2 -5
- package/src/Processor/EmissiveTriangleBuilder.js +3 -5
- package/src/Processor/EquirectHDRInfo.js +42 -5
- package/src/Processor/GeometryExtractor.js +5 -7
- package/src/Processor/SceneProcessor.js +3 -4
- 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 +100 -93
- package/src/TSL/Common.js +0 -1
- package/src/TSL/Debugger.js +2 -1
- package/src/TSL/LightsCore.js +1 -1
- package/src/TSL/LightsDirect.js +9 -7
- package/src/TSL/LightsSampling.js +3 -2
- package/src/TSL/PathTracerCore.js +4 -3
- package/src/TSL/Struct.js +0 -1
- package/src/managers/EnvironmentManager.js +1 -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.
|
|
3
|
+
"version": "5.1.1",
|
|
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
|
|
@@ -142,7 +142,6 @@ export class RenderStage {
|
|
|
142
142
|
|
|
143
143
|
const renderMode = context.getState( 'renderMode' ) || 0;
|
|
144
144
|
const tileRenderingComplete = context.getState( 'tileRenderingComplete' );
|
|
145
|
-
const frame = context.getState( 'frame' ) || 0;
|
|
146
145
|
|
|
147
146
|
switch ( this.executionMode ) {
|
|
148
147
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box3, Vector3, RectAreaLight, Color, FloatType, LinearFilter, EquirectangularReflectionMapping,
|
|
1
|
+
import { Box3, Vector3, RectAreaLight, Color, FloatType, LinearFilter, EquirectangularReflectionMapping,
|
|
2
2
|
TextureLoader, Mesh, MeshStandardMaterial, MeshPhysicalMaterial, CircleGeometry, Points, PointsMaterial, LoadingManager, EventDispatcher
|
|
3
3
|
} from 'three';
|
|
4
4
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
@@ -171,8 +171,6 @@ export class AssetLoader extends EventDispatcher {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
texture.generateMipmaps = true;
|
|
174
|
-
// texture.minFilter = LinearMipmapLinearFilter;
|
|
175
|
-
// texture.magFilter = LinearFilter;
|
|
176
174
|
|
|
177
175
|
this.applyEnvironmentToScene( texture );
|
|
178
176
|
this.dispatchEvent( { type: 'load', texture } );
|
|
@@ -353,7 +351,7 @@ export class AssetLoader extends EventDispatcher {
|
|
|
353
351
|
|
|
354
352
|
}
|
|
355
353
|
|
|
356
|
-
async findAndLoadModelFromZip( zip
|
|
354
|
+
async findAndLoadModelFromZip( zip ) {
|
|
357
355
|
|
|
358
356
|
const mainModelFiles = [
|
|
359
357
|
'scene.gltf', 'scene.glb', 'model.gltf', 'model.glb',
|
|
@@ -459,7 +457,6 @@ export class AssetLoader extends EventDispatcher {
|
|
|
459
457
|
if ( extension === 'gltf' ) {
|
|
460
458
|
|
|
461
459
|
const gltfContent = strFromU8( fileContent );
|
|
462
|
-
const gltfJson = JSON.parse( gltfContent );
|
|
463
460
|
const manager = new LoadingManager();
|
|
464
461
|
const gltfDir = filePath.split( '/' ).slice( 0, - 1 ).join( '/' );
|
|
465
462
|
|
|
@@ -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 );
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DataUtils, HalfFloatType, FloatType } from 'three';
|
|
1
|
+
import { DataUtils, HalfFloatType, FloatType, SRGBColorSpace } from 'three';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Binary search to find the closest index
|
|
@@ -38,18 +38,35 @@ function colorToLuminance( r, g, b ) {
|
|
|
38
38
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* sRGB to linear conversion (IEC 61966-2-1 transfer function)
|
|
43
|
+
*/
|
|
44
|
+
function sRGBToLinear( c ) {
|
|
45
|
+
|
|
46
|
+
return c <= 0.04045 ? c / 12.92 : ( ( c + 0.055 ) / 1.055 ) ** 2.4;
|
|
47
|
+
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
/**
|
|
42
51
|
* Extract Float32 RGBA pixel data from an environment map.
|
|
43
|
-
* Handles HalfFloat/integer type conversion
|
|
52
|
+
* Handles HalfFloat/integer type conversion, canvas extraction for
|
|
53
|
+
* non-DataTexture images (JPG/PNG), sRGB-to-linear conversion, and Y-flip.
|
|
44
54
|
* @returns {{ floatData: Float32Array, width: number, height: number }}
|
|
45
55
|
*/
|
|
46
56
|
export function extractFloatData( envMap ) {
|
|
47
57
|
|
|
48
|
-
const { width, height
|
|
58
|
+
const { width, height } = envMap.image;
|
|
59
|
+
let data = envMap.image.data;
|
|
60
|
+
let needsSRGBToLinear = false;
|
|
49
61
|
|
|
62
|
+
// No CPU-accessible data — extract from HTMLImageElement / ImageBitmap via canvas
|
|
50
63
|
if ( ! data ) {
|
|
51
64
|
|
|
52
|
-
|
|
65
|
+
const canvas = new OffscreenCanvas( width, height );
|
|
66
|
+
const ctx = canvas.getContext( '2d' );
|
|
67
|
+
ctx.drawImage( envMap.image, 0, 0, width, height );
|
|
68
|
+
data = ctx.getImageData( 0, 0, width, height ).data;
|
|
69
|
+
needsSRGBToLinear = true;
|
|
53
70
|
|
|
54
71
|
}
|
|
55
72
|
|
|
@@ -72,7 +89,7 @@ export function extractFloatData( envMap ) {
|
|
|
72
89
|
|
|
73
90
|
} else {
|
|
74
91
|
|
|
75
|
-
// Integer types (Uint8, Int16, etc.)
|
|
92
|
+
// Integer types (Uint8, Uint8Clamped, Int16, etc.)
|
|
76
93
|
let maxIntValue;
|
|
77
94
|
if ( data instanceof Int8Array || data instanceof Int16Array || data instanceof Int32Array ) {
|
|
78
95
|
|
|
@@ -93,6 +110,26 @@ export function extractFloatData( envMap ) {
|
|
|
93
110
|
|
|
94
111
|
}
|
|
95
112
|
|
|
113
|
+
// Also flag sRGB conversion for DataTextures explicitly marked as sRGB
|
|
114
|
+
if ( ! needsSRGBToLinear && envMap.colorSpace === SRGBColorSpace ) {
|
|
115
|
+
|
|
116
|
+
needsSRGBToLinear = true;
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Convert sRGB to linear so CDF luminance matches GPU-sampled linear values
|
|
121
|
+
if ( needsSRGBToLinear ) {
|
|
122
|
+
|
|
123
|
+
for ( let i = 0, l = floatData.length; i < l; i += 4 ) {
|
|
124
|
+
|
|
125
|
+
floatData[ i ] = sRGBToLinear( floatData[ i ] );
|
|
126
|
+
floatData[ i + 1 ] = sRGBToLinear( floatData[ i + 1 ] );
|
|
127
|
+
floatData[ i + 2 ] = sRGBToLinear( floatData[ i + 2 ] );
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
|
|
96
133
|
// Remove Y-flip for CDF computation
|
|
97
134
|
if ( envMap.flipY ) {
|
|
98
135
|
|
|
@@ -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;
|
|
@@ -820,8 +820,8 @@ export class SceneProcessor {
|
|
|
820
820
|
|
|
821
821
|
// Factory method for creating any additional scene elements
|
|
822
822
|
// Currently returns an empty array by default
|
|
823
|
-
const white = new Color( 0xffffff );
|
|
824
|
-
const black = new Color( 0x000000 );
|
|
823
|
+
// const white = new Color( 0xffffff );
|
|
824
|
+
// const black = new Color( 0x000000 );
|
|
825
825
|
return [
|
|
826
826
|
// { position: new Vector3( - 4, 2, 0 ), radius: 0.8, material: { color: white, emissive: black, emissiveIntensity: 0, roughness: 1.0 } },
|
|
827
827
|
// { position: new Vector3( - 1.5, 2, 0 ), radius: 0.8, material: { color: white, emissive: black, emissiveIntensity: 0, roughness: 1.0 } },
|
|
@@ -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
|
/**
|