rayzee 7.7.0 → 7.8.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 +1 -1
- package/dist/rayzee.es.js +1929 -1928
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +55 -55
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +45 -1
- package/src/Processor/SceneProcessor.js +165 -112
- package/src/Processor/ShaderBuilder.js +6 -24
- package/src/Stages/NormalDepth.js +16 -27
- package/src/Stages/PathTracer.js +16 -37
- package/src/TSL/DebugKernel.js +0 -2
- package/src/TSL/Debugger.js +1 -8
- package/src/TSL/Displacement.js +10 -10
- package/src/TSL/LightsDirect.js +8 -9
- package/src/TSL/ShadeKernel.js +1 -6
- package/src/TSL/TextureSampling.js +155 -34
- package/src/managers/MaterialDataManager.js +71 -39
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -492,9 +492,53 @@ export const TEXTURE_CONSTANTS = {
|
|
|
492
492
|
// guaranteed minimum). The configurable maxTextureSize setting is clamped to this.
|
|
493
493
|
MAX_TEXTURE_SIZE: 8192,
|
|
494
494
|
// Default cap applied when no maxTextureSize is supplied (engine standalone use).
|
|
495
|
-
DEFAULT_MAX_TEXTURE_SIZE: 4096
|
|
495
|
+
DEFAULT_MAX_TEXTURE_SIZE: 4096,
|
|
496
|
+
// Max layers (textures) per bucket array. Also the packing stride for (bucket, layer).
|
|
497
|
+
MAX_TEXTURES_LIMIT: 128,
|
|
498
|
+
// Size buckets per colorSpace pool. Material maps are grouped into this many
|
|
499
|
+
// longest-edge size classes so a small map no longer pays a large neighbour's
|
|
500
|
+
// footprint. 4 → ~8 bound material arrays (4 sRGB + 4 linear).
|
|
501
|
+
MATERIAL_BUCKET_COUNT: 4,
|
|
502
|
+
// Packing stride: a map's stored index encodes bucketId * BUCKET_LAYER_STRIDE + layer.
|
|
503
|
+
// Also the per-bucket layer cap. Kept at the WebGPU portable maxTextureArrayLayers floor (256)
|
|
504
|
+
// so a consolidated bucket (which merges several map types of one size) stays portable.
|
|
505
|
+
BUCKET_LAYER_STRIDE: 256,
|
|
496
506
|
};
|
|
497
507
|
|
|
508
|
+
// Longest-edge size ladder for a given cap, ascending.
|
|
509
|
+
// cap=4096, count=4 → [512, 1024, 2048, 4096].
|
|
510
|
+
export function getTextureBucketSizes( maxTextureSize, count = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT ) {
|
|
511
|
+
|
|
512
|
+
const sizes = [];
|
|
513
|
+
for ( let i = count - 1; i >= 0; i -- ) {
|
|
514
|
+
|
|
515
|
+
sizes.push( Math.max( TEXTURE_CONSTANTS.MIN_TEXTURE_WIDTH, Math.round( maxTextureSize / Math.pow( 2, i ) ) ) );
|
|
516
|
+
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return sizes;
|
|
520
|
+
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Bucket index for a texture, by its power-of-2 longest edge vs the cap's ladder.
|
|
524
|
+
// Larger-than-cap maps land in the top bucket (downscaled to cap, as before).
|
|
525
|
+
export function getTextureBucketId( width, height, maxTextureSize, count = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT ) {
|
|
526
|
+
|
|
527
|
+
const longest = Math.max( width || 1, height || 1 );
|
|
528
|
+
const pot = Math.pow( 2, Math.ceil( Math.log2( longest ) ) );
|
|
529
|
+
const sizes = getTextureBucketSizes( maxTextureSize, count );
|
|
530
|
+
for ( let i = 0; i < sizes.length; i ++ ) if ( pot <= sizes[ i ] ) return i;
|
|
531
|
+
return sizes.length - 1;
|
|
532
|
+
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Pack (bucketId, layer) into the single int slot a material map index occupies.
|
|
536
|
+
export function packTextureIndex( bucketId, layer ) {
|
|
537
|
+
|
|
538
|
+
return bucketId * TEXTURE_CONSTANTS.BUCKET_LAYER_STRIDE + layer;
|
|
539
|
+
|
|
540
|
+
}
|
|
541
|
+
|
|
498
542
|
// Default texture matrix for materials
|
|
499
543
|
export const DEFAULT_TEXTURE_MATRIX = [ 0, 0, 1, 1, 0, 0, 0, 1 ];
|
|
500
544
|
|
|
@@ -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 {
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
this.
|
|
88
|
-
this.
|
|
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;
|
|
@@ -762,40 +762,45 @@ export class SceneProcessor {
|
|
|
762
762
|
|
|
763
763
|
try {
|
|
764
764
|
|
|
765
|
-
//
|
|
765
|
+
// Group the extractor's per-type arrays into consolidated colorSpace×size-bucket
|
|
766
|
+
// pools, and rewrite each material's per-map index to the packed (bucket, layer)
|
|
767
|
+
// form. Must run BEFORE createMaterialRawData (which reads mat.map etc.).
|
|
768
|
+
const { srgbLists, linearLists, remap } = this._bucketTextures();
|
|
769
|
+
this._remapMaterialTextureIndices( remap );
|
|
770
|
+
|
|
771
|
+
// Material raw data for storage buffers (sync, ~1-5ms) — now holds packed indices.
|
|
766
772
|
if ( this.materials?.length ) {
|
|
767
773
|
|
|
768
774
|
this.materialData = this.textureCreator.createMaterialRawData( this.materials );
|
|
769
775
|
|
|
770
776
|
}
|
|
771
777
|
|
|
772
|
-
//
|
|
773
|
-
//
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
} )
|
|
794
|
-
)
|
|
795
|
-
);
|
|
778
|
+
// One DataArrayTexture per non-empty bucket. The sRGB pool (albedo + emissive — both
|
|
779
|
+
// authored in sRGB per glTF) carries SRGBColorSpace so the GPU decodes sRGB→linear
|
|
780
|
+
// before lighting; the linear pool (normal/roughness/metalness/bump/displacement —
|
|
781
|
+
// data textures) stays linear. Applied consistently across load AND rebuildMaterials
|
|
782
|
+
// (the prior model-load path omitted this, leaving albedo un-decoded / too bright).
|
|
783
|
+
const buildBucket = ( list, srgb ) => list.length === 0
|
|
784
|
+
? Promise.resolve( null )
|
|
785
|
+
: this.textureCreator.createTexturesToDataTexture( list ).then( tex => {
|
|
786
|
+
|
|
787
|
+
if ( tex && srgb ) tex.colorSpace = SRGBColorSpace;
|
|
788
|
+
return tex;
|
|
789
|
+
|
|
790
|
+
} );
|
|
791
|
+
|
|
792
|
+
const [ srgbTextures, linearTextures ] = await Promise.all( [
|
|
793
|
+
Promise.all( srgbLists.map( list => buildBucket( list, true ) ) ),
|
|
794
|
+
Promise.all( linearLists.map( list => buildBucket( list, false ) ) ),
|
|
795
|
+
] );
|
|
796
|
+
|
|
797
|
+
this.srgbBucketTextures = srgbTextures;
|
|
798
|
+
this.linearBucketTextures = linearTextures;
|
|
796
799
|
|
|
797
800
|
this._log( 'Material textures complete', {
|
|
798
801
|
materialData: !! this.materialData,
|
|
802
|
+
srgbBuckets: srgbTextures.map( t => ( t ? `${t.image.width}x${t.image.height}x${t.image.depth}` : '-' ) ).join( ',' ),
|
|
803
|
+
linearBuckets: linearTextures.map( t => ( t ? `${t.image.width}x${t.image.height}x${t.image.depth}` : '-' ) ).join( ',' ),
|
|
799
804
|
} );
|
|
800
805
|
|
|
801
806
|
} catch ( error ) {
|
|
@@ -807,6 +812,96 @@ export class SceneProcessor {
|
|
|
807
812
|
|
|
808
813
|
}
|
|
809
814
|
|
|
815
|
+
/**
|
|
816
|
+
* Group the extractor's seven per-type texture arrays into two consolidated colorSpace
|
|
817
|
+
* pools (sRGB: albedo+emissive; linear: normal/bump/roughness/metalness/displacement),
|
|
818
|
+
* each split into MATERIAL_BUCKET_COUNT longest-edge size buckets. Textures are deduped
|
|
819
|
+
* across types within a (pool, bucket) so a shared image (e.g. ORM) costs one layer.
|
|
820
|
+
* @returns {{ srgbLists: Array<Array>, linearLists: Array<Array>, remap: Object }}
|
|
821
|
+
* bucket lists + per-type remap arrays (old per-type layer → packed bucket index).
|
|
822
|
+
* @private
|
|
823
|
+
*/
|
|
824
|
+
_bucketTextures() {
|
|
825
|
+
|
|
826
|
+
const cap = this.config.maxTextureSize;
|
|
827
|
+
const K = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT;
|
|
828
|
+
const STRIDE = TEXTURE_CONSTANTS.BUCKET_LAYER_STRIDE;
|
|
829
|
+
|
|
830
|
+
const srgbLists = Array.from( { length: K }, () => [] );
|
|
831
|
+
const linearLists = Array.from( { length: K }, () => [] );
|
|
832
|
+
const srgbDedup = Array.from( { length: K }, () => new Map() );
|
|
833
|
+
const linearDedup = Array.from( { length: K }, () => new Map() );
|
|
834
|
+
|
|
835
|
+
// Persistent uuid → packed maps so runtime material edits (updateMaterial) can re-pack
|
|
836
|
+
// a texture's index against the CURRENT bucket layout instead of the stale per-type index.
|
|
837
|
+
this._srgbTexPacked = new Map();
|
|
838
|
+
this._linearTexPacked = new Map();
|
|
839
|
+
|
|
840
|
+
// Assign one texture to its (bucket, layer) within a pool; dedup by source uuid.
|
|
841
|
+
const assign = ( tex, lists, dedup, flat ) => {
|
|
842
|
+
|
|
843
|
+
if ( ! tex || ! tex.image ) return - 1;
|
|
844
|
+
const bucket = getTextureBucketId( tex.image.width, tex.image.height, cap, K );
|
|
845
|
+
const uuid = tex.source?.uuid ?? tex.uuid;
|
|
846
|
+
const seen = dedup[ bucket ].get( uuid );
|
|
847
|
+
if ( seen !== undefined ) return packTextureIndex( bucket, seen );
|
|
848
|
+
if ( lists[ bucket ].length >= STRIDE ) {
|
|
849
|
+
|
|
850
|
+
console.warn( `[SceneProcessor] Texture bucket ${bucket} full (${STRIDE}); dropping a map.` );
|
|
851
|
+
return - 1;
|
|
852
|
+
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
lists[ bucket ].push( tex );
|
|
856
|
+
const layer = lists[ bucket ].length - 1;
|
|
857
|
+
dedup[ bucket ].set( uuid, layer );
|
|
858
|
+
const packed = packTextureIndex( bucket, layer );
|
|
859
|
+
flat.set( uuid, packed );
|
|
860
|
+
return packed;
|
|
861
|
+
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
// Per-type arrays hold unique textures indexed by the layer the extractor assigned
|
|
865
|
+
// (= array position), so remap[type][oldLayer] = packed index.
|
|
866
|
+
const remapType = ( arr, lists, dedup, flat ) => ( arr || [] ).map( tex => assign( tex, lists, dedup, flat ) );
|
|
867
|
+
|
|
868
|
+
const remap = {
|
|
869
|
+
albedo: remapType( this.maps, srgbLists, srgbDedup, this._srgbTexPacked ),
|
|
870
|
+
emissive: remapType( this.emissiveMaps, srgbLists, srgbDedup, this._srgbTexPacked ),
|
|
871
|
+
normal: remapType( this.normalMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
872
|
+
bump: remapType( this.bumpMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
873
|
+
roughness: remapType( this.roughnessMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
874
|
+
metalness: remapType( this.metalnessMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
875
|
+
displacement: remapType( this.displacementMaps, linearLists, linearDedup, this._linearTexPacked ),
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
return { srgbLists, linearLists, remap };
|
|
879
|
+
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Rewrite each material's per-map index from the extractor's per-type layer to the
|
|
884
|
+
* packed (bucket, layer) index. Idempotency is NOT guaranteed — call exactly once per
|
|
885
|
+
* extraction (materials are freshly extracted on each process/rebuild).
|
|
886
|
+
* @private
|
|
887
|
+
*/
|
|
888
|
+
_remapMaterialTextureIndices( remap ) {
|
|
889
|
+
|
|
890
|
+
const fix = ( v, table ) => ( v >= 0 && v < table.length ? table[ v ] : - 1 );
|
|
891
|
+
for ( const mat of this.materials ) {
|
|
892
|
+
|
|
893
|
+
mat.map = fix( mat.map, remap.albedo );
|
|
894
|
+
mat.emissiveMap = fix( mat.emissiveMap, remap.emissive );
|
|
895
|
+
mat.normalMap = fix( mat.normalMap, remap.normal );
|
|
896
|
+
mat.bumpMap = fix( mat.bumpMap, remap.bump );
|
|
897
|
+
mat.roughnessMap = fix( mat.roughnessMap, remap.roughness );
|
|
898
|
+
mat.metalnessMap = fix( mat.metalnessMap, remap.metalness );
|
|
899
|
+
mat.displacementMap = fix( mat.displacementMap, remap.displacement );
|
|
900
|
+
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
}
|
|
904
|
+
|
|
810
905
|
/**
|
|
811
906
|
* Extract emissive triangles and build Light BVH.
|
|
812
907
|
* MUST run after BVH reordering — emissive data stores triangle indices
|
|
@@ -905,27 +1000,42 @@ export class SceneProcessor {
|
|
|
905
1000
|
*/
|
|
906
1001
|
_disposeTextures() {
|
|
907
1002
|
|
|
908
|
-
|
|
909
|
-
'albedoTextures', 'normalTextures', 'bumpTextures', 'roughnessTextures',
|
|
910
|
-
'metalnessTextures', 'emissiveTextures', 'displacementTextures'
|
|
911
|
-
];
|
|
1003
|
+
this._disposeBucketTextures();
|
|
912
1004
|
|
|
913
|
-
|
|
914
|
-
textureProps.forEach( prop => {
|
|
1005
|
+
}
|
|
915
1006
|
|
|
916
|
-
|
|
1007
|
+
/**
|
|
1008
|
+
* Dispose the consolidated bucket arrays (srgb/linear), each an Array<K> of
|
|
1009
|
+
* DataArrayTexture | null.
|
|
1010
|
+
* @private
|
|
1011
|
+
*/
|
|
1012
|
+
_disposeBucketTextures() {
|
|
917
1013
|
|
|
918
|
-
|
|
1014
|
+
for ( const prop of [ 'srgbBucketTextures', 'linearBucketTextures' ] ) {
|
|
919
1015
|
|
|
920
|
-
|
|
1016
|
+
const arr = this[ prop ];
|
|
1017
|
+
if ( ! arr ) continue;
|
|
1018
|
+
for ( const tex of arr ) {
|
|
921
1019
|
|
|
922
|
-
|
|
1020
|
+
if ( tex && typeof tex.dispose === 'function' ) {
|
|
923
1021
|
|
|
924
|
-
|
|
1022
|
+
try {
|
|
1023
|
+
|
|
1024
|
+
tex.dispose();
|
|
1025
|
+
|
|
1026
|
+
} catch ( error ) {
|
|
1027
|
+
|
|
1028
|
+
console.warn( `[SceneProcessor] Error disposing ${prop}:`, error );
|
|
1029
|
+
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
}
|
|
925
1033
|
|
|
926
1034
|
}
|
|
927
1035
|
|
|
928
|
-
|
|
1036
|
+
this[ prop ] = null;
|
|
1037
|
+
|
|
1038
|
+
}
|
|
929
1039
|
|
|
930
1040
|
}
|
|
931
1041
|
|
|
@@ -969,34 +1079,9 @@ export class SceneProcessor {
|
|
|
969
1079
|
this.displacementMaps = extractedData.displacementMaps;
|
|
970
1080
|
this.sceneFeatures = extractedData.sceneFeatures; // Update material feature flags
|
|
971
1081
|
|
|
972
|
-
//
|
|
973
|
-
|
|
974
|
-
|
|
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;
|
|
1082
|
+
// Bucket textures, remap material indices, regenerate raw material data, and
|
|
1083
|
+
// build the consolidated bucket arrays — same path as the initial build.
|
|
1084
|
+
await this._createMaterialTextures();
|
|
1000
1085
|
|
|
1001
1086
|
const duration = performance.now() - startTime;
|
|
1002
1087
|
this._log( `Material rebuild complete (${duration.toFixed( 2 )}ms)`, {
|
|
@@ -1026,37 +1111,7 @@ export class SceneProcessor {
|
|
|
1026
1111
|
*/
|
|
1027
1112
|
_disposeMaterialTextures() {
|
|
1028
1113
|
|
|
1029
|
-
|
|
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
|
-
} );
|
|
1114
|
+
this._disposeBucketTextures();
|
|
1060
1115
|
|
|
1061
1116
|
// Clear texture creator cache to prevent stale references
|
|
1062
1117
|
if ( this.textureCreator && this.textureCreator.textureCache ) {
|
|
@@ -1407,14 +1462,12 @@ export class SceneProcessor {
|
|
|
1407
1462
|
}
|
|
1408
1463
|
|
|
1409
1464
|
pathTracer.materialData.setMaterialTextures( {
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
bumpMaps: this.bumpTextures,
|
|
1413
|
-
roughnessMaps: this.roughnessTextures,
|
|
1414
|
-
metalnessMaps: this.metalnessTextures,
|
|
1415
|
-
emissiveMaps: this.emissiveTextures,
|
|
1416
|
-
displacementMaps: this.displacementTextures,
|
|
1465
|
+
srgbBuckets: this.srgbBucketTextures,
|
|
1466
|
+
linearBuckets: this.linearBucketTextures,
|
|
1417
1467
|
} );
|
|
1468
|
+
// Hand the uuid→packed maps to materialData so runtime edits (updateMaterial) can
|
|
1469
|
+
// re-pack a texture's index against this scene's bucket layout.
|
|
1470
|
+
pathTracer.materialData.setTexturePackMaps?.( this._srgbTexPacked, this._linearTexPacked );
|
|
1418
1471
|
|
|
1419
1472
|
if ( this.emissiveTriangleData ) {
|
|
1420
1473
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { texture } from 'three/tsl';
|
|
11
11
|
import { LinearFilter, DataArrayTexture } from 'three';
|
|
12
|
-
import {
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Fn, vec3, vec4, float, int, uint, uvec2, uniform, normalize, mat3, storage, If,
|
|
2
|
-
|
|
2
|
+
textureStore, workgroupId, localId } from 'three/tsl';
|
|
3
3
|
import { RenderTarget, StorageTexture } from 'three/webgpu';
|
|
4
|
-
import { HalfFloatType, RGBAFormat, NearestFilter,
|
|
4
|
+
import { HalfFloatType, RGBAFormat, NearestFilter, Matrix4, Box2, Vector2 } from 'three';
|
|
5
5
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
6
6
|
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
7
7
|
import { Ray, HitInfo, RayTracingMaterial, UVCache } from '../TSL/Struct.js';
|
|
8
8
|
import { traverseBVH } from '../TSL/BVHTraversal.js';
|
|
9
9
|
import { getMaterial } from '../TSL/Common.js';
|
|
10
|
-
import { computeUVCache, processNormal, processBump } from '../TSL/TextureSampling.js';
|
|
10
|
+
import { computeUVCache, processNormal, processBump, buildBucketTextureNodes, refreshBucketTextureNodes, setMaterialBucketTextures } from '../TSL/TextureSampling.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* NormalDepth — primary-ray G-buffer for SVGF gates.
|
|
@@ -104,22 +104,10 @@ export class NormalDepth extends RenderStage {
|
|
|
104
104
|
this._computeNode = null;
|
|
105
105
|
this._computeBuilt = false;
|
|
106
106
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
this.
|
|
111
|
-
this._bumpMapsTex = texture( this._makePlaceholderArray() );
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
_makePlaceholderArray() {
|
|
116
|
-
|
|
117
|
-
const t = new DataArrayTexture( new Uint8Array( [ 128, 128, 255, 255 ] ), 1, 1, 1 );
|
|
118
|
-
t.minFilter = LinearFilter;
|
|
119
|
-
t.magFilter = LinearFilter;
|
|
120
|
-
t.generateMipmaps = false;
|
|
121
|
-
t.needsUpdate = true;
|
|
122
|
-
return t;
|
|
107
|
+
// Independent linear-pool bucket nodes for this pipeline (normal + bump live in the
|
|
108
|
+
// linear pool). Built lazily in _buildCompute from the path tracer's materialData;
|
|
109
|
+
// value-swapped on model load. processNormal/processBump runtime-guard on map indices.
|
|
110
|
+
this._linearBuckets = null;
|
|
123
111
|
|
|
124
112
|
}
|
|
125
113
|
|
|
@@ -185,10 +173,8 @@ export class NormalDepth extends RenderStage {
|
|
|
185
173
|
|
|
186
174
|
}
|
|
187
175
|
|
|
188
|
-
// In-place
|
|
189
|
-
|
|
190
|
-
if ( md?.normalMaps ) this._normalMapsTex.value = md.normalMaps;
|
|
191
|
-
if ( md?.bumpMaps ) this._bumpMapsTex.value = md.bumpMaps;
|
|
176
|
+
// In-place bucket swaps (model change) — graph closes over the nodes, only .value changes.
|
|
177
|
+
if ( this._linearBuckets ) refreshBucketTextureNodes( this._linearBuckets, pt.materialData?.linearBuckets );
|
|
192
178
|
|
|
193
179
|
this._lastTriAttr = pt.triangleStorageAttr || this._lastTriAttr;
|
|
194
180
|
this._lastBvhAttr = pt.bvhStorageAttr || this._lastBvhAttr;
|
|
@@ -203,8 +189,11 @@ export class NormalDepth extends RenderStage {
|
|
|
203
189
|
const triStorage = this._triStorageNode;
|
|
204
190
|
const bvhStorage = this._bvhStorageNode;
|
|
205
191
|
const matStorage = this._matStorageNode;
|
|
206
|
-
|
|
207
|
-
|
|
192
|
+
// Independent linear-pool bucket nodes for this pipeline (normal + bump). The sRGB pool
|
|
193
|
+
// is never sampled here, so it gets placeholders. Publish to the sampling module before
|
|
194
|
+
// the graph is built so processNormal/processBump bake in THESE (per-pipeline) nodes.
|
|
195
|
+
this._linearBuckets = buildBucketTextureNodes( this.pathTracer?.materialData?.linearBuckets );
|
|
196
|
+
setMaterialBucketTextures( buildBucketTextureNodes( null ), this._linearBuckets );
|
|
208
197
|
const camWorld = this.cameraWorldMatrix;
|
|
209
198
|
const camProjInv = this.cameraProjectionMatrixInverse;
|
|
210
199
|
const resW = this.resolutionWidth;
|
|
@@ -266,8 +255,8 @@ export class NormalDepth extends RenderStage {
|
|
|
266
255
|
getMaterial( hit.materialIndex, matStorage )
|
|
267
256
|
).toVar();
|
|
268
257
|
const uvCache = UVCache.wrap( computeUVCache( hit.uv, material ) ).toVar();
|
|
269
|
-
const mapped = processNormal(
|
|
270
|
-
shadingNormal.assign( processBump(
|
|
258
|
+
const mapped = processNormal( hit.normal, material, uvCache ).toVar();
|
|
259
|
+
shadingNormal.assign( processBump( mapped, material, uvCache ) );
|
|
271
260
|
|
|
272
261
|
} );
|
|
273
262
|
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -17,6 +17,8 @@ import { buildShadeKernel, SHADE_WG_SIZE } from '../TSL/ShadeKernel.js';
|
|
|
17
17
|
import { buildCompactKernel, buildCompactSubgroupKernel, COMPACT_WG_SIZE } from '../TSL/CompactKernel.js';
|
|
18
18
|
import { buildFinalWriteKernel, FINALWRITE_WG_SIZE } from '../TSL/FinalWriteKernel.js';
|
|
19
19
|
import { buildDebugKernel, DEBUG_WG_SIZE } from '../TSL/DebugKernel.js';
|
|
20
|
+
import { setMaterialBucketTextures, buildBucketTextureNodes, refreshBucketTextureNodes } from '../TSL/TextureSampling.js';
|
|
21
|
+
import { setShadowAlbedoMaps } from '../TSL/LightsDirect.js';
|
|
20
22
|
import {
|
|
21
23
|
buildResetGlobalHistKernel, buildGlobalHistKernel, buildGlobalPrefixKernel, buildGlobalScatterKernel,
|
|
22
24
|
SORT_GLOBAL_WG_SIZE, SORT_GLOBAL_MAX_BINS,
|
|
@@ -126,8 +128,8 @@ export class PathTracer extends PathTracerStage {
|
|
|
126
128
|
if ( ! m ) return null;
|
|
127
129
|
return [
|
|
128
130
|
m.materialStorageAttr,
|
|
129
|
-
|
|
130
|
-
m.
|
|
131
|
+
...( m.srgbBuckets || [] ).filter( Boolean ),
|
|
132
|
+
...( m.linearBuckets || [] ).filter( Boolean ),
|
|
131
133
|
];
|
|
132
134
|
|
|
133
135
|
} );
|
|
@@ -449,13 +451,8 @@ export class PathTracer extends PathTracerStage {
|
|
|
449
451
|
|
|
450
452
|
const mat = this.materialData;
|
|
451
453
|
if ( ! mat ) return;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if ( mat.bumpMaps && t.bumpMaps ) t.bumpMaps.value = mat.bumpMaps;
|
|
455
|
-
if ( mat.metalnessMaps && t.metalnessMaps ) t.metalnessMaps.value = mat.metalnessMaps;
|
|
456
|
-
if ( mat.roughnessMaps && t.roughnessMaps ) t.roughnessMaps.value = mat.roughnessMaps;
|
|
457
|
-
if ( mat.emissiveMaps && t.emissiveMaps ) t.emissiveMaps.value = mat.emissiveMaps;
|
|
458
|
-
if ( mat.displacementMaps && t.displacementMaps ) t.displacementMaps.value = mat.displacementMaps;
|
|
454
|
+
refreshBucketTextureNodes( t.srgbBuckets, mat.srgbBuckets );
|
|
455
|
+
refreshBucketTextureNodes( t.linearBuckets, mat.linearBuckets );
|
|
459
456
|
|
|
460
457
|
}
|
|
461
458
|
|
|
@@ -661,26 +658,21 @@ export class PathTracer extends PathTracerStage {
|
|
|
661
658
|
// Independent texture nodes (never compiled elsewhere) avoid Three.js TextureNode caching across pipelines; refreshed via _refreshWfTextureNodes.
|
|
662
659
|
const _mat = this.materialData;
|
|
663
660
|
const _env = this.environment;
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
661
|
+
// Consolidated size-bucket nodes (K sRGB + K linear). Empty buckets get placeholders so
|
|
662
|
+
// every runtime branch references a valid node. Published to the sampling module before
|
|
663
|
+
// the Shade/Debug graphs are built so they bake in these (per-pipeline) nodes.
|
|
664
|
+
const freshSrgbBuckets = buildBucketTextureNodes( _mat.srgbBuckets );
|
|
665
|
+
const freshLinearBuckets = buildBucketTextureNodes( _mat.linearBuckets );
|
|
666
|
+
setMaterialBucketTextures( freshSrgbBuckets, freshLinearBuckets );
|
|
667
|
+
// Alpha-cutout shadow rays sample albedo (sRGB pool) — emitted into the shade graph now.
|
|
668
|
+
setShadowAlbedoMaps( freshSrgbBuckets );
|
|
672
669
|
const freshEnvTex = _env.environmentTexture ? texture( _env.environmentTexture ) : texNodes.envTex;
|
|
673
670
|
|
|
674
671
|
this._wfTexNodes = {
|
|
675
672
|
envTex: freshEnvTex,
|
|
676
673
|
envCDFTex: freshEnvCDF,
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
bumpMaps: freshBumpMaps,
|
|
680
|
-
metalnessMaps: freshMetalnessMaps,
|
|
681
|
-
roughnessMaps: freshRoughnessMaps,
|
|
682
|
-
emissiveMaps: freshEmissiveMaps,
|
|
683
|
-
displacementMaps: freshDisplacementMaps,
|
|
674
|
+
srgbBuckets: freshSrgbBuckets,
|
|
675
|
+
linearBuckets: freshLinearBuckets,
|
|
684
676
|
};
|
|
685
677
|
|
|
686
678
|
// Material-coherence sort gate (experiment): only worthwhile above a few materials.
|
|
@@ -761,13 +753,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
761
753
|
hitBufferRO: pb.hitBuffer.ro,
|
|
762
754
|
counters,
|
|
763
755
|
activeIndicesRO: this._sortMaterials ? qm.getSortedRO() : qm.getActiveReadRO(),
|
|
764
|
-
albedoMaps: freshAlbedoMaps,
|
|
765
|
-
normalMaps: freshNormalMaps,
|
|
766
|
-
bumpMaps: freshBumpMaps,
|
|
767
|
-
metalnessMaps: freshMetalnessMaps,
|
|
768
|
-
roughnessMaps: freshRoughnessMaps,
|
|
769
|
-
emissiveMaps: freshEmissiveMaps,
|
|
770
|
-
displacementMaps: freshDisplacementMaps,
|
|
771
756
|
envTexture: freshEnvTex,
|
|
772
757
|
environmentIntensity: this.environmentIntensity,
|
|
773
758
|
envMatrix: this.environmentMatrix,
|
|
@@ -926,12 +911,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
926
911
|
enableEnvironmentLight: this.enableEnvironment,
|
|
927
912
|
visMode: this.visMode,
|
|
928
913
|
debugVisScale: this.debugVisScale,
|
|
929
|
-
albedoMaps: freshAlbedoMaps,
|
|
930
|
-
normalMaps: freshNormalMaps,
|
|
931
|
-
bumpMaps: freshBumpMaps,
|
|
932
|
-
metalnessMaps: freshMetalnessMaps,
|
|
933
|
-
roughnessMaps: freshRoughnessMaps,
|
|
934
|
-
emissiveMaps: freshEmissiveMaps,
|
|
935
914
|
frame: this.frame,
|
|
936
915
|
} );
|
|
937
916
|
this._kernelManager.register( 'debug',
|