rayzee 7.10.3 → 7.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rayzee",
3
- "version": "7.10.3",
3
+ "version": "7.11.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",
@@ -390,8 +390,8 @@ export const TRIANGLE_DATA_LAYOUT = {
390
390
  // Shared between CPU writers (TextureCreator, MaterialDataManager) and GPU readers (Common.js getMaterial).
391
391
  export const MATERIAL_DATA_LAYOUT = {
392
392
 
393
- SLOTS_PER_MATERIAL: 30, // vec4 slots per material
394
- 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)
395
395
 
396
396
  // ── Flat float offsets (CPU side) ────────────────────────────────
397
397
  // Used as: data[ materialIndex * FLOATS_PER_MATERIAL + offset ]
@@ -438,8 +438,14 @@ export const MATERIAL_DATA_LAYOUT = {
438
438
  SUBSURFACE_COLOR: 108, SUBSURFACE: 111,
439
439
  // Slot 28: subsurfaceRadius.rgb (mean free path) + radius scale
440
440
  SUBSURFACE_RADIUS: 112, SUBSURFACE_RADIUS_SCALE: 115,
441
- // Slot 29: anisotropy g (floats 117-119 reserved for future SSS)
442
- 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,
443
449
 
444
450
  // ── Vec4 slot indices (GPU/TSL side) ─────────────────────────────
445
451
  // Used with getDatafromStorageBuffer( buf, matIdx, int(slot), int(SLOTS_PER_MATERIAL) )
@@ -466,7 +472,10 @@ export const MATERIAL_DATA_LAYOUT = {
466
472
  DISPLACEMENT_TRANSFORM_A: 25, DISPLACEMENT_TRANSFORM_B: 26,
467
473
  SUBSURFACE_A: 27, // subsurfaceColor.rgb, subsurface weight
468
474
  SUBSURFACE_B: 28, // subsurfaceRadius.rgb, subsurfaceRadiusScale
469
- 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
470
479
  },
471
480
 
472
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 = [];
@@ -19,6 +19,7 @@ export class ShaderBuilder {
19
19
  // Previous-frame texture nodes (sample from MRT RenderTarget)
20
20
  this.prevColorTexNode = null;
21
21
  this.prevAlbedoTexNode = null;
22
+ this.prevNormalDepthTexNode = null;
22
23
 
23
24
  // Scene texture nodes cache (for in-place updates on model change)
24
25
  this._sceneTextureNodes = null;
@@ -98,6 +99,7 @@ export class ShaderBuilder {
98
99
  const readTextures = storageTextures.getReadTextures();
99
100
  this.prevColorTexNode = texture( readTextures.color );
100
101
  this.prevAlbedoTexNode = texture( readTextures.albedo );
102
+ this.prevNormalDepthTexNode = texture( readTextures.normalDepth );
101
103
 
102
104
  const createArrayPlaceholder = () => {
103
105
 
@@ -137,6 +139,7 @@ export class ShaderBuilder {
137
139
 
138
140
  this.prevColorTexNode = null;
139
141
  this.prevAlbedoTexNode = null;
142
+ this.prevNormalDepthTexNode = null;
140
143
  this._sceneTextureNodes = null;
141
144
 
142
145
  }
@@ -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;
@@ -46,6 +46,11 @@ export class PathTracer extends PathTracerStage {
46
46
  // uniform — NOT baked — so DenoisingManager can toggle it without a (UI-freezing) kernel rebuild.
47
47
  this._auxGBufferEnabled = false;
48
48
  this._auxGBufferUniform = uniform( 0, 'uint' );
49
+ // Clean-aux mode: temporally accumulate + renormalize the aux NORMAL (like albedo) so a clean-aux
50
+ // OIDN model (calb_cnrm/high, alb_nrm/balanced) gets the prefiltered-ish normal it expects instead
51
+ // of the point-sampled per-frame value that leaks noise. Off for fast/ASVGF (they want the bump normal).
52
+ this._cleanAuxNormalEnabled = false;
53
+ this._cleanAuxNormalUniform = uniform( 0, 'uint' );
49
54
 
50
55
  // CPU sizes per-bounce kernels from last frame's survivor curve; kernels bound on ENTERING_COUNT so over-sizing is safe. (indirect dispatch not viable — three.js doesn't sync compute-written indirect buffers across submissions)
51
56
  this._useDynamicDispatch = true;
@@ -220,6 +225,7 @@ export class PathTracer extends PathTracerStage {
220
225
 
221
226
  this.shaderBuilder.prevColorTexNode.value = readTextures.color;
222
227
  this.shaderBuilder.prevAlbedoTexNode.value = readTextures.albedo;
228
+ this.shaderBuilder.prevNormalDepthTexNode.value = readTextures.normalDepth;
223
229
 
224
230
  }
225
231
 
@@ -410,6 +416,19 @@ export class PathTracer extends PathTracerStage {
410
416
 
411
417
  }
412
418
 
419
+ // Clean-aux normal: when a clean-aux OIDN model is active (calb_cnrm/high, alb_nrm/balanced), FinalWrite
420
+ // temporally accumulates + renormalizes the aux normal so the model isn't fed per-frame point-sampled
421
+ // noise. DenoisingManager calls this on OIDN enable + quality change. Live uniform → value flip + reset.
422
+ setCleanAuxNormal( enabled ) {
423
+
424
+ enabled = !! enabled;
425
+ if ( this._cleanAuxNormalEnabled === enabled ) return;
426
+ this._cleanAuxNormalEnabled = enabled;
427
+ this._cleanAuxNormalUniform.value = enabled ? 1 : 0;
428
+ this.reset();
429
+
430
+ }
431
+
413
432
  // UI-driven resize (Resolution dropdown) — parent bypasses _handleResize(), so hook here too.
414
433
  setSize( width, height ) {
415
434
 
@@ -605,6 +624,7 @@ export class PathTracer extends PathTracerStage {
605
624
 
606
625
  const prevColor = this.shaderBuilder.prevColorTexNode;
607
626
  const prevAlbedo = this.shaderBuilder.prevAlbedoTexNode;
627
+ const prevNormalDepth = this.shaderBuilder.prevNormalDepthTexNode;
608
628
  const writeTex = this.storageTextures.getWriteTextures();
609
629
 
610
630
  const counters = qm.getCounters();
@@ -907,10 +927,12 @@ export class PathTracer extends PathTracerStage {
907
927
  transparentBackground: this.transparentBackground,
908
928
  prevAccumTexture: prevColor,
909
929
  prevAlbedoTexture: prevAlbedo,
930
+ prevNormalDepthTexture: prevNormalDepth,
910
931
  renderWidth: this._wfRenderWidth,
911
932
  renderHeight: this._wfRenderHeight,
912
933
  visMode: this.visMode,
913
934
  auxGBufferEnabled: this._auxGBufferUniform,
935
+ cleanAuxNormalEnabled: this._cleanAuxNormalUniform,
914
936
  } );
915
937
  this._kernelManager.register( 'finalWrite',
916
938
  // Per-pixel (w×h) — kernel averages the S sample-slots internally.
@@ -2,6 +2,7 @@ import {
2
2
  Fn,
3
3
  vec3,
4
4
  float,
5
+ dot,
5
6
  normalize,
6
7
  reflect,
7
8
  max,
@@ -10,10 +11,10 @@ import {
10
11
 
11
12
  import { struct } from './patches.js';
12
13
 
13
- import { DotProducts } from './Struct.js';
14
- import { PI, MIN_CLEARCOAT_ROUGHNESS, computeDotProducts } from './Common.js';
15
- import { DistributionGGX } from './MaterialProperties.js';
16
- import { ImportanceSampleGGX, ImportanceSampleCosine } from './MaterialSampling.js';
14
+ import { AnisoFrame, DotProducts } from './Struct.js';
15
+ import { PI, MIN_CLEARCOAT_ROUGHNESS, computeDotProductsAniso, anisoTangentFrame } from './Common.js';
16
+ import { DistributionGGX, computeAnisoAlphas, calculateVNDFPDFAniso } from './MaterialProperties.js';
17
+ import { ImportanceSampleGGX, ImportanceSampleCosine, sampleGGXVNDFAniso } from './MaterialSampling.js';
17
18
  import { evaluateLayeredBRDF } from './MaterialEvaluation.js';
18
19
  import { RandomValue } from './Random.js';
19
20
 
@@ -62,8 +63,20 @@ export const sampleClearcoat = Fn( ( [
62
63
 
63
64
  } ).ElseIf( rand.lessThan( clearcoatWeight.add( specularWeight ) ), () => {
64
65
 
65
- // Sample base specular
66
- H.assign( ImportanceSampleGGX( { N, roughness: baseRoughness, Xi: randomSample } ) );
66
+ // Sample base specular (anisotropic VNDF when anisotropy > 0)
67
+ If( material.anisotropy.greaterThan( 0.0 ), () => {
68
+
69
+ const f = AnisoFrame.wrap( anisoTangentFrame( N, material.anisotropyRotation ) );
70
+ const localV = vec3( dot( V, f.Ta ), dot( V, f.Ba ), dot( V, N ) );
71
+ const a = computeAnisoAlphas( material.roughness, material.anisotropy );
72
+ const localH = sampleGGXVNDFAniso( { V: localV, alphaX: a.x, alphaY: a.y, Xi: randomSample } );
73
+ H.assign( f.Ta.mul( localH.x ).add( f.Ba.mul( localH.y ) ).add( N.mul( localH.z ) ) );
74
+
75
+ } ).Else( () => {
76
+
77
+ H.assign( ImportanceSampleGGX( { N, roughness: baseRoughness, Xi: randomSample } ) );
78
+
79
+ } );
67
80
  L.assign( reflect( V.negate(), H ) );
68
81
 
69
82
  } ).Else( () => {
@@ -74,12 +87,23 @@ export const sampleClearcoat = Fn( ( [
74
87
 
75
88
  } );
76
89
 
77
- // Calculate dot products
78
- const dots = DotProducts.wrap( computeDotProducts( N, V, L ) );
90
+ // Calculate dot products (aniso-aware: also projects onto the anisotropy frame)
91
+ const dots = DotProducts.wrap( computeDotProductsAniso( N, V, L, material ) );
79
92
 
80
- // Calculate individual PDFs
93
+ // Calculate individual PDFs. Clearcoat layer is always isotropic; the base specular
94
+ // matches its sampler — VNDF-aniso when anisotropy>0, else the half-vector GGX pdf.
81
95
  const clearcoatPDF = DistributionGGX( dots.NoH, clearcoatRoughness ).mul( dots.NoH ).div( float( 4.0 ).mul( dots.VoH ) ).mul( clearcoatWeight );
82
- const specularPDF = DistributionGGX( dots.NoH, baseRoughness ).mul( dots.NoH ).div( float( 4.0 ).mul( dots.VoH ) ).mul( specularWeight );
96
+ const specularPDF = float( 0.0 ).toVar();
97
+ If( material.anisotropy.greaterThan( 0.0 ), () => {
98
+
99
+ const a = computeAnisoAlphas( material.roughness, material.anisotropy );
100
+ specularPDF.assign( calculateVNDFPDFAniso( a.x, a.y, dots.NoH, dots.ToH, dots.BoH, dots.NoV, dots.ToV, dots.BoV ).mul( specularWeight ) );
101
+
102
+ } ).Else( () => {
103
+
104
+ specularPDF.assign( DistributionGGX( dots.NoH, baseRoughness ).mul( dots.NoH ).div( float( 4.0 ).mul( dots.VoH ) ).mul( specularWeight ) );
105
+
106
+ } );
83
107
  const diffusePDF = dots.NoL.div( PI ).mul( diffuseWeight );
84
108
 
85
109
  // Combined PDF using MIS
package/src/TSL/Common.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Fn, wgslFn, float, vec2, vec3, vec4, int, mat3, If, max, dot, clamp, bool as tslBool } from 'three/tsl';
2
2
 
3
3
  import {
4
+ AnisoFrame,
4
5
  DotProducts,
5
6
  MaterialClassification,
6
7
  MISStrategy,
@@ -155,6 +156,58 @@ export const computeDotProducts = Fn( ( [ N, V, L ] ) => {
155
156
  NoH: max( dot( N, H ), 0.001 ),
156
157
  VoH: max( dot( V, H ), 0.001 ),
157
158
  LoH: max( dot( L, H ), 0.001 ),
159
+ ToH: float( 0.0 ), BoH: float( 0.0 ),
160
+ ToV: float( 0.0 ), BoV: float( 0.0 ),
161
+ ToL: float( 0.0 ), BoL: float( 0.0 ),
162
+ } );
163
+
164
+ } );
165
+
166
+ // Anisotropy tangent frame: arbitrary ONB from N (matching constructTBN + normal mapping),
167
+ // rotated by anisotropyRotation. Single source of truth for sampler AND eval/PDF so their
168
+ // frames are bit-identical (required for MIS consistency).
169
+ export const anisoTangentFrame = Fn( ( [ N, rotation ] ) => {
170
+
171
+ const majorAxis = N.x.abs().lessThan( 0.999 ).select( vec3( 1.0, 0.0, 0.0 ), vec3( 0.0, 1.0, 0.0 ) );
172
+ const T0 = N.cross( majorAxis ).normalize();
173
+ const B0 = N.cross( T0 ).normalize();
174
+ const c = rotation.cos();
175
+ const s = rotation.sin();
176
+ return AnisoFrame( { Ta: T0.mul( c ).add( B0.mul( s ) ), Ba: B0.mul( c ).sub( T0.mul( s ) ) } );
177
+
178
+ } );
179
+
180
+ // Anisotropy-aware dot products: computeDotProducts + tangent-frame projections of H/V/L
181
+ // (0 for isotropic materials, so aniso branches gated on anisotropy>0 are a no-op).
182
+ export const computeDotProductsAniso = Fn( ( [ N, V, L, material ] ) => {
183
+
184
+ const H = V.add( L ).toVar();
185
+ const lenSq = dot( H, H ).toVar();
186
+ H.assign( lenSq.greaterThan( EPSILON ).select( H.div( lenSq.sqrt() ), vec3( 0.0, 0.0, 1.0 ) ) );
187
+
188
+ const ToH = float( 0.0 ).toVar();
189
+ const BoH = float( 0.0 ).toVar();
190
+ const ToV = float( 0.0 ).toVar();
191
+ const BoV = float( 0.0 ).toVar();
192
+ const ToL = float( 0.0 ).toVar();
193
+ const BoL = float( 0.0 ).toVar();
194
+
195
+ If( material.anisotropy.greaterThan( 0.0 ), () => {
196
+
197
+ const f = AnisoFrame.wrap( anisoTangentFrame( N, material.anisotropyRotation ) );
198
+ ToH.assign( dot( f.Ta, H ) ); BoH.assign( dot( f.Ba, H ) );
199
+ ToV.assign( dot( f.Ta, V ) ); BoV.assign( dot( f.Ba, V ) );
200
+ ToL.assign( dot( f.Ta, L ) ); BoL.assign( dot( f.Ba, L ) );
201
+
202
+ } );
203
+
204
+ return DotProducts( {
205
+ NoL: max( dot( N, L ), 0.001 ),
206
+ NoV: max( dot( N, V ), 0.001 ),
207
+ NoH: max( dot( N, H ), 0.001 ),
208
+ VoH: max( dot( V, H ), 0.001 ),
209
+ LoH: max( dot( L, H ), 0.001 ),
210
+ ToH, BoH, ToV, BoV, ToL, BoL,
158
211
  } );
159
212
 
160
213
  } );
@@ -345,6 +398,9 @@ export const getMaterial = Fn( ( [ materialIndex, materialBuffer ] ) => {
345
398
  const data27 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.SUBSURFACE_A ), int( MATERIAL_SLOTS ) ).toVar();
346
399
  const data28 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.SUBSURFACE_B ), int( MATERIAL_SLOTS ) ).toVar();
347
400
  const data29 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.SUBSURFACE_C ), int( MATERIAL_SLOTS ) ).toVar();
401
+ const data30 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_A ), int( MATERIAL_SLOTS ) ).toVar();
402
+ const data31 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_B ), int( MATERIAL_SLOTS ) ).toVar();
403
+ const data32 = getDatafromStorageBuffer( materialBuffer, materialIndex, int( S.EXT_MAP_INDICES_C ), int( MATERIAL_SLOTS ) ).toVar();
348
404
 
349
405
  return RayTracingMaterial( {
350
406
  color: vec4( data0.rgb, 1.0 ),
@@ -371,6 +427,18 @@ export const getMaterial = Fn( ( [ materialIndex, materialBuffer ] ) => {
371
427
  subsurfaceRadius: data28.rgb,
372
428
  subsurfaceRadiusScale: data28.a,
373
429
  subsurfaceAnisotropy: data29.r,
430
+ anisotropy: data29.g,
431
+ anisotropyRotation: data29.b,
432
+ anisotropyMapIndex: int( data29.a ),
433
+ transmissionMapIndex: int( data30.r ),
434
+ clearcoatMapIndex: int( data30.g ),
435
+ clearcoatRoughnessMapIndex: int( data30.b ),
436
+ sheenColorMapIndex: int( data30.a ),
437
+ sheenRoughnessMapIndex: int( data31.r ),
438
+ iridescenceMapIndex: int( data31.g ),
439
+ iridescenceThicknessMapIndex: int( data31.b ),
440
+ specularIntensityMapIndex: int( data31.a ),
441
+ specularColorMapIndex: int( data32.r ),
374
442
  albedoMapIndex: int( data8.r ),
375
443
  normalMapIndex: int( data8.g ),
376
444
  roughnessMapIndex: int( data8.b ),
@@ -458,6 +526,18 @@ export const diffuseGroundMaterial = Fn( () => {
458
526
  subsurfaceRadius: vec3( 1.0, 0.2, 0.1 ),
459
527
  subsurfaceRadiusScale: float( 1.0 ),
460
528
  subsurfaceAnisotropy: float( 0.0 ),
529
+ anisotropy: float( 0.0 ),
530
+ anisotropyRotation: float( 0.0 ),
531
+ anisotropyMapIndex: int( - 1 ),
532
+ transmissionMapIndex: int( - 1 ),
533
+ clearcoatMapIndex: int( - 1 ),
534
+ clearcoatRoughnessMapIndex: int( - 1 ),
535
+ sheenColorMapIndex: int( - 1 ),
536
+ sheenRoughnessMapIndex: int( - 1 ),
537
+ iridescenceMapIndex: int( - 1 ),
538
+ iridescenceThicknessMapIndex: int( - 1 ),
539
+ specularIntensityMapIndex: int( - 1 ),
540
+ specularColorMapIndex: int( - 1 ),
461
541
  } );
462
542
 
463
543
  } );
@@ -26,7 +26,7 @@ import {
26
26
  } from 'three/tsl';
27
27
 
28
28
  import { struct } from './patches.js';
29
- import { MIN_PDF, getDatafromStorageBuffer, powerHeuristic, MATERIAL_SLOTS, MATERIAL_SLOT, computeDotProducts } from './Common.js';
29
+ import { MIN_PDF, getDatafromStorageBuffer, powerHeuristic, MATERIAL_SLOTS, MATERIAL_SLOT, computeDotProductsAniso } from './Common.js';
30
30
  import { RandomValue } from './Random.js';
31
31
  import { calculateMaterialPDFFromDots } from './LightsSampling.js';
32
32
  import { evaluateMaterialResponseFromDots } from './MaterialEvaluation.js';
@@ -573,7 +573,7 @@ export const calculateEmissiveTriangleContributionDebug = Fn( ( [
573
573
 
574
574
  // Share H + dot products between BRDF eval and PDF (computeDotProducts
575
575
  // would otherwise run twice with identical inputs).
576
- const dots = DotProducts.wrap( computeDotProducts( normal, viewDir, emissiveSample.direction ) );
576
+ const dots = DotProducts.wrap( computeDotProductsAniso( normal, viewDir, emissiveSample.direction, material ) );
577
577
  const brdfValue = evaluateMaterialResponseFromDots( material, dots );
578
578
  const brdfPdf = calculateMaterialPDFFromDots( material, dots );
579
579