rayzee 7.7.0 → 7.9.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.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/dist/assets/{BVHSubtreeWorker-D9GImjGj.js → BVHSubtreeWorker-sNzvxn66.js} +2 -2
  3. package/dist/assets/{BVHSubtreeWorker-D9GImjGj.js.map → BVHSubtreeWorker-sNzvxn66.js.map} +1 -1
  4. package/dist/assets/{BVHWorker-CNJ0UBQz.js → BVHWorker-CiVdFrwe.js} +2 -2
  5. package/dist/assets/{BVHWorker-CNJ0UBQz.js.map → BVHWorker-CiVdFrwe.js.map} +1 -1
  6. package/dist/rayzee.es.js +2057 -2035
  7. package/dist/rayzee.es.js.map +1 -1
  8. package/dist/rayzee.umd.js +55 -55
  9. package/dist/rayzee.umd.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/EngineDefaults.js +45 -1
  12. package/src/EngineEvents.js +1 -0
  13. package/src/Passes/OIDNDenoiser.js +25 -1
  14. package/src/PathTracerApp.js +36 -0
  15. package/src/Processor/AssetLoader.js +3 -0
  16. package/src/Processor/GeometryExtractor.js +35 -9
  17. package/src/Processor/PackedRayBuffer.js +15 -1
  18. package/src/Processor/QueueManager.js +13 -1
  19. package/src/Processor/SceneProcessor.js +176 -113
  20. package/src/Processor/ShaderBuilder.js +6 -24
  21. package/src/Processor/TextureCreator.js +16 -32
  22. package/src/Processor/Workers/BVHWorker.js +6 -1
  23. package/src/Stages/EdgeFilter.js +4 -3
  24. package/src/Stages/MotionVector.js +4 -4
  25. package/src/Stages/NormalDepth.js +20 -30
  26. package/src/Stages/PathTracer.js +20 -40
  27. package/src/TSL/DebugKernel.js +0 -2
  28. package/src/TSL/Debugger.js +1 -8
  29. package/src/TSL/Displacement.js +10 -10
  30. package/src/TSL/GenerateKernel.js +5 -1
  31. package/src/TSL/LightsDirect.js +8 -9
  32. package/src/TSL/LightsIndirect.js +15 -9
  33. package/src/TSL/LightsSampling.js +9 -19
  34. package/src/TSL/Random.js +11 -1
  35. package/src/TSL/ShadeKernel.js +1 -6
  36. package/src/TSL/TextureSampling.js +155 -34
  37. package/src/managers/EnvironmentManager.js +11 -0
  38. package/src/managers/MaterialDataManager.js +71 -39
  39. package/src/managers/RenderTargetManager.js +0 -522
@@ -9,7 +9,8 @@ import { GeometryExtractor } from './GeometryExtractor.js';
9
9
  import { EmissiveTriangleBuilder } from './EmissiveTriangleBuilder.js';
10
10
  import { updateLoading } from '../Processor/utils.js';
11
11
  import { BuildTimer } from './BuildTimer.js';
12
- import { TRIANGLE_DATA_LAYOUT, TEXTURE_CONSTANTS } from '../EngineDefaults.js';
12
+ import { SRGBColorSpace } from 'three';
13
+ import { TRIANGLE_DATA_LAYOUT, TEXTURE_CONSTANTS, getTextureBucketId, packTextureIndex } from '../EngineDefaults.js';
13
14
  import { fetchAsWorker } from './Workers/fetchAsWorker.js';
14
15
  import BVH_WORKER_URL from './Workers/BVHWorker.js?worker&url';
15
16
  import BVH_REFIT_WORKER_URL from './Workers/BVHRefitWorker.js?worker&url';
@@ -79,14 +80,13 @@ export class SceneProcessor {
79
80
  this._rebuildGeneration = 0; // Monotonic counter to discard stale background rebuilds
80
81
  this._pendingRebuilds = new Map(); // meshIndex → worker
81
82
 
82
- // Initialize texture references
83
- this.albedoTextures = null;
84
- this.normalTextures = null;
85
- this.bumpTextures = null;
86
- this.roughnessTextures = null;
87
- this.metalnessTextures = null;
88
- this.emissiveTextures = null;
89
- this.displacementTextures = null;
83
+ // Initialize texture references.
84
+ // Material maps are packed into consolidated size-bucketed arrays (see _bucketTextures):
85
+ // srgbBucketTextures[K] — albedo + emissive (SRGBColorSpace)
86
+ // linearBucketTextures[K] normal/bump/roughness/metalness/displacement
87
+ // A material's per-map index encodes (bucket, layer) via packTextureIndex.
88
+ this.srgbBucketTextures = null;
89
+ this.linearBucketTextures = null;
90
90
  this.emissiveTriangleData = null;
91
91
  this.emissiveTriangleCount = 0;
92
92
  this.lightBVHNodeData = null;
@@ -422,7 +422,13 @@ export class SceneProcessor {
422
422
  enabled: this.bvhBuilder.enableReinsertionOptimization,
423
423
  batchSizeRatio: this.bvhBuilder.reinsertionBatchSizeRatio,
424
424
  maxIterations: this.bvhBuilder.reinsertionMaxIterations
425
- }
425
+ },
426
+ // BVH build params — previously omitted, so the pool path built at the
427
+ // BVHBuilder default (leaf 8) instead of the configured value.
428
+ maxLeafSize: this.bvhBuilder.maxLeafSize,
429
+ numBins: this.bvhBuilder.numBins,
430
+ maxBins: this.bvhBuilder.maxBins,
431
+ minBins: this.bvhBuilder.minBins,
426
432
  };
427
433
 
428
434
  const totalTasks = poolTasks.length + parallelTasks.length;
@@ -630,6 +636,10 @@ export class SceneProcessor {
630
636
  sharedReorderBuffer: null,
631
637
  treeletOptimization: treeletOpts,
632
638
  reinsertionOptimization: opts.reinsertionOptimization,
639
+ maxLeafSize: opts.maxLeafSize,
640
+ numBins: opts.numBins,
641
+ maxBins: opts.maxBins,
642
+ minBins: opts.minBins,
633
643
  }, [ meshTriData.buffer ] );
634
644
 
635
645
  };
@@ -762,40 +772,45 @@ export class SceneProcessor {
762
772
 
763
773
  try {
764
774
 
765
- // Material raw data for storage buffers (sync, ~1-5ms)
775
+ // Group the extractor's per-type arrays into consolidated colorSpace×size-bucket
776
+ // pools, and rewrite each material's per-map index to the packed (bucket, layer)
777
+ // form. Must run BEFORE createMaterialRawData (which reads mat.map etc.).
778
+ const { srgbLists, linearLists, remap } = this._bucketTextures();
779
+ this._remapMaterialTextureIndices( remap );
780
+
781
+ // Material raw data for storage buffers (sync, ~1-5ms) — now holds packed indices.
766
782
  if ( this.materials?.length ) {
767
783
 
768
784
  this.materialData = this.textureCreator.createMaterialRawData( this.materials );
769
785
 
770
786
  }
771
787
 
772
- // Material texture arrays GPU DataArrayTextures
773
- // All 7 map types are independent process in parallel
774
- const mapTypesList = [
775
- { data: this.maps, prop: 'albedoTextures' },
776
- { data: this.normalMaps, prop: 'normalTextures' },
777
- { data: this.bumpMaps, prop: 'bumpTextures' },
778
- { data: this.roughnessMaps, prop: 'roughnessTextures' },
779
- { data: this.metalnessMaps, prop: 'metalnessTextures' },
780
- { data: this.emissiveMaps, prop: 'emissiveTextures' },
781
- { data: this.displacementMaps, prop: 'displacementTextures' },
782
- ];
783
-
784
- await Promise.all(
785
- mapTypesList
786
- .filter( ( { data } ) => data?.length > 0 )
787
- .map( ( { data, prop } ) =>
788
- this.textureCreator.createTexturesToDataTexture( data )
789
- .then( result => {
790
-
791
- this[ prop ] = result;
792
-
793
- } )
794
- )
795
- );
788
+ // One DataArrayTexture per non-empty bucket. The sRGB pool (albedo + emissive — both
789
+ // authored in sRGB per glTF) carries SRGBColorSpace so the GPU decodes sRGB→linear
790
+ // before lighting; the linear pool (normal/roughness/metalness/bump/displacement —
791
+ // data textures) stays linear. Applied consistently across load AND rebuildMaterials
792
+ // (the prior model-load path omitted this, leaving albedo un-decoded / too bright).
793
+ const buildBucket = ( list, srgb ) => list.length === 0
794
+ ? Promise.resolve( null )
795
+ : this.textureCreator.createTexturesToDataTexture( list ).then( tex => {
796
+
797
+ if ( tex && srgb ) tex.colorSpace = SRGBColorSpace;
798
+ return tex;
799
+
800
+ } );
801
+
802
+ const [ srgbTextures, linearTextures ] = await Promise.all( [
803
+ Promise.all( srgbLists.map( list => buildBucket( list, true ) ) ),
804
+ Promise.all( linearLists.map( list => buildBucket( list, false ) ) ),
805
+ ] );
806
+
807
+ this.srgbBucketTextures = srgbTextures;
808
+ this.linearBucketTextures = linearTextures;
796
809
 
797
810
  this._log( 'Material textures complete', {
798
811
  materialData: !! this.materialData,
812
+ srgbBuckets: srgbTextures.map( t => ( t ? `${t.image.width}x${t.image.height}x${t.image.depth}` : '-' ) ).join( ',' ),
813
+ linearBuckets: linearTextures.map( t => ( t ? `${t.image.width}x${t.image.height}x${t.image.depth}` : '-' ) ).join( ',' ),
799
814
  } );
800
815
 
801
816
  } catch ( error ) {
@@ -807,6 +822,96 @@ export class SceneProcessor {
807
822
 
808
823
  }
809
824
 
825
+ /**
826
+ * Group the extractor's seven per-type texture arrays into two consolidated colorSpace
827
+ * pools (sRGB: albedo+emissive; linear: normal/bump/roughness/metalness/displacement),
828
+ * each split into MATERIAL_BUCKET_COUNT longest-edge size buckets. Textures are deduped
829
+ * across types within a (pool, bucket) so a shared image (e.g. ORM) costs one layer.
830
+ * @returns {{ srgbLists: Array<Array>, linearLists: Array<Array>, remap: Object }}
831
+ * bucket lists + per-type remap arrays (old per-type layer → packed bucket index).
832
+ * @private
833
+ */
834
+ _bucketTextures() {
835
+
836
+ const cap = this.config.maxTextureSize;
837
+ const K = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT;
838
+ const STRIDE = TEXTURE_CONSTANTS.BUCKET_LAYER_STRIDE;
839
+
840
+ const srgbLists = Array.from( { length: K }, () => [] );
841
+ const linearLists = Array.from( { length: K }, () => [] );
842
+ const srgbDedup = Array.from( { length: K }, () => new Map() );
843
+ const linearDedup = Array.from( { length: K }, () => new Map() );
844
+
845
+ // Persistent uuid → packed maps so runtime material edits (updateMaterial) can re-pack
846
+ // a texture's index against the CURRENT bucket layout instead of the stale per-type index.
847
+ this._srgbTexPacked = new Map();
848
+ this._linearTexPacked = new Map();
849
+
850
+ // Assign one texture to its (bucket, layer) within a pool; dedup by source uuid.
851
+ const assign = ( tex, lists, dedup, flat ) => {
852
+
853
+ if ( ! tex || ! tex.image ) return - 1;
854
+ const bucket = getTextureBucketId( tex.image.width, tex.image.height, cap, K );
855
+ const uuid = tex.source?.uuid ?? tex.uuid;
856
+ const seen = dedup[ bucket ].get( uuid );
857
+ if ( seen !== undefined ) return packTextureIndex( bucket, seen );
858
+ if ( lists[ bucket ].length >= STRIDE ) {
859
+
860
+ console.warn( `[SceneProcessor] Texture bucket ${bucket} full (${STRIDE}); dropping a map.` );
861
+ return - 1;
862
+
863
+ }
864
+
865
+ lists[ bucket ].push( tex );
866
+ const layer = lists[ bucket ].length - 1;
867
+ dedup[ bucket ].set( uuid, layer );
868
+ const packed = packTextureIndex( bucket, layer );
869
+ flat.set( uuid, packed );
870
+ return packed;
871
+
872
+ };
873
+
874
+ // Per-type arrays hold unique textures indexed by the layer the extractor assigned
875
+ // (= array position), so remap[type][oldLayer] = packed index.
876
+ const remapType = ( arr, lists, dedup, flat ) => ( arr || [] ).map( tex => assign( tex, lists, dedup, flat ) );
877
+
878
+ const remap = {
879
+ albedo: remapType( this.maps, srgbLists, srgbDedup, this._srgbTexPacked ),
880
+ emissive: remapType( this.emissiveMaps, srgbLists, srgbDedup, this._srgbTexPacked ),
881
+ normal: remapType( this.normalMaps, linearLists, linearDedup, this._linearTexPacked ),
882
+ bump: remapType( this.bumpMaps, linearLists, linearDedup, this._linearTexPacked ),
883
+ roughness: remapType( this.roughnessMaps, linearLists, linearDedup, this._linearTexPacked ),
884
+ metalness: remapType( this.metalnessMaps, linearLists, linearDedup, this._linearTexPacked ),
885
+ displacement: remapType( this.displacementMaps, linearLists, linearDedup, this._linearTexPacked ),
886
+ };
887
+
888
+ return { srgbLists, linearLists, remap };
889
+
890
+ }
891
+
892
+ /**
893
+ * Rewrite each material's per-map index from the extractor's per-type layer to the
894
+ * packed (bucket, layer) index. Idempotency is NOT guaranteed — call exactly once per
895
+ * extraction (materials are freshly extracted on each process/rebuild).
896
+ * @private
897
+ */
898
+ _remapMaterialTextureIndices( remap ) {
899
+
900
+ const fix = ( v, table ) => ( v >= 0 && v < table.length ? table[ v ] : - 1 );
901
+ for ( const mat of this.materials ) {
902
+
903
+ mat.map = fix( mat.map, remap.albedo );
904
+ mat.emissiveMap = fix( mat.emissiveMap, remap.emissive );
905
+ mat.normalMap = fix( mat.normalMap, remap.normal );
906
+ mat.bumpMap = fix( mat.bumpMap, remap.bump );
907
+ mat.roughnessMap = fix( mat.roughnessMap, remap.roughness );
908
+ mat.metalnessMap = fix( mat.metalnessMap, remap.metalness );
909
+ mat.displacementMap = fix( mat.displacementMap, remap.displacement );
910
+
911
+ }
912
+
913
+ }
914
+
810
915
  /**
811
916
  * Extract emissive triangles and build Light BVH.
812
917
  * MUST run after BVH reordering — emissive data stores triangle indices
@@ -905,27 +1010,42 @@ export class SceneProcessor {
905
1010
  */
906
1011
  _disposeTextures() {
907
1012
 
908
- const textureProps = [
909
- 'albedoTextures', 'normalTextures', 'bumpTextures', 'roughnessTextures',
910
- 'metalnessTextures', 'emissiveTextures', 'displacementTextures'
911
- ];
1013
+ this._disposeBucketTextures();
1014
+
1015
+ }
1016
+
1017
+ /**
1018
+ * Dispose the consolidated bucket arrays (srgb/linear), each an Array<K> of
1019
+ * DataArrayTexture | null.
1020
+ * @private
1021
+ */
1022
+ _disposeBucketTextures() {
912
1023
 
913
- // Dispose each texture if it exists
914
- textureProps.forEach( prop => {
1024
+ for ( const prop of [ 'srgbBucketTextures', 'linearBucketTextures' ] ) {
915
1025
 
916
- if ( this[ prop ] ) {
1026
+ const arr = this[ prop ];
1027
+ if ( ! arr ) continue;
1028
+ for ( const tex of arr ) {
917
1029
 
918
- if ( typeof this[ prop ].dispose === 'function' ) {
1030
+ if ( tex && typeof tex.dispose === 'function' ) {
919
1031
 
920
- this[ prop ].dispose();
1032
+ try {
921
1033
 
922
- }
1034
+ tex.dispose();
1035
+
1036
+ } catch ( error ) {
923
1037
 
924
- this[ prop ] = null;
1038
+ console.warn( `[SceneProcessor] Error disposing ${prop}:`, error );
1039
+
1040
+ }
1041
+
1042
+ }
925
1043
 
926
1044
  }
927
1045
 
928
- } );
1046
+ this[ prop ] = null;
1047
+
1048
+ }
929
1049
 
930
1050
  }
931
1051
 
@@ -969,34 +1089,9 @@ export class SceneProcessor {
969
1089
  this.displacementMaps = extractedData.displacementMaps;
970
1090
  this.sceneFeatures = extractedData.sceneFeatures; // Update material feature flags
971
1091
 
972
- // Create new material and texture data only
973
- const params = {
974
- materials: this.materials,
975
- triangles: this.triangleData, // Reuse existing triangle data
976
- maps: this.maps,
977
- normalMaps: this.normalMaps,
978
- bumpMaps: this.bumpMaps,
979
- roughnessMaps: this.roughnessMaps,
980
- metalnessMaps: this.metalnessMaps,
981
- emissiveMaps: this.emissiveMaps,
982
- displacementMaps: this.displacementMaps,
983
- bvhRoot: this.bvhRoot // Reuse existing BVH
984
- };
985
-
986
- // Create only material and texture-related textures
987
- const textures = await this.textureCreator.createMaterialTextures( params );
988
-
989
- // Regenerate raw material data for storage buffers
990
- this.materialData = this.textureCreator.createMaterialRawData( this.materials );
991
-
992
- // Update texture references (keep triangle and BVH data unchanged)
993
- this.albedoTextures = textures.albedoTexture;
994
- this.normalTextures = textures.normalTexture;
995
- this.bumpTextures = textures.bumpTexture;
996
- this.roughnessTextures = textures.roughnessTexture;
997
- this.metalnessTextures = textures.metalnessTexture;
998
- this.emissiveTextures = textures.emissiveTexture;
999
- this.displacementTextures = textures.displacementTexture;
1092
+ // Bucket textures, remap material indices, regenerate raw material data, and
1093
+ // build the consolidated bucket arrays — same path as the initial build.
1094
+ await this._createMaterialTextures();
1000
1095
 
1001
1096
  const duration = performance.now() - startTime;
1002
1097
  this._log( `Material rebuild complete (${duration.toFixed( 2 )}ms)`, {
@@ -1026,37 +1121,7 @@ export class SceneProcessor {
1026
1121
  */
1027
1122
  _disposeMaterialTextures() {
1028
1123
 
1029
- const materialTextureProps = [
1030
- 'albedoTextures', 'normalTextures',
1031
- 'bumpTextures', 'roughnessTextures', 'metalnessTextures', 'emissiveTextures',
1032
- 'displacementTextures'
1033
- ];
1034
-
1035
- materialTextureProps.forEach( prop => {
1036
-
1037
- if ( this[ prop ] ) {
1038
-
1039
- try {
1040
-
1041
- if ( typeof this[ prop ].dispose === 'function' ) {
1042
-
1043
- this[ prop ].dispose();
1044
-
1045
- }
1046
-
1047
- } catch ( error ) {
1048
-
1049
- console.warn( `[SceneProcessor] Error disposing ${prop}:`, error );
1050
-
1051
- } finally {
1052
-
1053
- this[ prop ] = null;
1054
-
1055
- }
1056
-
1057
- }
1058
-
1059
- } );
1124
+ this._disposeBucketTextures();
1060
1125
 
1061
1126
  // Clear texture creator cache to prevent stale references
1062
1127
  if ( this.textureCreator && this.textureCreator.textureCache ) {
@@ -1407,14 +1472,12 @@ export class SceneProcessor {
1407
1472
  }
1408
1473
 
1409
1474
  pathTracer.materialData.setMaterialTextures( {
1410
- albedoMaps: this.albedoTextures,
1411
- normalMaps: this.normalTextures,
1412
- bumpMaps: this.bumpTextures,
1413
- roughnessMaps: this.roughnessTextures,
1414
- metalnessMaps: this.metalnessTextures,
1415
- emissiveMaps: this.emissiveTextures,
1416
- displacementMaps: this.displacementTextures,
1475
+ srgbBuckets: this.srgbBucketTextures,
1476
+ linearBuckets: this.linearBucketTextures,
1417
1477
  } );
1478
+ // Hand the uuid→packed maps to materialData so runtime edits (updateMaterial) can
1479
+ // re-pack a texture's index against this scene's bucket layout.
1480
+ pathTracer.materialData.setTexturePackMaps?.( this._srgbTexPacked, this._linearTexPacked );
1418
1481
 
1419
1482
  if ( this.emissiveTriangleData ) {
1420
1483
 
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { texture } from 'three/tsl';
11
11
  import { LinearFilter, DataArrayTexture } from 'three';
12
- import { setShadowAlbedoMaps, setAlphaShadowsUniform } from '../TSL/LightsDirect.js';
12
+ import { setAlphaShadowsUniform } from '../TSL/LightsDirect.js';
13
13
  import { setGoboMapsTexture, setIESProfilesTexture } from '../TSL/LightsCore.js';
14
14
 
15
15
  export class ShaderBuilder {
@@ -30,7 +30,6 @@ export class ShaderBuilder {
30
30
  const nodes = this._sceneTextureNodes;
31
31
 
32
32
  const env = stage.environment;
33
- const mat = stage.materialData;
34
33
 
35
34
  if ( env.environmentTexture && nodes.envTex ) {
36
35
 
@@ -38,13 +37,8 @@ export class ShaderBuilder {
38
37
 
39
38
  }
40
39
 
41
- if ( mat.albedoMaps && nodes.albedoMapsTex ) nodes.albedoMapsTex.value = mat.albedoMaps;
42
- if ( mat.normalMaps && nodes.normalMapsTex ) nodes.normalMapsTex.value = mat.normalMaps;
43
- if ( mat.bumpMaps && nodes.bumpMapsTex ) nodes.bumpMapsTex.value = mat.bumpMaps;
44
- if ( mat.metalnessMaps && nodes.metalnessMapsTex ) nodes.metalnessMapsTex.value = mat.metalnessMaps;
45
- if ( mat.roughnessMaps && nodes.roughnessMapsTex ) nodes.roughnessMapsTex.value = mat.roughnessMaps;
46
- if ( mat.emissiveMaps && nodes.emissiveMapsTex ) nodes.emissiveMapsTex.value = mat.emissiveMaps;
47
- if ( mat.displacementMaps && nodes.displacementMapsTex ) nodes.displacementMapsTex.value = mat.displacementMaps;
40
+ // Material bucket arrays are owned by PathTracer's independent wavefront nodes
41
+ // (_refreshWfTextureNodes); nothing to swap here.
48
42
  if ( stage.goboMaps && nodes.goboMapsTex ) nodes.goboMapsTex.value = stage.goboMaps;
49
43
  if ( stage.iesProfiles && nodes.iesProfilesTex ) nodes.iesProfilesTex.value = stage.iesProfiles;
50
44
 
@@ -116,14 +110,9 @@ export class ShaderBuilder {
116
110
 
117
111
  };
118
112
 
119
- const mat = stage.materialData;
120
- const albedoMapsTex = mat.albedoMaps ? texture( mat.albedoMaps ) : createArrayPlaceholder();
121
- const normalMapsTex = mat.normalMaps ? texture( mat.normalMaps ) : createArrayPlaceholder();
122
- const bumpMapsTex = mat.bumpMaps ? texture( mat.bumpMaps ) : createArrayPlaceholder();
123
- const metalnessMapsTex = mat.metalnessMaps ? texture( mat.metalnessMaps ) : createArrayPlaceholder();
124
- const roughnessMapsTex = mat.roughnessMaps ? texture( mat.roughnessMaps ) : createArrayPlaceholder();
125
- const emissiveMapsTex = mat.emissiveMaps ? texture( mat.emissiveMaps ) : createArrayPlaceholder();
126
- const displacementMapsTex = mat.displacementMaps ? texture( mat.displacementMaps ) : createArrayPlaceholder();
113
+ // Material map arrays (consolidated size buckets) are owned by PathTracer's
114
+ // independent wavefront nodes + setMaterialBucketTextures/setShadowAlbedoMaps
115
+ // see PathTracer._buildWavefrontKernels. Nothing material-map related is bound here.
127
116
 
128
117
  // Spot light gobo array — placeholder until GoboManager populates it.
129
118
  const goboMapsTex = stage.goboMaps ? texture( stage.goboMaps ) : createArrayPlaceholder();
@@ -133,16 +122,9 @@ export class ShaderBuilder {
133
122
  const iesProfilesTex = stage.iesProfiles ? texture( stage.iesProfiles ) : createArrayPlaceholder();
134
123
  setIESProfilesTexture( iesProfilesTex );
135
124
 
136
- // Set albedo texture array for alpha-aware shadow rays (module-level in LightsDirect.js).
137
- // Always pass the texture node (real or placeholder) so alpha-cutout code is emitted
138
- // into the shader at graph construction time. Runtime albedoMapIndex >= 0 guards sampling.
139
- setShadowAlbedoMaps( albedoMapsTex );
140
-
141
125
  const result = {
142
126
  triStorage, bvhStorage, matStorage, lightBufferStorage,
143
127
  envTex,
144
- albedoMapsTex, normalMapsTex, bumpMapsTex,
145
- metalnessMapsTex, roughnessMapsTex, emissiveMapsTex, displacementMapsTex,
146
128
  goboMapsTex, iesProfilesTex,
147
129
  };
148
130
 
@@ -537,9 +537,6 @@ export class TextureCreator {
537
537
  case 'worker-direct':
538
538
  result = await this.processWithWorkerDirect( normalized );
539
539
  break;
540
- case 'worker-chunked':
541
- result = await this.processWithWorkerChunked( normalized, strategy.chunkSize );
542
- break;
543
540
  case 'main-batch':
544
541
  result = await this.processOnMainThreadBatch( normalized, strategy.batchSize );
545
542
  break;
@@ -587,10 +584,10 @@ export class TextureCreator {
587
584
 
588
585
  if ( this.capabilities.workers && estimatedMemory > MEMORY_CONSTANTS.MAX_TEXTURE_MEMORY ) {
589
586
 
590
- return {
591
- method: 'worker-chunked',
592
- chunkSize: Math.max( 1, Math.floor( textures.length / 4 ) )
593
- };
587
+ // Very large texture set: stream on the main thread (GC-yielding, builds the
588
+ // full array correctly). The former 'worker-chunked' path combined its chunks
589
+ // by returning only the first, silently dropping the rest removed.
590
+ return { method: 'main-streaming' };
594
591
 
595
592
  } else if ( this.capabilities.workers && totalPixels > 2097152 ) {
596
593
 
@@ -710,8 +707,18 @@ export class TextureCreator {
710
707
 
711
708
  try {
712
709
 
713
- // Option 1: Direct ImageBitmap transfer (when supported)
714
- if ( typeof createImageBitmap !== 'undefined' && texture.image instanceof HTMLImageElement ) {
710
+ // Option 1: Direct ImageBitmap transfer (when supported). Covers HTMLImageElement
711
+ // AND ImageBitmap (what GLTFLoader's ImageBitmapLoader produces) and canvases the
712
+ // worker extracts pixels on its OffscreenCanvas, so this keeps the full-res
713
+ // getImageData off the main thread (Option 2 blocks it per texture).
714
+ const img = texture.image;
715
+ const canDirect = typeof createImageBitmap !== 'undefined' && (
716
+ img instanceof HTMLImageElement
717
+ || ( typeof ImageBitmap !== 'undefined' && img instanceof ImageBitmap )
718
+ || ( typeof HTMLCanvasElement !== 'undefined' && img instanceof HTMLCanvasElement )
719
+ || ( typeof OffscreenCanvas !== 'undefined' && img instanceof OffscreenCanvas )
720
+ );
721
+ if ( canDirect ) {
715
722
 
716
723
  const bitmap = await createImageBitmap( texture.image, {
717
724
  imageOrientation: flipY ? 'flipY' : 'none',
@@ -760,21 +767,6 @@ export class TextureCreator {
760
767
 
761
768
  }
762
769
 
763
- async processWithWorkerChunked( textures, chunkSize ) {
764
-
765
- const results = [];
766
- for ( let i = 0; i < textures.length; i += chunkSize ) {
767
-
768
- const chunk = textures.slice( i, i + chunkSize );
769
- const chunkResult = await this.processWithWorkerDirect( chunk );
770
- results.push( chunkResult );
771
-
772
- }
773
-
774
- return this.combineTextureResults( results );
775
-
776
- }
777
-
778
770
  async processOnMainThreadBatch( textures, batchSize ) {
779
771
 
780
772
  const validTextures = textures.filter( tex => tex?.image );
@@ -1451,14 +1443,6 @@ export class TextureCreator {
1451
1443
 
1452
1444
  }
1453
1445
 
1454
- combineTextureResults( results ) {
1455
-
1456
- // Combine multiple texture array results into one
1457
- // This is a simplified implementation - you may need more sophisticated merging
1458
- return results[ 0 ]; // For now, return first result
1459
-
1460
- }
1461
-
1462
1446
  dispose() {
1463
1447
 
1464
1448
  this.canvasPool.dispose();
@@ -184,8 +184,13 @@ function handleAssemble( data ) {
184
184
 
185
185
  function handleFullBuild( data ) {
186
186
 
187
- const { triangleData, triangleByteOffset, triangleByteLength, depth, reportProgress, treeletOptimization, reinsertionOptimization, sharedReorderBuffer } = data;
187
+ const { triangleData, triangleByteOffset, triangleByteLength, depth, reportProgress, treeletOptimization, reinsertionOptimization, sharedReorderBuffer, maxLeafSize, numBins, maxBins, minBins } = data;
188
188
  const builder = new BVHBuilder();
189
+ // Honor the build params forwarded from SceneProcessor (previously ignored → default leaf 8).
190
+ if ( maxLeafSize !== undefined ) builder.maxLeafSize = maxLeafSize;
191
+ if ( numBins !== undefined ) builder.numBins = numBins;
192
+ if ( maxBins !== undefined ) builder.maxBins = maxBins;
193
+ if ( minBins !== undefined ) builder.minBins = minBins;
189
194
 
190
195
  try {
191
196
 
@@ -105,10 +105,11 @@ export class EdgeFilter extends RenderStage {
105
105
  const center = textureLoad( inputTex, coord ).xyz;
106
106
  const centerLum = dot( center, REC709_LUMINANCE_COEFFICIENTS );
107
107
 
108
- // NormalDepth writes (0,0,0, 1e6) for miss rays. Decoded normal
108
+ // NormalDepth writes (0,0,0, 65504) for miss rays. Decoded normal
109
109
  // (-1,-1,-1) is non-unit and explodes pow(dot, phi); use the depth
110
- // sentinel as a 0/1 hit flag and zero out cross-kind weights.
111
- const MISS_THRESHOLD = 1e5;
110
+ // sentinel as a 0/1 hit flag and zero out cross-kind weights. Threshold
111
+ // sits below the 65504 sentinel (and above any real depth).
112
+ const MISS_THRESHOLD = 6e4;
112
113
  const centerND = textureLoad( ndTex, coord );
113
114
  const centerNormal = centerND.xyz.mul( 2.0 ).sub( 1.0 );
114
115
  const centerDepth = centerND.w;
@@ -172,8 +172,8 @@ export class MotionVector extends RenderStage {
172
172
 
173
173
  const result = vec4( 0.0, 0.0, linearDepth, 1.0 ).toVar();
174
174
 
175
- // Sky / background (depth >= 1e5) — no motion
176
- If( linearDepth.lessThan( float( 1e5 ) ), () => {
175
+ // Sky / background (depth >= 65504 sentinel) — no motion
176
+ If( linearDepth.lessThan( float( 6e4 ) ), () => {
177
177
 
178
178
  // Pixel coordinate → NDC
179
179
  // Negate Y to match PathTracer convention
@@ -275,8 +275,8 @@ export class MotionVector extends RenderStage {
275
275
 
276
276
  const result = vec4( 0.0, 0.0, 0.0, 0.0 ).toVar();
277
277
 
278
- // Skip first frame and sky
279
- If( isFirstFrameU.lessThan( 0.5 ).and( linearDepth.lessThan( float( 1e5 ) ) ), () => {
278
+ // Skip first frame and sky (depth >= 65504 sentinel)
279
+ If( isFirstFrameU.lessThan( 0.5 ).and( linearDepth.lessThan( float( 6e4 ) ) ), () => {
280
280
 
281
281
  // Pixel coordinate → NDC
282
282
  // Negate Y to match PathTracer convention