rayzee 8.5.0 → 8.6.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.
Files changed (64) hide show
  1. package/README.md +60 -56
  2. package/dist/{DLSSNeural-BNSJ8V3z.js → NeuralRetouch-CYOJ21Wn.js} +33 -33
  3. package/dist/{DLSSSuperRes-BoEW89Cl.js → NeuralSuperRes-Dc6jRYke.js} +25 -25
  4. package/dist/ToneMapGPU-BVE852iN.js +533 -0
  5. package/dist/rayzee.es.js +27235 -9074
  6. package/dist/rayzee.umd.js +255 -187
  7. package/package.json +1 -1
  8. package/src/AssetConfig.js +21 -7
  9. package/src/Color/BakedViews.js +134 -0
  10. package/src/Color/BuiltinViews.js +261 -0
  11. package/src/Color/ColorManagement.js +1130 -0
  12. package/src/Color/ColorSpaces.js +432 -0
  13. package/src/Color/Displays.js +19 -0
  14. package/src/Color/InputColorSpaces.js +243 -0
  15. package/src/Color/LutBake.js +371 -0
  16. package/src/Color/OcioRuntime.js +338 -0
  17. package/src/Color/OcioViews.js +419 -0
  18. package/src/Color/ViewTransforms.js +323 -0
  19. package/src/Color/WorkingMatrix.js +86 -0
  20. package/src/EngineDefaults.js +55 -3
  21. package/src/EngineIssues.js +3 -0
  22. package/src/Passes/AIUpscaler.js +16 -7
  23. package/src/Passes/OIDNDenoiser.js +13 -5
  24. package/src/PathTracerApp.js +227 -7
  25. package/src/Processor/AssetLoader.js +1 -2
  26. package/src/Processor/EmissiveTriangleBuilder.js +34 -2
  27. package/src/Processor/GeometryExtractor.js +151 -138
  28. package/src/Processor/LightSerializer.js +8 -3
  29. package/src/Processor/MaterialPacking.js +168 -0
  30. package/src/Processor/PackedRayBuffer.js +6 -2
  31. package/src/Processor/ProceduralSky.js +4 -0
  32. package/src/Processor/SceneProcessor.js +25 -1
  33. package/src/Processor/SimpleSky.js +8 -0
  34. package/src/Processor/TextureCreator.js +137 -91
  35. package/src/Processor/TextureReadback.js +92 -0
  36. package/src/Processor/ToneMapCPU.js +80 -165
  37. package/src/Processor/ToneMapGPU.js +122 -151
  38. package/src/RenderSettings.js +1 -0
  39. package/src/Stages/Compositor.js +11 -2
  40. package/src/Stages/PathTracer.js +2 -0
  41. package/src/Stages/PathTracerStage.js +1 -1
  42. package/src/TSL/Common.js +15 -0
  43. package/src/TSL/Debugger.js +2 -3
  44. package/src/TSL/EmissiveSampling.js +6 -8
  45. package/src/TSL/ExtendKernel.js +9 -0
  46. package/src/TSL/Fresnel.js +23 -1
  47. package/src/TSL/HitFacet.js +102 -0
  48. package/src/TSL/LightsDirect.js +12 -33
  49. package/src/TSL/LightsSampling.js +15 -7
  50. package/src/TSL/MaterialEvaluation.js +26 -19
  51. package/src/TSL/MaterialProperties.js +74 -37
  52. package/src/TSL/MaterialTransmission.js +2 -3
  53. package/src/TSL/ShadeKernel.js +34 -21
  54. package/src/TSL/ShadowTerminator.js +125 -0
  55. package/src/TSL/Struct.js +9 -0
  56. package/src/TSL/Subsurface.js +2 -3
  57. package/src/index.js +32 -4
  58. package/src/managers/DenoisingManager.js +60 -44
  59. package/src/managers/EnvironmentManager.js +20 -0
  60. package/src/managers/MaterialDataManager.js +118 -208
  61. package/src/managers/UniformManager.js +1 -0
  62. package/src/{dlss/DLSSNeural.js → neural/NeuralRetouch.js} +42 -41
  63. package/src/{dlss/DLSSSuperRes.js → neural/NeuralSuperRes.js} +27 -26
  64. package/dist/ToneMapGPU-DzqSyzTx.js +0 -341
@@ -1,6 +1,6 @@
1
1
  import { BufferAttribute, Vector3, Vector2, Color, Matrix3, Matrix4, FrontSide, BackSide, DoubleSide, RGBAFormat } from "three";
2
2
  import {
3
- TEXTURE_CONSTANTS, TRIANGLE_DATA_LAYOUT, packNormalOct, packTriangleFlags
3
+ TEXTURE_CONSTANTS, TRIANGLE_DATA_LAYOUT, MATERIAL_DEFAULTS, packNormalOct, packTriangleFlags
4
4
  } from '../EngineDefaults.js';
5
5
  import { ISSUE_CODES } from '../EngineIssues.js';
6
6
  import { ChunkedRecords, SHARED_MEMORY_AVAILABLE } from './ChunkedRecords.js';
@@ -10,6 +10,20 @@ const log = createLogger( 'geometry' );
10
10
 
11
11
  const MAX_TEXTURES_LIMIT = TEXTURE_CONSTANTS.MAX_TEXTURES_LIMIT;
12
12
 
13
+ const COLOR_DEFAULTS = new Set( [ 'color', 'emissive', 'attenuationColor', 'sheenColor', 'specularColor', 'subsurfaceColor' ] );
14
+
15
+ /**
16
+ * Where a packed material value came from: the three.js material carried it, a legacy-type
17
+ * conversion (Basic/Lambert/Phong/Toon) produced it, the engine filled it from
18
+ * MATERIAL_DEFAULTS, or a host set it at runtime through setMaterialProperty.
19
+ */
20
+ export const MATERIAL_VALUE_SOURCE = Object.freeze( {
21
+ MATERIAL: 'material',
22
+ MAPPED: 'mapped',
23
+ DEFAULT: 'default',
24
+ HOST: 'host',
25
+ } );
26
+
13
27
  const IDENTITY_ELEMENTS = [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ];
14
28
 
15
29
  function isIdentityElements( e ) {
@@ -19,6 +33,36 @@ function isIdentityElements( e ) {
19
33
 
20
34
  }
21
35
 
36
+ /**
37
+ * The texture that feeds each GPU map slot. Re-packing texture indices without this loses the
38
+ * unlit routing (a MeshBasicMaterial's map is also its emission map).
39
+ * @param {import('three').Material} material
40
+ * @returns {Object<string, import('three').Texture|undefined>}
41
+ */
42
+ export function resolveMaterialTextures( material ) {
43
+
44
+ return {
45
+ map: material.map,
46
+ normalMap: material.normalMap,
47
+ bumpMap: material.bumpMap,
48
+ roughnessMap: material.roughnessMap,
49
+ metalnessMap: material.metalnessMap,
50
+ emissiveMap: material.emissiveMap ?? ( material.isMeshBasicMaterial ? material.map : undefined ),
51
+ displacementMap: material.displacementMap,
52
+ anisotropyMap: material.anisotropyMap,
53
+ clearcoatMap: material.clearcoatMap,
54
+ clearcoatRoughnessMap: material.clearcoatRoughnessMap,
55
+ transmissionMap: material.transmissionMap,
56
+ sheenColorMap: material.sheenColorMap,
57
+ sheenRoughnessMap: material.sheenRoughnessMap,
58
+ specularIntensityMap: material.specularIntensityMap,
59
+ specularColorMap: material.specularColorMap,
60
+ iridescenceMap: material.iridescenceMap,
61
+ iridescenceThicknessMap: material.iridescenceThicknessMap,
62
+ };
63
+
64
+ }
65
+
22
66
  /**
23
67
  * glTF 2.0 alphaMode for a three.js material: 0 OPAQUE, 1 MASK, 2 BLEND.
24
68
  *
@@ -734,43 +778,15 @@ export class GeometryExtractor {
734
778
 
735
779
  getPhysicalDefaults() {
736
780
 
737
- // Defaults optimized for physically-based path tracing
738
- return {
739
- emissive: new Color( 0, 0, 0 ),
740
- emissiveIntensity: 1.0,
741
- roughness: 1.0,
742
- metalness: 0.0,
743
- ior: 1.5, // Common dielectric IOR (glass, plastic)
744
- opacity: 1.0,
745
- transmission: 0.0,
746
- thickness: 0.1,
747
- attenuationColor: new Color( 0xffffff ),
748
- attenuationDistance: Infinity, // No attenuation by default
749
- dispersion: 0.0,
750
- sheen: 0.0,
751
- sheenRoughness: 1.0,
752
- sheenColor: new Color( 0x000000 ),
753
- specularIntensity: 1.0,
754
- specularColor: new Color( 0xffffff ),
755
- clearcoat: 0.0,
756
- clearcoatRoughness: 0.0,
757
- iridescence: 0.0,
758
- iridescenceIOR: 1.3,
759
- iridescenceThicknessRange: [ 100, 400 ],
760
- normalScale: { x: 1, y: 1 },
761
- bumpScale: 1.0,
762
- displacementScale: 1.0,
763
- alphaTest: 0.0,
764
- // Subsurface scattering (no native MeshPhysicalMaterial equivalent)
765
- subsurface: 0.0,
766
- subsurfaceColor: new Color( 0xffffff ),
767
- subsurfaceRadius: [ 1.0, 0.2, 0.1 ], // skin-like: red travels furthest
768
- subsurfaceRadiusScale: 1.0,
769
- subsurfaceAnisotropy: 0.0,
770
- // Surface specular anisotropy (native MeshPhysicalMaterial / KHR_materials_anisotropy)
771
- anisotropy: 0.0,
772
- anisotropyRotation: 0.0
773
- };
781
+ const defaults = {};
782
+ for ( const [ key, value ] of Object.entries( MATERIAL_DEFAULTS ) ) {
783
+
784
+ defaults[ key ] = COLOR_DEFAULTS.has( key ) ? new Color().fromArray( value ) : Array.isArray( value ) ? [ ...value ] : value;
785
+
786
+ }
787
+
788
+ defaults.normalScale = { x: MATERIAL_DEFAULTS.normalScale[ 0 ], y: MATERIAL_DEFAULTS.normalScale[ 1 ] };
789
+ return defaults;
774
790
 
775
791
  }
776
792
 
@@ -782,12 +798,13 @@ export class GeometryExtractor {
782
798
  switch ( materialType ) {
783
799
 
784
800
  case 'basic':
785
- // MeshBasicMaterial -> Unlit/Emissive material
801
+ // Unlit (GLTFLoader's KHR_materials_unlit): colour and texture are emission, nothing reflects.
786
802
  mapped.emissive = material.color.clone();
787
803
  mapped.emissiveIntensity = 1.0;
788
- mapped.color = new Color( 0x000000 ); // No diffuse reflection
804
+ mapped.color = new Color( 0x000000 );
789
805
  mapped.roughness = 1.0;
790
806
  mapped.metalness = 0.0;
807
+ mapped.specularIntensity = 0.0;
791
808
  break;
792
809
 
793
810
  case 'lambert':
@@ -807,14 +824,14 @@ export class GeometryExtractor {
807
824
 
808
825
  }
809
826
 
810
- // Convert specular color to specular intensity
827
+ // Strength in specularIntensity, hue alone in specularColor; carrying it in both squared it.
811
828
  if ( material.specular ) {
812
829
 
813
- const specularLuminance = material.specular.r * 0.299 +
814
- material.specular.g * 0.587 +
815
- material.specular.b * 0.114;
830
+ const s = material.specular;
831
+ const specularLuminance = s.r * 0.299 + s.g * 0.587 + s.b * 0.114;
832
+ const peak = Math.max( s.r, s.g, s.b );
816
833
  mapped.specularIntensity = Math.min( specularLuminance * 2.0, 1.0 );
817
- mapped.specularColor = material.specular.clone();
834
+ mapped.specularColor = peak > 0 ? s.clone().multiplyScalar( 1 / peak ) : new Color( 1, 1, 1 );
818
835
 
819
836
  }
820
837
 
@@ -842,121 +859,117 @@ export class GeometryExtractor {
842
859
  const defaults = this.getPhysicalDefaults();
843
860
  const materialType = this.getMaterialType( material );
844
861
  const legacyMapping = this.mapLegacyMaterialToPhysical( material, materialType );
862
+ const textures = resolveMaterialTextures( material );
863
+ const sources = {};
845
864
 
846
- // A transmissive surface is a dielectric interface whose IOR drives refraction, so it wins
847
- // over the metal Fresnel hack: glTF defaults metallicFactor to 1, which otherwise stamps
848
- // 2.5 onto water/glass authored as MeshStandardMaterial.
849
- const isTransmissive = ( material.transmission ?? 0.0 ) > 0.0;
850
- const isMetallic = ( material.metalness ?? legacyMapping.metalness ?? 0.0 ) > 0.1;
851
- const defaultIOR = ( isMetallic && ! isTransmissive ) ? 2.5 : defaults.ior;
865
+ const pick = ( key ) => {
852
866
 
853
- // Handle color conversion for different material types
854
- let baseColor = material.color || new Color( 0xffffff );
855
- if ( materialType === 'basic' && ! material.map ) {
867
+ if ( legacyMapping[ key ] !== undefined ) {
856
868
 
857
- // For basic materials without textures, treat color as emissive
858
- baseColor = new Color( 0x000000 );
869
+ sources[ key ] = MATERIAL_VALUE_SOURCE.MAPPED;
870
+ return legacyMapping[ key ];
859
871
 
860
- }
872
+ }
873
+
874
+ if ( material[ key ] !== undefined && material[ key ] !== null ) {
875
+
876
+ sources[ key ] = MATERIAL_VALUE_SOURCE.MATERIAL;
877
+ return material[ key ];
878
+
879
+ }
880
+
881
+ sources[ key ] = MATERIAL_VALUE_SOURCE.DEFAULT;
882
+ return defaults[ key ];
883
+
884
+ };
885
+
886
+ for ( const key of [ 'transparent', 'alphaMode', 'side', 'depthWrite' ] ) sources[ key ] = MATERIAL_VALUE_SOURCE.MATERIAL;
861
887
 
862
888
  return {
863
889
  uuid: material.uuid,
864
890
 
865
- // Base material properties
866
- color: baseColor,
867
- emissive: legacyMapping.emissive ?? material.emissive ?? defaults.emissive,
868
- emissiveIntensity: legacyMapping.emissiveIntensity ?? material.emissiveIntensity ?? defaults.emissiveIntensity,
891
+ color: pick( 'color' ),
892
+ emissive: pick( 'emissive' ),
893
+ emissiveIntensity: pick( 'emissiveIntensity' ),
869
894
 
870
- // Surface properties
871
895
  // Floor at 0.02 (the sampler's own VNDF-PDF clamp, MaterialProperties.js) rather than
872
896
  // 0.05, so near-mirror metals stay sharp without entering a new firefly regime.
873
- roughness: Math.max( 0.02, legacyMapping.roughness ?? material.roughness ?? defaults.roughness ),
874
- metalness: legacyMapping.metalness ?? material.metalness ?? defaults.metalness,
875
-
876
- // Optical properties
877
- ior: material.ior ?? defaultIOR,
878
- opacity: material.opacity ?? defaults.opacity,
879
-
880
- // Transmission properties (MeshPhysicalMaterial only)
881
- transmission: material.transmission ?? defaults.transmission,
882
- thickness: material.thickness ?? defaults.thickness,
883
- attenuationColor: material.attenuationColor ?? defaults.attenuationColor,
884
- attenuationDistance: material.attenuationDistance ?? defaults.attenuationDistance,
885
-
886
- // Advanced properties (MeshPhysicalMaterial only)
887
- dispersion: material.dispersion ?? defaults.dispersion,
888
- sheen: material.sheen ?? defaults.sheen,
889
- sheenRoughness: material.sheenRoughness ?? defaults.sheenRoughness,
890
- sheenColor: material.sheenColor ?? defaults.sheenColor,
891
- clearcoat: material.clearcoat ?? defaults.clearcoat,
892
- clearcoatRoughness: material.clearcoatRoughness ?? defaults.clearcoatRoughness,
893
- iridescence: material.iridescence ?? defaults.iridescence,
894
- iridescenceIOR: material.iridescenceIOR ?? defaults.iridescenceIOR,
895
- iridescenceThicknessRange: material.iridescenceThicknessRange ?? defaults.iridescenceThicknessRange,
896
-
897
- // Subsurface scattering (custom props; MeshPhysicalMaterial has none)
898
- subsurface: material.subsurface ?? defaults.subsurface,
899
- subsurfaceColor: material.subsurfaceColor ?? defaults.subsurfaceColor,
900
- subsurfaceRadius: material.subsurfaceRadius ?? defaults.subsurfaceRadius,
901
- subsurfaceRadiusScale: material.subsurfaceRadiusScale ?? defaults.subsurfaceRadiusScale,
902
- subsurfaceAnisotropy: material.subsurfaceAnisotropy ?? defaults.subsurfaceAnisotropy,
903
-
904
- // Surface specular anisotropy (native MeshPhysicalMaterial / KHR_materials_anisotropy)
905
- anisotropy: material.anisotropy ?? defaults.anisotropy,
906
- anisotropyRotation: material.anisotropyRotation ?? defaults.anisotropyRotation,
907
-
908
- // Specular properties (for compatibility)
909
- specularIntensity: legacyMapping.specularIntensity ?? material.specularIntensity ?? defaults.specularIntensity,
910
- specularColor: legacyMapping.specularColor ?? material.specularColor ?? defaults.specularColor,
911
-
912
- // Surface detail properties
913
- normalScale: material.normalScale ?? defaults.normalScale,
914
- bumpScale: material.bumpScale ?? defaults.bumpScale,
915
- displacementScale: material.displacementScale ?? defaults.displacementScale,
916
-
917
- // Transparency and alpha
897
+ roughness: Math.max( 0.02, pick( 'roughness' ) ),
898
+ metalness: pick( 'metalness' ),
899
+
900
+ ior: pick( 'ior' ),
901
+ opacity: pick( 'opacity' ),
902
+
903
+ transmission: pick( 'transmission' ),
904
+ thickness: pick( 'thickness' ),
905
+ attenuationColor: pick( 'attenuationColor' ),
906
+ attenuationDistance: pick( 'attenuationDistance' ),
907
+
908
+ dispersion: pick( 'dispersion' ),
909
+ sheen: pick( 'sheen' ),
910
+ sheenRoughness: pick( 'sheenRoughness' ),
911
+ sheenColor: pick( 'sheenColor' ),
912
+ clearcoat: pick( 'clearcoat' ),
913
+ clearcoatRoughness: pick( 'clearcoatRoughness' ),
914
+ iridescence: pick( 'iridescence' ),
915
+ iridescenceIOR: pick( 'iridescenceIOR' ),
916
+ iridescenceThicknessRange: pick( 'iridescenceThicknessRange' ),
917
+
918
+ subsurface: pick( 'subsurface' ),
919
+ subsurfaceColor: pick( 'subsurfaceColor' ),
920
+ subsurfaceRadius: pick( 'subsurfaceRadius' ),
921
+ subsurfaceRadiusScale: pick( 'subsurfaceRadiusScale' ),
922
+ subsurfaceAnisotropy: pick( 'subsurfaceAnisotropy' ),
923
+
924
+ anisotropy: pick( 'anisotropy' ),
925
+ anisotropyRotation: pick( 'anisotropyRotation' ),
926
+
927
+ specularIntensity: pick( 'specularIntensity' ),
928
+ specularColor: pick( 'specularColor' ),
929
+
930
+ normalScale: pick( 'normalScale' ),
931
+ bumpScale: pick( 'bumpScale' ),
932
+ displacementScale: pick( 'displacementScale' ),
933
+
918
934
  transparent: material.transparent ? 1 : 0,
919
- alphaTest: material.alphaTest ?? defaults.alphaTest,
935
+ alphaTest: pick( 'alphaTest' ),
920
936
  alphaMode: deriveAlphaMode( material ),
921
937
 
922
- // Rendering properties
923
938
  side: this.getMaterialSide( material ),
924
939
  depthWrite: material.depthWrite ?? true ? 1 : 0,
925
940
 
926
- // Texture processing
927
- map: this.processTexture( material.map, this.maps ),
928
- normalMap: this.processTexture( material.normalMap, this.normalMaps ),
929
- bumpMap: this.processTexture( material.bumpMap, this.bumpMaps ),
930
- roughnessMap: this.processTexture( material.roughnessMap, this.roughnessMaps ),
931
- metalnessMap: this.processTexture( material.metalnessMap, this.metalnessMaps ),
932
- emissiveMap: this.processTexture( material.emissiveMap, this.emissiveMaps ),
933
- displacementMap: this.processTexture( material.displacementMap, this.displacementMaps ),
934
- anisotropyMap: this.processTexture( material.anisotropyMap, this.anisotropyMaps ),
941
+ map: this.processTexture( textures.map, this.maps ),
942
+ normalMap: this.processTexture( textures.normalMap, this.normalMaps ),
943
+ bumpMap: this.processTexture( textures.bumpMap, this.bumpMaps ),
944
+ roughnessMap: this.processTexture( textures.roughnessMap, this.roughnessMaps ),
945
+ metalnessMap: this.processTexture( textures.metalnessMap, this.metalnessMaps ),
946
+ emissiveMap: this.processTexture( textures.emissiveMap, this.emissiveMaps ),
947
+ displacementMap: this.processTexture( textures.displacementMap, this.displacementMaps ),
948
+ anisotropyMap: this.processTexture( textures.anisotropyMap, this.anisotropyMaps ),
935
949
 
936
950
  // Advanced texture maps (MeshPhysicalMaterial only). Folded into their scalar factors
937
951
  // in ShadeKernel (applyExtensionMaps). thicknessMap stays dropped — thickness has no
938
952
  // render effect yet (see gap-plan Phase 4.4).
939
- clearcoatMap: this.processTexture( material.clearcoatMap, this.clearcoatMaps ),
940
- clearcoatRoughnessMap: this.processTexture( material.clearcoatRoughnessMap, this.clearcoatRoughnessMaps ),
941
- transmissionMap: this.processTexture( material.transmissionMap, this.transmissionMaps ),
953
+ clearcoatMap: this.processTexture( textures.clearcoatMap, this.clearcoatMaps ),
954
+ clearcoatRoughnessMap: this.processTexture( textures.clearcoatRoughnessMap, this.clearcoatRoughnessMaps ),
955
+ transmissionMap: this.processTexture( textures.transmissionMap, this.transmissionMaps ),
942
956
  thicknessMap: this.processTexture( material.thicknessMap, [] ),
943
- sheenColorMap: this.processTexture( material.sheenColorMap, this.sheenColorMaps ),
944
- sheenRoughnessMap: this.processTexture( material.sheenRoughnessMap, this.sheenRoughnessMaps ),
945
- specularIntensityMap: this.processTexture( material.specularIntensityMap, this.specularIntensityMaps ),
946
- specularColorMap: this.processTexture( material.specularColorMap, this.specularColorMaps ),
947
- iridescenceMap: this.processTexture( material.iridescenceMap, this.iridescenceMaps ),
948
- iridescenceThicknessMap: this.processTexture( material.iridescenceThicknessMap, this.iridescenceThicknessMaps ),
949
-
950
- // Texture transformation matrices
951
- mapMatrix: this.getTextureMatrix( material.map ),
952
- normalMapMatrices: this.getTextureMatrix( material.normalMap ),
953
- bumpMapMatrices: this.getTextureMatrix( material.bumpMap ),
954
- roughnessMapMatrices: this.getTextureMatrix( material.roughnessMap ),
955
- metalnessMapMatrices: this.getTextureMatrix( material.metalnessMap ),
956
- emissiveMapMatrices: this.getTextureMatrix( material.emissiveMap ),
957
- displacementMapMatrices: this.getTextureMatrix( material.displacementMap ),
958
-
959
- // Material type for debugging/optimization
957
+ sheenColorMap: this.processTexture( textures.sheenColorMap, this.sheenColorMaps ),
958
+ sheenRoughnessMap: this.processTexture( textures.sheenRoughnessMap, this.sheenRoughnessMaps ),
959
+ specularIntensityMap: this.processTexture( textures.specularIntensityMap, this.specularIntensityMaps ),
960
+ specularColorMap: this.processTexture( textures.specularColorMap, this.specularColorMaps ),
961
+ iridescenceMap: this.processTexture( textures.iridescenceMap, this.iridescenceMaps ),
962
+ iridescenceThicknessMap: this.processTexture( textures.iridescenceThicknessMap, this.iridescenceThicknessMaps ),
963
+
964
+ mapMatrix: this.getTextureMatrix( textures.map ),
965
+ normalMapMatrices: this.getTextureMatrix( textures.normalMap ),
966
+ bumpMapMatrices: this.getTextureMatrix( textures.bumpMap ),
967
+ roughnessMapMatrices: this.getTextureMatrix( textures.roughnessMap ),
968
+ metalnessMapMatrices: this.getTextureMatrix( textures.metalnessMap ),
969
+ emissiveMapMatrices: this.getTextureMatrix( textures.emissiveMap ),
970
+ displacementMapMatrices: this.getTextureMatrix( textures.displacementMap ),
971
+
972
+ sources,
960
973
  originalType: materialType
961
974
  };
962
975
 
@@ -1,5 +1,6 @@
1
1
  import { Vector3, Quaternion } from 'three';
2
2
  import { blackbodyToLinearRGB } from './blackbody.js';
3
+ import { convertLinearTriple } from '../Color/WorkingMatrix.js';
3
4
 
4
5
  // Point & spot lights specify Power in Watts; the shader uses radiant intensity
5
6
  // (W/sr) as `intensity / dist²`, so convert with I = P / 4π (isotropic emitter),
@@ -15,15 +16,19 @@ function effectiveEmission( light ) {
15
16
  const exposure = Number.isFinite( ud.exposure ) ? ud.exposure : 0;
16
17
  const intensity = light.intensity * Math.pow( 2, exposure );
17
18
 
18
- let r = light.color.r, g = light.color.g, b = light.color.b;
19
+ const rgb = [ light.color.r, light.color.g, light.color.b ];
19
20
  if ( ud.useTemperature ) {
20
21
 
21
22
  const [ tr, tg, tb ] = blackbodyToLinearRGB( ud.temperature ?? 6500 );
22
- r *= tr; g *= tg; b *= tb;
23
+ rgb[ 0 ] *= tr; rgb[ 1 ] *= tg; rgb[ 2 ] *= tb;
23
24
 
24
25
  }
25
26
 
26
- return { r, g, b, intensity };
27
+ // A light colour is authored against sRGB primaries, like everything else three.js hands over.
28
+ // Rendering in a wider working space without converting it tints every surface it touches.
29
+ convertLinearTriple( rgb );
30
+
31
+ return { r: rgb[ 0 ], g: rgb[ 1 ], b: rgb[ 2 ], intensity };
27
32
 
28
33
  }
29
34
 
@@ -0,0 +1,168 @@
1
+ import { MATERIAL_DATA_LAYOUT as M, MATERIAL_DEFAULTS as D, normalizeAttenuationDistance } from '../EngineDefaults.js';
2
+
3
+ // The first 8 Matrix3 elements; the GPU rebuilds the 9th (always 1).
4
+ const IDENTITY_UV = [ 1, 0, 0, 0, 1, 0, 0, 0 ];
5
+
6
+ const TRANSFORMS = [
7
+ [ 'mapMatrix', M.ALBEDO_TRANSFORM ],
8
+ [ 'normalMapMatrices', M.NORMAL_TRANSFORM ],
9
+ [ 'roughnessMapMatrices', M.ROUGHNESS_TRANSFORM ],
10
+ [ 'metalnessMapMatrices', M.METALNESS_TRANSFORM ],
11
+ [ 'emissiveMapMatrices', M.EMISSIVE_TRANSFORM ],
12
+ [ 'bumpMapMatrices', M.BUMP_TRANSFORM ],
13
+ [ 'displacementMapMatrices', M.DISPLACEMENT_TRANSFORM ],
14
+ ];
15
+
16
+ /** Floats no field writes; the packer gives them fixed values. */
17
+ export const MATERIAL_RESERVED_FLOATS = Object.freeze( [
18
+ M.VISIBLE, M.SHEEN_COLOR + 3, M.DISPLACEMENT_MAP_INDEX + 1,
19
+ M.SPECULAR_COLOR_MAP_INDEX + 1, M.SPECULAR_COLOR_MAP_INDEX + 2, M.SPECULAR_COLOR_MAP_INDEX + 3,
20
+ ] );
21
+
22
+ /**
23
+ * Weights and roughnesses, [0, 1] in glTF and three.js. Past 1 an energy split like
24
+ * 1 − clearcoat·E goes negative: a glTF chrome with clearcoatFactor 4 added negative light.
25
+ */
26
+ export const UNIT_RANGE_PROPERTIES = Object.freeze( new Set( [
27
+ 'metalness', 'roughness', 'transmission', 'opacity', 'clearcoat', 'clearcoatRoughness',
28
+ 'sheen', 'sheenRoughness', 'specularIntensity', 'iridescence', 'subsurface', 'anisotropy',
29
+ ] ) );
30
+
31
+ export const clampUnit = ( value ) => Math.min( Math.max( value, 0 ), 1 );
32
+
33
+ function num( value, fallback ) {
34
+
35
+ const n = typeof value === 'boolean' ? + value : value;
36
+ return Number.isFinite( n ) ? n : fallback;
37
+
38
+ }
39
+
40
+ function mapIndex( value ) {
41
+
42
+ return Number.isInteger( value ) ? value : - 1;
43
+
44
+ }
45
+
46
+ function writeTriple( data, at, value, fallback ) {
47
+
48
+ const r = value?.r ?? value?.[ 0 ] ?? value?.x;
49
+ const g = value?.g ?? value?.[ 1 ] ?? value?.y;
50
+ const b = value?.b ?? value?.[ 2 ] ?? value?.z;
51
+ data[ at ] = num( r, fallback[ 0 ] );
52
+ data[ at + 1 ] = num( g, fallback[ 1 ] );
53
+ data[ at + 2 ] = num( b, fallback[ 2 ] );
54
+
55
+ }
56
+
57
+ /**
58
+ * The one writer of a material block, for the scene upload and runtime edits alike. A missing or
59
+ * non-finite field gets MATERIAL_DEFAULTS.
60
+ * @param {Float32Array} data
61
+ * @param {number} base - float offset of the material's block
62
+ * @param {Object} mat - a GeometryExtractor.createMaterialObject() result, or any subset of it
63
+ */
64
+ export function packMaterial( data, base, mat ) {
65
+
66
+ const s = ( offset, value, fallback ) => {
67
+
68
+ data[ base + offset ] = num( value, fallback );
69
+
70
+ };
71
+
72
+ const u = ( offset, value, fallback ) => {
73
+
74
+ data[ base + offset ] = clampUnit( num( value, fallback ) );
75
+
76
+ };
77
+
78
+ s( M.IOR, mat.ior, D.ior );
79
+ u( M.TRANSMISSION, mat.transmission, D.transmission );
80
+ s( M.THICKNESS, mat.thickness, D.thickness );
81
+ s( M.EMISSIVE_INTENSITY, mat.emissiveIntensity, D.emissiveIntensity );
82
+
83
+ writeTriple( data, base + M.ATTENUATION_COLOR, mat.attenuationColor, D.attenuationColor );
84
+ data[ base + M.ATTENUATION_DISTANCE ] = normalizeAttenuationDistance( mat.attenuationDistance ?? D.attenuationDistance );
85
+
86
+ u( M.OPACITY, mat.opacity, D.opacity );
87
+ s( M.SIDE, mat.side, D.side );
88
+ s( M.TRANSPARENT, mat.transparent, D.transparent );
89
+ s( M.ALPHA_TEST, mat.alphaTest, D.alphaTest );
90
+
91
+ s( M.ALPHA_MODE, mat.alphaMode, D.alphaMode );
92
+ s( M.DEPTH_WRITE, mat.depthWrite, D.depthWrite );
93
+ const normalScale = mat.normalScale;
94
+ const uniformScale = typeof normalScale === 'number' ? normalScale : undefined;
95
+ s( M.NORMAL_SCALE, uniformScale ?? normalScale?.x ?? normalScale?.[ 0 ], D.normalScale[ 0 ] );
96
+ s( M.NORMAL_SCALE + 1, uniformScale ?? normalScale?.y ?? normalScale?.[ 1 ], D.normalScale[ 1 ] );
97
+
98
+ writeTriple( data, base + M.COLOR, mat.color, D.color );
99
+ u( M.METALNESS, mat.metalness, D.metalness );
100
+
101
+ writeTriple( data, base + M.EMISSIVE, mat.emissive, D.emissive );
102
+ u( M.ROUGHNESS, mat.roughness, D.roughness );
103
+
104
+ data[ base + M.ALBEDO_MAP_INDEX ] = mapIndex( mat.map );
105
+ data[ base + M.NORMAL_MAP_INDEX ] = mapIndex( mat.normalMap );
106
+ data[ base + M.ROUGHNESS_MAP_INDEX ] = mapIndex( mat.roughnessMap );
107
+ data[ base + M.METALNESS_MAP_INDEX ] = mapIndex( mat.metalnessMap );
108
+
109
+ data[ base + M.EMISSIVE_MAP_INDEX ] = mapIndex( mat.emissiveMap );
110
+ data[ base + M.BUMP_MAP_INDEX ] = mapIndex( mat.bumpMap );
111
+ u( M.CLEARCOAT, mat.clearcoat, D.clearcoat );
112
+ u( M.CLEARCOAT_ROUGHNESS, mat.clearcoatRoughness, D.clearcoatRoughness );
113
+
114
+ s( M.DISPERSION, mat.dispersion, D.dispersion );
115
+ u( M.SHEEN, mat.sheen, D.sheen );
116
+ u( M.SHEEN_ROUGHNESS, mat.sheenRoughness, D.sheenRoughness );
117
+
118
+ writeTriple( data, base + M.SHEEN_COLOR, mat.sheenColor, D.sheenColor );
119
+
120
+ u( M.SPECULAR_INTENSITY, mat.specularIntensity, D.specularIntensity );
121
+ writeTriple( data, base + M.SPECULAR_COLOR, mat.specularColor, D.specularColor );
122
+
123
+ u( M.IRIDESCENCE, mat.iridescence, D.iridescence );
124
+ s( M.IRIDESCENCE_IOR, mat.iridescenceIOR, D.iridescenceIOR );
125
+ s( M.IRIDESCENCE_THICKNESS_RANGE, mat.iridescenceThicknessRange?.[ 0 ], D.iridescenceThicknessRange[ 0 ] );
126
+ s( M.IRIDESCENCE_THICKNESS_RANGE + 1, mat.iridescenceThicknessRange?.[ 1 ], D.iridescenceThicknessRange[ 1 ] );
127
+
128
+ s( M.BUMP_SCALE, mat.bumpScale, D.bumpScale );
129
+ s( M.DISPLACEMENT_SCALE, mat.displacementScale, D.displacementScale );
130
+ data[ base + M.DISPLACEMENT_MAP_INDEX ] = mapIndex( mat.displacementMap );
131
+
132
+ for ( const [ key, offset ] of TRANSFORMS ) {
133
+
134
+ const matrix = mat[ key ];
135
+ for ( let i = 0; i < 8; i ++ ) data[ base + offset + i ] = num( matrix?.[ i ], IDENTITY_UV[ i ] );
136
+
137
+ }
138
+
139
+ writeTriple( data, base + M.SUBSURFACE_COLOR, mat.subsurfaceColor, D.subsurfaceColor );
140
+ u( M.SUBSURFACE, mat.subsurface, D.subsurface );
141
+ writeTriple( data, base + M.SUBSURFACE_RADIUS, mat.subsurfaceRadius, D.subsurfaceRadius );
142
+ s( M.SUBSURFACE_RADIUS_SCALE, mat.subsurfaceRadiusScale, D.subsurfaceRadiusScale );
143
+
144
+ s( M.SUBSURFACE_ANISOTROPY, mat.subsurfaceAnisotropy, D.subsurfaceAnisotropy );
145
+ u( M.ANISOTROPY, mat.anisotropy, D.anisotropy );
146
+ s( M.ANISOTROPY_ROTATION, mat.anisotropyRotation, D.anisotropyRotation );
147
+ data[ base + M.ANISOTROPY_MAP_INDEX ] = mapIndex( mat.anisotropyMap );
148
+
149
+ data[ base + M.TRANSMISSION_MAP_INDEX ] = mapIndex( mat.transmissionMap );
150
+ data[ base + M.CLEARCOAT_MAP_INDEX ] = mapIndex( mat.clearcoatMap );
151
+ data[ base + M.CLEARCOAT_ROUGHNESS_MAP_INDEX ] = mapIndex( mat.clearcoatRoughnessMap );
152
+ data[ base + M.SHEEN_COLOR_MAP_INDEX ] = mapIndex( mat.sheenColorMap );
153
+
154
+ data[ base + M.SHEEN_ROUGHNESS_MAP_INDEX ] = mapIndex( mat.sheenRoughnessMap );
155
+ data[ base + M.IRIDESCENCE_MAP_INDEX ] = mapIndex( mat.iridescenceMap );
156
+ data[ base + M.IRIDESCENCE_THICKNESS_MAP_INDEX ] = mapIndex( mat.iridescenceThicknessMap );
157
+ data[ base + M.SPECULAR_INTENSITY_MAP_INDEX ] = mapIndex( mat.specularIntensityMap );
158
+
159
+ data[ base + M.SPECULAR_COLOR_MAP_INDEX ] = mapIndex( mat.specularColorMap );
160
+
161
+ data[ base + M.VISIBLE ] = 1;
162
+ data[ base + M.SHEEN_COLOR + 3 ] = 1;
163
+ data[ base + M.DISPLACEMENT_MAP_INDEX + 1 ] = 0;
164
+ data[ base + M.SPECULAR_COLOR_MAP_INDEX + 1 ] = 0;
165
+ data[ base + M.SPECULAR_COLOR_MAP_INDEX + 2 ] = 0;
166
+ data[ base + M.SPECULAR_COLOR_MAP_INDEX + 3 ] = 0;
167
+
168
+ }
@@ -281,12 +281,16 @@ export const readHitMaterialIndex = ( buf, id ) =>
281
281
  export const readHitInstanceLeaf = ( buf, id ) =>
282
282
  int( buf.element( soa( id, HIT.NORMAL_MAT ) ).z ).sub( int( 1 ) );
283
283
 
284
- export const writeHitPacked = ( buf, id, distance, triIndex, baryU, baryV, normal, matIndex, instanceLeaf ) => {
284
+ /** Facet normal and terminator lift, packed by HitFacet.packHitFacet. */
285
+ export const readHitFacet = ( buf, id ) =>
286
+ buf.element( soa( id, HIT.NORMAL_MAT ) ).w;
287
+
288
+ export const writeHitPacked = ( buf, id, distance, triIndex, baryU, baryV, normal, matIndex, instanceLeaf, facet = uint( 0 ) ) => {
285
289
 
286
290
  buf.element( soa( id, HIT.DIST_TRI_BARY ) )
287
291
  .assign( uvec4( floatBitsToUint( distance ), triIndex, floatBitsToUint( baryU ), floatBitsToUint( baryV ) ) );
288
292
  buf.element( soa( id, HIT.NORMAL_MAT ) )
289
- .assign( uvec4( packNormalOct( normal ), matIndex, instanceLeaf, uint( 0 ) ) );
293
+ .assign( uvec4( packNormalOct( normal ), matIndex, instanceLeaf, facet ) );
290
294
 
291
295
  };
292
296
 
@@ -248,6 +248,10 @@ export class ProceduralSky {
248
248
  }
249
249
 
250
250
  this._texture.needsUpdate = true;
251
+ // Fresh linear Rec.709 pixels in a reused texture. Colour management records which space a
252
+ // texture's pixels hold so it never converts them twice; left in place, that record would
253
+ // claim these were already converted and they never would be.
254
+ delete this._texture.userData?.__rayzeeColorSpace;
251
255
  this.lastRenderTime = performance.now() - startTime;
252
256
 
253
257
  return this._texture;
@@ -2163,7 +2163,7 @@ export class SceneProcessor {
2163
2163
 
2164
2164
  if ( this.materialData ) {
2165
2165
 
2166
- pathTracer.materialData.setMaterialData( this.materialData );
2166
+ pathTracer.materialData.setMaterialData( this.materialData, this.materials.map( m => m.sources ) );
2167
2167
 
2168
2168
  } else {
2169
2169
 
@@ -2240,6 +2240,30 @@ export class SceneProcessor {
2240
2240
 
2241
2241
  }
2242
2242
 
2243
+ /**
2244
+ * Re-read every emitter's colour, after the working space changed.
2245
+ *
2246
+ * `rebuildMaterials` re-packs the material buffer but leaves this list alone, and next-event
2247
+ * estimation lights the scene from this list — so without a rebuild, emitters would be seen in
2248
+ * the new space and cast light in the old one.
2249
+ *
2250
+ * @returns {object|null} GPU upload payload
2251
+ */
2252
+ rebuildEmissiveColors() {
2253
+
2254
+ if ( ! this.emissiveTriangleBuilder || ! this.triangleCount ) return null;
2255
+
2256
+ this.emissiveTriangleBuilder.extractEmissiveTriangles(
2257
+ this.triangles,
2258
+ this.materials,
2259
+ this.triangleCount,
2260
+ this.instanceTable ?? null
2261
+ );
2262
+ this.emissiveTriangleBuilder.createEmissiveRawData();
2263
+ return this._collectEmissivePayload();
2264
+
2265
+ }
2266
+
2243
2267
  /**
2244
2268
  * Re-derive the sampled emissive set from per-mesh visibility.
2245
2269
  * @param {Set<number>|Iterable<number>} hiddenMeshIndices - meshIndex values that are world-hidden
@@ -87,6 +87,10 @@ export class SimpleSky {
87
87
  }
88
88
 
89
89
  this._texture.needsUpdate = true;
90
+ // Fresh linear Rec.709 pixels in a reused texture. Colour management records which space a
91
+ // texture's pixels hold so it never converts them twice; left in place, that record would
92
+ // claim these were already converted and they never would be.
93
+ delete this._texture.userData?.__rayzeeColorSpace;
90
94
  this.lastRenderTime = performance.now() - startTime;
91
95
  return this._texture;
92
96
 
@@ -120,6 +124,10 @@ export class SimpleSky {
120
124
  }
121
125
 
122
126
  this._texture.needsUpdate = true;
127
+ // Fresh linear Rec.709 pixels in a reused texture. Colour management records which space a
128
+ // texture's pixels hold so it never converts them twice; left in place, that record would
129
+ // claim these were already converted and they never would be.
130
+ delete this._texture.userData?.__rayzeeColorSpace;
123
131
  this.lastRenderTime = performance.now() - startTime;
124
132
  return this._texture;
125
133