rayzee 7.10.2 → 7.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "7.10.2",
3
+ "version": "7.11.0",
4
4
  "type": "module",
5
5
  "description": "Real-time WebGPU path tracing engine built on Three.js",
6
6
  "main": "dist/rayzee.umd.js",
@@ -109,9 +109,15 @@ export const ENGINE_DEFAULTS = {
109
109
  directionalLightPosition: [ 1, 1, 1 ],
110
110
  directionalLightAngle: 0.0,
111
111
 
112
- filterStrength: 0.75,
113
- strengthDecaySpeed: 0.05,
114
- edgeThreshold: 1.0,
112
+ // EdgeAware denoiser (spatial-only SVGF à-trous). filterStrength: final blend
113
+ // (0 = raw, 1 = filtered). edgeAtrousIterations: à-trous passes (step 1,2,4,8,16).
114
+ // edgePhiLuminance: variance-scaled luminance edge-stop. edgePhiNormal: normal cone
115
+ // exponent. edgePhiDepth: RELATIVE depth tolerance (fraction of ray distance).
116
+ filterStrength: 1.0,
117
+ edgeAtrousIterations: 5,
118
+ edgePhiLuminance: 4.0,
119
+ edgePhiNormal: 64.0,
120
+ edgePhiDepth: 0.1,
115
121
 
116
122
  enableOIDN: false,
117
123
  oidnQuality: 'fast',
@@ -384,8 +390,8 @@ export const TRIANGLE_DATA_LAYOUT = {
384
390
  // Shared between CPU writers (TextureCreator, MaterialDataManager) and GPU readers (Common.js getMaterial).
385
391
  export const MATERIAL_DATA_LAYOUT = {
386
392
 
387
- SLOTS_PER_MATERIAL: 30, // vec4 slots per material
388
- FLOATS_PER_MATERIAL: 120, // total floats per material (30 × 4)
393
+ SLOTS_PER_MATERIAL: 33, // vec4 slots per material
394
+ FLOATS_PER_MATERIAL: 132, // total floats per material (33 × 4)
389
395
 
390
396
  // ── Flat float offsets (CPU side) ────────────────────────────────
391
397
  // Used as: data[ materialIndex * FLOATS_PER_MATERIAL + offset ]
@@ -432,8 +438,14 @@ export const MATERIAL_DATA_LAYOUT = {
432
438
  SUBSURFACE_COLOR: 108, SUBSURFACE: 111,
433
439
  // Slot 28: subsurfaceRadius.rgb (mean free path) + radius scale
434
440
  SUBSURFACE_RADIUS: 112, SUBSURFACE_RADIUS_SCALE: 115,
435
- // Slot 29: anisotropy g (floats 117-119 reserved for future SSS)
436
- SUBSURFACE_ANISOTROPY: 116,
441
+ // Slot 29: subsurfaceAnisotropy g (116) + surface anisotropy (strength 117, rotation 118, map index 119)
442
+ SUBSURFACE_ANISOTROPY: 116, ANISOTROPY: 117, ANISOTROPY_ROTATION: 118, ANISOTROPY_MAP_INDEX: 119,
443
+ // Slot 30: extension-texture map indices A (transmission, clearcoat, clearcoatRoughness, sheenColor)
444
+ TRANSMISSION_MAP_INDEX: 120, CLEARCOAT_MAP_INDEX: 121, CLEARCOAT_ROUGHNESS_MAP_INDEX: 122, SHEEN_COLOR_MAP_INDEX: 123,
445
+ // Slot 31: extension-texture map indices B (sheenRoughness, iridescence, iridescenceThickness, specularIntensity)
446
+ SHEEN_ROUGHNESS_MAP_INDEX: 124, IRIDESCENCE_MAP_INDEX: 125, IRIDESCENCE_THICKNESS_MAP_INDEX: 126, SPECULAR_INTENSITY_MAP_INDEX: 127,
447
+ // Slot 32: extension-texture map indices C (specularColor + 3 reserved)
448
+ SPECULAR_COLOR_MAP_INDEX: 128,
437
449
 
438
450
  // ── Vec4 slot indices (GPU/TSL side) ─────────────────────────────
439
451
  // Used with getDatafromStorageBuffer( buf, matIdx, int(slot), int(SLOTS_PER_MATERIAL) )
@@ -460,7 +472,10 @@ export const MATERIAL_DATA_LAYOUT = {
460
472
  DISPLACEMENT_TRANSFORM_A: 25, DISPLACEMENT_TRANSFORM_B: 26,
461
473
  SUBSURFACE_A: 27, // subsurfaceColor.rgb, subsurface weight
462
474
  SUBSURFACE_B: 28, // subsurfaceRadius.rgb, subsurfaceRadiusScale
463
- SUBSURFACE_C: 29, // subsurfaceAnisotropy, reserved
475
+ SUBSURFACE_C: 29, // subsurfaceAnisotropy g, anisotropy, anisotropyRotation, anisotropyMapIndex
476
+ EXT_MAP_INDICES_A: 30, // transmission, clearcoat, clearcoatRoughness, sheenColor map indices
477
+ EXT_MAP_INDICES_B: 31, // sheenRoughness, iridescence, iridescenceThickness, specularIntensity map indices
478
+ EXT_MAP_INDICES_C: 32, // specularColor map index + 3 reserved
464
479
  },
465
480
 
466
481
  };
@@ -1590,6 +1590,17 @@ export class AssetLoader extends EventDispatcher {
1590
1590
 
1591
1591
  }
1592
1592
 
1593
+ // glTF punctual point/spot intensity is candela (W/sr); the engine's light
1594
+ // model is Blender-style Power (W) that LightSerializer divides by 4π. Convert
1595
+ // glTF candela → Power once at import (×4π) so it nets to the correct I/d².
1596
+ // Directional (lux ≈ irradiance) is used directly and needs no conversion.
1597
+ if ( ( object.isPointLight || object.isSpotLight ) && ! userData.__candelaConverted ) {
1598
+
1599
+ object.intensity *= 4 * Math.PI;
1600
+ userData.__candelaConverted = true;
1601
+
1602
+ }
1603
+
1593
1604
  // Process ceiling lights
1594
1605
  if ( object.name.startsWith( 'RectAreaLightPlaceholder' ) &&
1595
1606
  userData.name
@@ -292,7 +292,10 @@ export class GeometryExtractor {
292
292
  subsurfaceColor: new Color( 0xffffff ),
293
293
  subsurfaceRadius: [ 1.0, 0.2, 0.1 ], // skin-like: red travels furthest
294
294
  subsurfaceRadiusScale: 1.0,
295
- subsurfaceAnisotropy: 0.0
295
+ subsurfaceAnisotropy: 0.0,
296
+ // Surface specular anisotropy (native MeshPhysicalMaterial / KHR_materials_anisotropy)
297
+ anisotropy: 0.0,
298
+ anisotropyRotation: 0.0
296
299
  };
297
300
 
298
301
  }
@@ -399,7 +402,9 @@ export class GeometryExtractor {
399
402
  emissiveIntensity: legacyMapping.emissiveIntensity ?? material.emissiveIntensity ?? defaults.emissiveIntensity,
400
403
 
401
404
  // Surface properties
402
- roughness: Math.max( 0.05, legacyMapping.roughness ?? material.roughness ?? defaults.roughness ),
405
+ // Floor at 0.02 (the sampler's own VNDF-PDF clamp, MaterialProperties.js) rather than
406
+ // 0.05, so near-mirror metals stay sharp without entering a new firefly regime.
407
+ roughness: Math.max( 0.02, legacyMapping.roughness ?? material.roughness ?? defaults.roughness ),
403
408
  metalness: legacyMapping.metalness ?? material.metalness ?? defaults.metalness,
404
409
 
405
410
  // Optical properties
@@ -430,6 +435,10 @@ export class GeometryExtractor {
430
435
  subsurfaceRadiusScale: material.subsurfaceRadiusScale ?? defaults.subsurfaceRadiusScale,
431
436
  subsurfaceAnisotropy: material.subsurfaceAnisotropy ?? defaults.subsurfaceAnisotropy,
432
437
 
438
+ // Surface specular anisotropy (native MeshPhysicalMaterial / KHR_materials_anisotropy)
439
+ anisotropy: material.anisotropy ?? defaults.anisotropy,
440
+ anisotropyRotation: material.anisotropyRotation ?? defaults.anisotropyRotation,
441
+
433
442
  // Specular properties (for compatibility)
434
443
  specularIntensity: legacyMapping.specularIntensity ?? material.specularIntensity ?? defaults.specularIntensity,
435
444
  specularColor: legacyMapping.specularColor ?? material.specularColor ?? defaults.specularColor,
@@ -456,18 +465,21 @@ export class GeometryExtractor {
456
465
  metalnessMap: this.processTexture( material.metalnessMap, this.metalnessMaps ),
457
466
  emissiveMap: this.processTexture( material.emissiveMap, this.emissiveMaps ),
458
467
  displacementMap: this.processTexture( material.displacementMap, this.displacementMaps ),
459
-
460
- // Advanced texture maps (MeshPhysicalMaterial only)
461
- clearcoatMap: this.processTexture( material.clearcoatMap, [] ),
462
- clearcoatRoughnessMap: this.processTexture( material.clearcoatRoughnessMap, [] ),
463
- transmissionMap: this.processTexture( material.transmissionMap, [] ),
468
+ anisotropyMap: this.processTexture( material.anisotropyMap, this.anisotropyMaps ),
469
+
470
+ // Advanced texture maps (MeshPhysicalMaterial only). Folded into their scalar factors
471
+ // in ShadeKernel (applyExtensionMaps). thicknessMap stays dropped — thickness has no
472
+ // render effect yet (see gap-plan Phase 4.4).
473
+ clearcoatMap: this.processTexture( material.clearcoatMap, this.clearcoatMaps ),
474
+ clearcoatRoughnessMap: this.processTexture( material.clearcoatRoughnessMap, this.clearcoatRoughnessMaps ),
475
+ transmissionMap: this.processTexture( material.transmissionMap, this.transmissionMaps ),
464
476
  thicknessMap: this.processTexture( material.thicknessMap, [] ),
465
- sheenColorMap: this.processTexture( material.sheenColorMap, [] ),
466
- sheenRoughnessMap: this.processTexture( material.sheenRoughnessMap, [] ),
467
- specularIntensityMap: this.processTexture( material.specularIntensityMap, [] ),
468
- specularColorMap: this.processTexture( material.specularColorMap, [] ),
469
- iridescenceMap: this.processTexture( material.iridescenceMap, [] ),
470
- iridescenceThicknessMap: this.processTexture( material.iridescenceThicknessMap, [] ),
477
+ sheenColorMap: this.processTexture( material.sheenColorMap, this.sheenColorMaps ),
478
+ sheenRoughnessMap: this.processTexture( material.sheenRoughnessMap, this.sheenRoughnessMaps ),
479
+ specularIntensityMap: this.processTexture( material.specularIntensityMap, this.specularIntensityMaps ),
480
+ specularColorMap: this.processTexture( material.specularColorMap, this.specularColorMaps ),
481
+ iridescenceMap: this.processTexture( material.iridescenceMap, this.iridescenceMaps ),
482
+ iridescenceThicknessMap: this.processTexture( material.iridescenceThicknessMap, this.iridescenceThicknessMaps ),
471
483
 
472
484
  // Texture transformation matrices
473
485
  mapMatrix: this.getTextureMatrix( material.map ),
@@ -819,6 +831,16 @@ export class GeometryExtractor {
819
831
  this.emissiveMaps = [];
820
832
  this.roughnessMaps = [];
821
833
  this.displacementMaps = [];
834
+ this.anisotropyMaps = [];
835
+ this.transmissionMaps = [];
836
+ this.clearcoatMaps = [];
837
+ this.clearcoatRoughnessMaps = [];
838
+ this.sheenColorMaps = [];
839
+ this.sheenRoughnessMaps = [];
840
+ this.iridescenceMaps = [];
841
+ this.iridescenceThicknessMaps = [];
842
+ this.specularIntensityMaps = [];
843
+ this.specularColorMaps = [];
822
844
  this.directionalLights = [];
823
845
  this.cameras = [];
824
846
 
@@ -857,6 +879,16 @@ export class GeometryExtractor {
857
879
  emissiveMaps: this.emissiveMaps,
858
880
  roughnessMaps: this.roughnessMaps,
859
881
  displacementMaps: this.displacementMaps,
882
+ anisotropyMaps: this.anisotropyMaps,
883
+ transmissionMaps: this.transmissionMaps,
884
+ clearcoatMaps: this.clearcoatMaps,
885
+ clearcoatRoughnessMaps: this.clearcoatRoughnessMaps,
886
+ sheenColorMaps: this.sheenColorMaps,
887
+ sheenRoughnessMaps: this.sheenRoughnessMaps,
888
+ iridescenceMaps: this.iridescenceMaps,
889
+ iridescenceThicknessMaps: this.iridescenceThicknessMaps,
890
+ specularIntensityMaps: this.specularIntensityMaps,
891
+ specularColorMaps: this.specularColorMaps,
860
892
  directionalLights: this.directionalLights,
861
893
  cameras: this.cameras,
862
894
  sceneFeatures: this.sceneFeatures // Scene-wide material feature flags
@@ -63,6 +63,16 @@ export class SceneProcessor {
63
63
  this.metalnessMaps = [];
64
64
  this.emissiveMaps = [];
65
65
  this.displacementMaps = [];
66
+ this.anisotropyMaps = [];
67
+ this.transmissionMaps = [];
68
+ this.clearcoatMaps = [];
69
+ this.clearcoatRoughnessMaps = [];
70
+ this.sheenColorMaps = [];
71
+ this.sheenRoughnessMaps = [];
72
+ this.iridescenceMaps = [];
73
+ this.iridescenceThicknessMaps = [];
74
+ this.specularIntensityMaps = [];
75
+ this.specularColorMaps = [];
66
76
  this.directionalLights = [];
67
77
  this.cameras = [];
68
78
  this.spheres = [];
@@ -320,6 +330,16 @@ export class SceneProcessor {
320
330
  this.metalnessMaps = extractedData.metalnessMaps;
321
331
  this.emissiveMaps = extractedData.emissiveMaps;
322
332
  this.displacementMaps = extractedData.displacementMaps;
333
+ this.anisotropyMaps = extractedData.anisotropyMaps;
334
+ this.transmissionMaps = extractedData.transmissionMaps;
335
+ this.clearcoatMaps = extractedData.clearcoatMaps;
336
+ this.clearcoatRoughnessMaps = extractedData.clearcoatRoughnessMaps;
337
+ this.sheenColorMaps = extractedData.sheenColorMaps;
338
+ this.sheenRoughnessMaps = extractedData.sheenRoughnessMaps;
339
+ this.iridescenceMaps = extractedData.iridescenceMaps;
340
+ this.iridescenceThicknessMaps = extractedData.iridescenceThicknessMaps;
341
+ this.specularIntensityMaps = extractedData.specularIntensityMaps;
342
+ this.specularColorMaps = extractedData.specularColorMaps;
323
343
  this.directionalLights = extractedData.directionalLights;
324
344
  this.cameras = extractedData.cameras;
325
345
  this.sceneFeatures = extractedData.sceneFeatures; // Store material feature flags for shader optimization
@@ -883,6 +903,17 @@ export class SceneProcessor {
883
903
  roughness: remapType( this.roughnessMaps, linearLists, linearDedup, this._linearTexPacked ),
884
904
  metalness: remapType( this.metalnessMaps, linearLists, linearDedup, this._linearTexPacked ),
885
905
  displacement: remapType( this.displacementMaps, linearLists, linearDedup, this._linearTexPacked ),
906
+ anisotropy: remapType( this.anisotropyMaps, linearLists, linearDedup, this._linearTexPacked ),
907
+ // Extension maps — data maps → linear pool; color maps (sheenColor, specularColor) → sRGB pool.
908
+ transmission: remapType( this.transmissionMaps, linearLists, linearDedup, this._linearTexPacked ),
909
+ clearcoat: remapType( this.clearcoatMaps, linearLists, linearDedup, this._linearTexPacked ),
910
+ clearcoatRoughness: remapType( this.clearcoatRoughnessMaps, linearLists, linearDedup, this._linearTexPacked ),
911
+ sheenColor: remapType( this.sheenColorMaps, srgbLists, srgbDedup, this._srgbTexPacked ),
912
+ sheenRoughness: remapType( this.sheenRoughnessMaps, linearLists, linearDedup, this._linearTexPacked ),
913
+ iridescence: remapType( this.iridescenceMaps, linearLists, linearDedup, this._linearTexPacked ),
914
+ iridescenceThickness: remapType( this.iridescenceThicknessMaps, linearLists, linearDedup, this._linearTexPacked ),
915
+ specularIntensity: remapType( this.specularIntensityMaps, linearLists, linearDedup, this._linearTexPacked ),
916
+ specularColor: remapType( this.specularColorMaps, srgbLists, srgbDedup, this._srgbTexPacked ),
886
917
  };
887
918
 
888
919
  return { srgbLists, linearLists, remap };
@@ -907,6 +938,16 @@ export class SceneProcessor {
907
938
  mat.roughnessMap = fix( mat.roughnessMap, remap.roughness );
908
939
  mat.metalnessMap = fix( mat.metalnessMap, remap.metalness );
909
940
  mat.displacementMap = fix( mat.displacementMap, remap.displacement );
941
+ mat.anisotropyMap = fix( mat.anisotropyMap, remap.anisotropy );
942
+ mat.transmissionMap = fix( mat.transmissionMap, remap.transmission );
943
+ mat.clearcoatMap = fix( mat.clearcoatMap, remap.clearcoat );
944
+ mat.clearcoatRoughnessMap = fix( mat.clearcoatRoughnessMap, remap.clearcoatRoughness );
945
+ mat.sheenColorMap = fix( mat.sheenColorMap, remap.sheenColor );
946
+ mat.sheenRoughnessMap = fix( mat.sheenRoughnessMap, remap.sheenRoughness );
947
+ mat.iridescenceMap = fix( mat.iridescenceMap, remap.iridescence );
948
+ mat.iridescenceThicknessMap = fix( mat.iridescenceThicknessMap, remap.iridescenceThickness );
949
+ mat.specularIntensityMap = fix( mat.specularIntensityMap, remap.specularIntensity );
950
+ mat.specularColorMap = fix( mat.specularColorMap, remap.specularColor );
910
951
 
911
952
  }
912
953
 
@@ -984,6 +1025,16 @@ export class SceneProcessor {
984
1025
  this.metalnessMaps = [];
985
1026
  this.emissiveMaps = [];
986
1027
  this.displacementMaps = [];
1028
+ this.anisotropyMaps = [];
1029
+ this.transmissionMaps = [];
1030
+ this.clearcoatMaps = [];
1031
+ this.clearcoatRoughnessMaps = [];
1032
+ this.sheenColorMaps = [];
1033
+ this.sheenRoughnessMaps = [];
1034
+ this.iridescenceMaps = [];
1035
+ this.iridescenceThicknessMaps = [];
1036
+ this.specularIntensityMaps = [];
1037
+ this.specularColorMaps = [];
987
1038
  this.directionalLights = [];
988
1039
  this.cameras = [];
989
1040
  this.spheres = [];
@@ -1,4 +1,4 @@
1
- import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace } from "three";
1
+ import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace, RepeatWrapping } from "three";
2
2
  import { TEXTURE_CONSTANTS, MEMORY_CONSTANTS, DEFAULT_TEXTURE_MATRIX, MATERIAL_DATA_LAYOUT } from '../EngineDefaults.js';
3
3
  import { fetchAsWorker } from './Workers/fetchAsWorker.js';
4
4
  import TEXTURES_WORKER_URL from './Workers/TexturesWorker.js?worker&url';
@@ -983,25 +983,31 @@ export class TextureCreator {
983
983
  // Slot 12: displacement
984
984
  mat.bumpScale, mat.displacementScale, mat.displacementMap, 0,
985
985
  mapMatrix[ 0 ], mapMatrix[ 1 ], mapMatrix[ 2 ], mapMatrix[ 3 ],
986
- mapMatrix[ 4 ], mapMatrix[ 5 ], mapMatrix[ 6 ], 1,
986
+ mapMatrix[ 4 ], mapMatrix[ 5 ], mapMatrix[ 6 ], mapMatrix[ 7 ],
987
987
  normalMapMatrices[ 0 ], normalMapMatrices[ 1 ], normalMapMatrices[ 2 ], normalMapMatrices[ 3 ],
988
- normalMapMatrices[ 4 ], normalMapMatrices[ 5 ], normalMapMatrices[ 6 ], 1,
988
+ normalMapMatrices[ 4 ], normalMapMatrices[ 5 ], normalMapMatrices[ 6 ], normalMapMatrices[ 7 ],
989
989
  roughnessMapMatrices[ 0 ], roughnessMapMatrices[ 1 ], roughnessMapMatrices[ 2 ], roughnessMapMatrices[ 3 ],
990
- roughnessMapMatrices[ 4 ], roughnessMapMatrices[ 5 ], roughnessMapMatrices[ 6 ], 1,
990
+ roughnessMapMatrices[ 4 ], roughnessMapMatrices[ 5 ], roughnessMapMatrices[ 6 ], roughnessMapMatrices[ 7 ],
991
991
  metalnessMapMatrices[ 0 ], metalnessMapMatrices[ 1 ], metalnessMapMatrices[ 2 ], metalnessMapMatrices[ 3 ],
992
- metalnessMapMatrices[ 4 ], metalnessMapMatrices[ 5 ], metalnessMapMatrices[ 6 ], 1,
992
+ metalnessMapMatrices[ 4 ], metalnessMapMatrices[ 5 ], metalnessMapMatrices[ 6 ], metalnessMapMatrices[ 7 ],
993
993
  emissiveMapMatrices[ 0 ], emissiveMapMatrices[ 1 ], emissiveMapMatrices[ 2 ], emissiveMapMatrices[ 3 ],
994
- emissiveMapMatrices[ 4 ], emissiveMapMatrices[ 5 ], emissiveMapMatrices[ 6 ], 1,
994
+ emissiveMapMatrices[ 4 ], emissiveMapMatrices[ 5 ], emissiveMapMatrices[ 6 ], emissiveMapMatrices[ 7 ],
995
995
  bumpMapMatrices[ 0 ], bumpMapMatrices[ 1 ], bumpMapMatrices[ 2 ], bumpMapMatrices[ 3 ],
996
- bumpMapMatrices[ 4 ], bumpMapMatrices[ 5 ], bumpMapMatrices[ 6 ], 1,
996
+ bumpMapMatrices[ 4 ], bumpMapMatrices[ 5 ], bumpMapMatrices[ 6 ], bumpMapMatrices[ 7 ],
997
997
  displacementMapMatrices[ 0 ], displacementMapMatrices[ 1 ], displacementMapMatrices[ 2 ], displacementMapMatrices[ 3 ],
998
- displacementMapMatrices[ 4 ], displacementMapMatrices[ 5 ], displacementMapMatrices[ 6 ], 1,
998
+ displacementMapMatrices[ 4 ], displacementMapMatrices[ 5 ], displacementMapMatrices[ 6 ], displacementMapMatrices[ 7 ],
999
999
  // Slot 27: subsurface (subsurfaceColor.rgb, subsurface weight)
1000
1000
  mat.subsurfaceColor?.r ?? 1, mat.subsurfaceColor?.g ?? 1, mat.subsurfaceColor?.b ?? 1, mat.subsurface ?? 0,
1001
1001
  // Slot 28: subsurface (subsurfaceRadius.rgb, subsurfaceRadiusScale)
1002
1002
  mat.subsurfaceRadius?.[ 0 ] ?? 1, mat.subsurfaceRadius?.[ 1 ] ?? 0.2, mat.subsurfaceRadius?.[ 2 ] ?? 0.1, mat.subsurfaceRadiusScale ?? 1,
1003
- // Slot 29: subsurface (anisotropy g, reserved)
1004
- mat.subsurfaceAnisotropy ?? 0, 0, 0, 0,
1003
+ // Slot 29: subsurfaceAnisotropy g + surface anisotropy (strength, rotation, map index)
1004
+ mat.subsurfaceAnisotropy ?? 0, mat.anisotropy ?? 0, mat.anisotropyRotation ?? 0, mat.anisotropyMap ?? - 1,
1005
+ // Slot 30: extension map indices A (transmission, clearcoat, clearcoatRoughness, sheenColor)
1006
+ mat.transmissionMap ?? - 1, mat.clearcoatMap ?? - 1, mat.clearcoatRoughnessMap ?? - 1, mat.sheenColorMap ?? - 1,
1007
+ // Slot 31: extension map indices B (sheenRoughness, iridescence, iridescenceThickness, specularIntensity)
1008
+ mat.sheenRoughnessMap ?? - 1, mat.iridescenceMap ?? - 1, mat.iridescenceThicknessMap ?? - 1, mat.specularIntensityMap ?? - 1,
1009
+ // Slot 32: extension map indices C (specularColor + 3 reserved)
1010
+ mat.specularColorMap ?? - 1, 0, 0, 0,
1005
1011
  ];
1006
1012
 
1007
1013
  data.set( materialData, stride );
@@ -1314,6 +1320,11 @@ export class TextureCreator {
1314
1320
 
1315
1321
  texture.minFilter = LinearFilter;
1316
1322
  texture.magFilter = LinearFilter;
1323
+ // glTF's default sampler wrap is REPEAT; array layers are independent (not an atlas),
1324
+ // so per-layer repeat is safe and lets tiling UVs (outside [0,1]) tile instead of
1325
+ // clamping. MIRRORED_REPEAT / per-texture wrap is a follow-up (not stored per-slot yet).
1326
+ texture.wrapS = RepeatWrapping;
1327
+ texture.wrapT = RepeatWrapping;
1317
1328
  texture.format = RGBAFormat;
1318
1329
  texture.type = UnsignedByteType;
1319
1330
  texture.needsUpdate = true;