rayzee 9.4.1 → 9.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +226 -25
  2. package/dist/ActiveColor-YvuQq44O.js +13 -0
  3. package/dist/ArchiveImporter-xuUJiklP.js +4301 -0
  4. package/dist/AssetConfig-DSEntE65.js +30 -0
  5. package/dist/BidirectionalIntegrator-C5PKjJoT.js +934 -0
  6. package/dist/ColorManagement-CheBCPaW.js +1488 -0
  7. package/dist/EngineDefaults-DH3bI5t-.js +844 -0
  8. package/dist/EngineEvents-Ci8dVWCP.js +73 -0
  9. package/dist/EngineIssues-CIZnXHsF.js +72 -0
  10. package/dist/FileFormats-sPGJeCRc.js +102 -0
  11. package/dist/Logger-BnHghprG.js +97 -0
  12. package/dist/{NeuralRetouch-vkdXXyK2.js → NeuralRetouch-CW11yLZ_.js} +8 -6
  13. package/dist/{NeuralSuperRes-FVqRJKQV.js → NeuralSuperRes-BekaecYg.js} +55 -120
  14. package/dist/PathTracer-Ccl9KOuF.js +29729 -0
  15. package/dist/PhysicalSky-Dv2PMFGW.js +1219 -0
  16. package/dist/RayzeeRenderer-BQxnzCTD.js +3542 -0
  17. package/dist/SolarLimb-ken-JbDK.js +721 -0
  18. package/dist/ToneMapGPU-BHi5JzIq.js +177 -0
  19. package/dist/ViewTransforms-D0rTLBsX.js +255 -0
  20. package/dist/WorkingMatrix-C6y233hT.js +27 -0
  21. package/dist/ZipReader-gDSWr0I5.js +98 -0
  22. package/dist/addons/archives.es.js +3 -0
  23. package/dist/addons/bidirectional.es.js +2 -0
  24. package/dist/addons/color.es.js +3 -0
  25. package/dist/addons/formats.es.js +2 -0
  26. package/dist/addons/physical-sky.es.js +2 -0
  27. package/dist/addons/storage.es.js +3 -0
  28. package/dist/archiveFormats-BZw7TiDp.js +75 -0
  29. package/dist/identity-DrQsGT2o.js +39 -0
  30. package/dist/openStorage-DwcBRUj7.js +820 -0
  31. package/dist/rayzee-core.es.js +12 -0
  32. package/dist/rayzee.es.js +2266 -44125
  33. package/dist/rayzee.umd.js +485 -473
  34. package/dist/sceneCachePolicy-BmwYOYDu.js +183 -0
  35. package/dist/shared-BGBcSxBQ.js +17 -0
  36. package/package.json +23 -2
  37. package/src/Color/ActiveColor.js +34 -0
  38. package/src/Color/BasicColor.js +147 -0
  39. package/src/Color/ColorManagement.js +7 -31
  40. package/src/EngineDefaults.js +23 -14
  41. package/src/EngineEvents.js +2 -0
  42. package/src/EngineIssues.js +1 -0
  43. package/src/PathTracerApp.js +617 -4016
  44. package/src/Pipeline/PipelineContext.js +0 -4
  45. package/src/Platform.js +36 -0
  46. package/src/Processor/ArchiveCache.js +1 -1
  47. package/src/Processor/ArchiveImporter.js +1259 -0
  48. package/src/Processor/AssetLoader.js +322 -1746
  49. package/src/Processor/AtmosphereModel.js +1 -2
  50. package/src/Processor/BVHBuilder.js +4 -95
  51. package/src/Processor/EnvironmentExactTable.js +187 -0
  52. package/src/Processor/EquirectHDRInfo.js +30 -223
  53. package/src/Processor/FileFormats.js +116 -0
  54. package/src/Processor/GLTFDecoders.js +85 -0
  55. package/src/Processor/GLTFDiffuseTransmission.js +63 -0
  56. package/src/Processor/GeometryExtractor.js +12 -1
  57. package/src/Processor/IESParser.js +47 -35
  58. package/src/Processor/KernelManager.js +35 -2
  59. package/src/Processor/MaterialPacking.js +8 -7
  60. package/src/Processor/PBRT/PBRTMaterials.js +129 -41
  61. package/src/Processor/PBRT/PBRTParser.js +30 -17
  62. package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
  63. package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
  64. package/src/Processor/PBRT/PFM.js +59 -0
  65. package/src/Processor/PackedRayBuffer.js +116 -9
  66. package/src/Processor/ParallelBVHBuilder.js +3 -6
  67. package/src/Processor/PhysicalSky.js +5 -4
  68. package/src/Processor/PlatformImageLoader.js +50 -6
  69. package/src/Processor/QueueManager.js +12 -3
  70. package/src/Processor/ReinsertionOptimizer.js +2 -2
  71. package/src/Processor/SceneProcessor.js +15 -55
  72. package/src/Processor/ShaderBuilder.js +6 -16
  73. package/src/Processor/SimpleSky.js +1 -62
  74. package/src/Processor/SolarLimb.js +3 -0
  75. package/src/Processor/TextureCreator.js +1 -1
  76. package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
  77. package/src/Processor/Workers/BVHWorker.js +5 -18
  78. package/src/Processor/Workers/CDFWorker.js +6 -183
  79. package/src/Processor/archiveFormats.js +7 -0
  80. package/src/Processor/utils.js +2 -1
  81. package/src/RayzeeRenderer.js +3902 -0
  82. package/src/RenderSettings.js +49 -41
  83. package/src/Stages/ASVGF.js +5 -7
  84. package/src/Stages/AutoExposure.js +3 -12
  85. package/src/Stages/Compositor.js +8 -23
  86. package/src/Stages/NRD.js +9 -2
  87. package/src/Stages/NormalDepth.js +9 -6
  88. package/src/Stages/PathTracer.js +254 -38
  89. package/src/Stages/PathTracerStage.js +11 -16
  90. package/src/Storage/BLASCache.js +1 -1
  91. package/src/Storage/CDFCache.js +18 -17
  92. package/src/Storage/DownloadCache.js +1 -1
  93. package/src/Storage/SceneGraphCodec.js +46 -3
  94. package/src/Storage/SpillStore.js +1 -1
  95. package/src/Storage/StorageManager.js +2 -4
  96. package/src/Storage/areas.js +4 -0
  97. package/src/TSL/BVHTraversal.js +62 -24
  98. package/src/TSL/Bidirectional.js +204 -0
  99. package/src/TSL/BidirectionalLamps.js +132 -0
  100. package/src/TSL/Clearcoat.js +15 -6
  101. package/src/TSL/Common.js +19 -14
  102. package/src/TSL/ConnectKernel.js +147 -0
  103. package/src/TSL/Debugger.js +1 -1
  104. package/src/TSL/Displacement.js +4 -3
  105. package/src/TSL/EmissiveSampling.js +64 -17
  106. package/src/TSL/Environment.js +77 -106
  107. package/src/TSL/EnvironmentCDF.js +85 -39
  108. package/src/TSL/ExtendKernel.js +8 -3
  109. package/src/TSL/FinalWriteKernel.js +4 -4
  110. package/src/TSL/GenerateKernel.js +18 -4
  111. package/src/TSL/HitFacet.js +35 -5
  112. package/src/TSL/LightBVHSampling.js +40 -9
  113. package/src/TSL/LightGenerateKernel.js +293 -0
  114. package/src/TSL/LightGuide.js +198 -0
  115. package/src/TSL/LightSplatKernel.js +165 -0
  116. package/src/TSL/LightsCore.js +13 -38
  117. package/src/TSL/LightsDirect.js +28 -29
  118. package/src/TSL/LightsIndirect.js +3 -1
  119. package/src/TSL/LightsSampling.js +90 -38
  120. package/src/TSL/MaterialEvaluation.js +113 -31
  121. package/src/TSL/MaterialProperties.js +44 -17
  122. package/src/TSL/MaterialTransmission.js +52 -47
  123. package/src/TSL/MergeKernel.js +218 -0
  124. package/src/TSL/PathTracerCore.js +65 -29
  125. package/src/TSL/Random.js +4 -0
  126. package/src/TSL/SceneResources.js +51 -0
  127. package/src/TSL/ShadeKernel.js +881 -103
  128. package/src/TSL/Struct.js +10 -0
  129. package/src/TSL/Sun.js +1 -1
  130. package/src/TSL/TextureSampling.js +65 -60
  131. package/src/addons/archives.js +14 -0
  132. package/src/addons/bidirectional.js +13 -0
  133. package/src/addons/color.js +32 -0
  134. package/src/addons/formats.js +16 -0
  135. package/src/addons/physicalSky.js +14 -0
  136. package/src/addons/storage.js +19 -0
  137. package/src/core.js +35 -0
  138. package/src/index.js +3 -1
  139. package/src/integrators/BidirectionalIntegrator.js +832 -0
  140. package/src/managers/EnvironmentManager.js +94 -91
  141. package/src/managers/MaterialDataManager.js +42 -0
  142. package/src/managers/UniformManager.js +0 -1
  143. package/dist/ToneMapGPU-DKQM1pMd.js +0 -552
  144. package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
  145. package/src/Processor/TreeletOptimizer.js +0 -574
@@ -0,0 +1,116 @@
1
+ import { FloatType, Mesh, MeshStandardMaterial, Points, PointsMaterial } from 'three';
2
+
3
+ /**
4
+ * File formats beyond glTF and .hdr, each registered on its own (`assetLoader.registerFormat( fbxFormat )`) so a host
5
+ * ships only the readers it uses. A model format's `parse( file, { filename, cache } )` resolves to `{ model, result? }`;
6
+ * an environment format's `createLoader( manager )` to a three.js loader whose `loadAsync` resolves to a texture.
7
+ * Each three.js loader is imported the first time its format is read.
8
+ */
9
+
10
+ export const fbxFormat = {
11
+ name: 'FBX', label: 'FBX', type: 'model', extensions: [ 'fbx' ],
12
+ async parse( file, { cache } ) {
13
+
14
+ cache.fbx ??= new ( await import( 'three/addons/loaders/FBXLoader.js' ) ).FBXLoader();
15
+ return { model: cache.fbx.parse( await file.arrayBuffer() ) };
16
+
17
+ },
18
+ };
19
+
20
+ export const objFormat = {
21
+ name: 'OBJ', label: 'OBJ', type: 'model', extensions: [ 'obj' ],
22
+ async parse( file, { filename, cache } ) {
23
+
24
+ cache.obj ??= new ( await import( 'three/addons/loaders/OBJLoader.js' ) ).OBJLoader();
25
+ const model = cache.obj.parse( await file.text() );
26
+ model.name = filename;
27
+ return { model };
28
+
29
+ },
30
+ };
31
+
32
+ export const stlFormat = {
33
+ name: 'STL', label: 'STL', type: 'model', extensions: [ 'stl' ],
34
+ async parse( file, { filename, cache } ) {
35
+
36
+ cache.stl ??= new ( await import( 'three/addons/loaders/STLLoader.js' ) ).STLLoader();
37
+ const model = new Mesh( cache.stl.parse( await file.arrayBuffer() ), new MeshStandardMaterial() );
38
+ model.name = filename;
39
+ return { model };
40
+
41
+ },
42
+ };
43
+
44
+ export const plyFormat = {
45
+ name: 'PLY (Polygon File Format)', label: 'PLY', type: 'model', extensions: [ 'ply' ],
46
+ async parse( file, { filename, cache } ) {
47
+
48
+ cache.ply ??= new ( await import( 'three/addons/loaders/PLYLoader.js' ) ).PLYLoader();
49
+ const geometry = cache.ply.parse( await file.arrayBuffer() );
50
+ let model;
51
+ if ( geometry.index !== null ) {
52
+
53
+ model = new Mesh( geometry, new MeshStandardMaterial() );
54
+
55
+ } else {
56
+
57
+ const material = new PointsMaterial( { size: 0.01 } );
58
+ material.vertexColors = geometry.hasAttribute( 'color' );
59
+ model = new Points( geometry, material );
60
+
61
+ }
62
+
63
+ model.name = filename;
64
+ return { model };
65
+
66
+ },
67
+ };
68
+
69
+ export const colladaFormat = {
70
+ name: 'Collada', label: 'Collada', type: 'model', extensions: [ 'dae' ],
71
+ async parse( file, { filename, cache } ) {
72
+
73
+ cache.collada ??= new ( await import( 'three/addons/loaders/ColladaLoader.js' ) ).ColladaLoader();
74
+ const collada = cache.collada.parse( await file.text() );
75
+ collada.scene.name = filename;
76
+ return { model: collada.scene, result: collada };
77
+
78
+ },
79
+ };
80
+
81
+ export const threeMFFormat = {
82
+ name: '3D Manufacturing Format', label: '3MF', type: 'model', extensions: [ '3mf' ],
83
+ async parse( file, { cache } ) {
84
+
85
+ cache.threemf ??= new ( await import( 'three/addons/loaders/3MFLoader.js' ) ).ThreeMFLoader();
86
+ return { model: cache.threemf.parse( await file.arrayBuffer() ) };
87
+
88
+ },
89
+ };
90
+
91
+ export const usdFormat = {
92
+ name: 'USD (Universal Scene Description)', label: 'USD', type: 'model', extensions: [ 'usd', 'usda', 'usdc', 'usdz' ],
93
+ async parse( file, { filename, cache } ) {
94
+
95
+ cache.usd ??= new ( await import( 'three/addons/loaders/USDLoader.js' ) ).USDLoader();
96
+ const data = await file.arrayBuffer();
97
+ // parse() returns the group at once but resolves textures later; the materials are read after, so wait for them.
98
+ const model = await new Promise( ( resolve, reject ) => cache.usd.parse( data, '', resolve, reject ) );
99
+ model.name = filename;
100
+ return { model };
101
+
102
+ },
103
+ };
104
+
105
+ export const exrFormat = {
106
+ name: 'EXR (OpenEXR)', label: 'EXR', type: 'environment', extensions: [ 'exr' ],
107
+ async createLoader( manager ) {
108
+
109
+ const { EXRLoader } = await import( 'three/addons/loaders/EXRLoader.js' );
110
+ return new EXRLoader( manager ).setDataType( FloatType );
111
+
112
+ },
113
+ };
114
+
115
+ /** Every format above. */
116
+ export const allFormats = [ fbxFormat, objFormat, stlFormat, plyFormat, colladaFormat, threeMFFormat, usdFormat, exrFormat ];
@@ -0,0 +1,85 @@
1
+ import { getAssetConfig } from '../AssetConfig.js';
2
+
3
+ const DRACO = 'KHR_draco_mesh_compression';
4
+ const BASISU = 'KHR_texture_basisu';
5
+ const MESHOPT = [ 'EXT_meshopt_compression', 'KHR_meshopt_compression' ];
6
+
7
+ const decoder = new TextDecoder();
8
+
9
+ // The glTF's JSON as text: a .glb's first chunk, a .gltf whole. Only searched, never parsed.
10
+ function jsonText( data ) {
11
+
12
+ if ( typeof data === 'string' ) return data;
13
+ if ( ! ( data instanceof ArrayBuffer ) ) return JSON.stringify( data?.extensionsUsed ?? [] );
14
+
15
+ const view = new DataView( data );
16
+ const isBinary = data.byteLength >= 20 && view.getUint32( 0, true ) === 0x46546C67;
17
+ if ( ! isBinary ) return decoder.decode( data );
18
+ const length = Math.min( view.getUint32( 12, true ), data.byteLength - 20 );
19
+ return decoder.decode( new Uint8Array( data, 20, length ) );
20
+
21
+ }
22
+
23
+ async function attachDecoders( loader, text, renderer ) {
24
+
25
+ const { dracoDecoderPath, ktx2TranscoderPath } = getAssetConfig();
26
+ const [ draco, ktx2, meshopt ] = await Promise.all( [
27
+ text.includes( DRACO ) && ! loader.dracoLoader ? import( 'three/addons/loaders/DRACOLoader.js' ) : null,
28
+ text.includes( BASISU ) && ! loader.ktx2Loader ? import( 'three/addons/loaders/KTX2Loader.js' ) : null,
29
+ MESHOPT.some( name => text.includes( name ) ) && ! loader.meshoptDecoder ? import( 'three/addons/libs/meshopt_decoder.module.js' ) : null,
30
+ ] );
31
+
32
+ if ( draco ) {
33
+
34
+ const dracoLoader = new draco.DRACOLoader();
35
+ dracoLoader.setDecoderConfig( { type: 'js' } );
36
+ dracoLoader.setDecoderPath( dracoDecoderPath );
37
+ loader.setDRACOLoader( dracoLoader );
38
+
39
+ }
40
+
41
+ if ( ktx2 ) {
42
+
43
+ const ktx2Loader = new ktx2.KTX2Loader();
44
+ ktx2Loader.setTranscoderPath( ktx2TranscoderPath );
45
+ if ( renderer ) {
46
+
47
+ ktx2Loader.detectSupport( renderer );
48
+ // RGBA only: GPU-compressed texture arrays are blocked by a three.js TSL limitation (the node compiler's
49
+ // state survives dispose(), so swapping DataArrayTexture for CompressedArrayTexture breaks WGSL compilation).
50
+ ktx2Loader.workerConfig = {
51
+ astcSupported: false, etc1Supported: false, etc2Supported: false,
52
+ dxtSupported: false, bptcSupported: false, pvrtcSupported: false,
53
+ };
54
+
55
+ }
56
+
57
+ loader.setKTX2Loader( ktx2Loader );
58
+
59
+ }
60
+
61
+ if ( meshopt ) loader.setMeshoptDecoder( meshopt.MeshoptDecoder );
62
+
63
+ }
64
+
65
+ /**
66
+ * Gives a GLTFLoader the Draco, KTX2 and meshopt decoders only when the file it parses uses them, importing each then:
67
+ * most glTFs use none, and the three are ~35 KB compressed a host would otherwise always ship.
68
+ * @param {Object} loader - a GLTFLoader
69
+ * @param {?Object} renderer - for KTX2's format detection
70
+ */
71
+ export function decodersOnDemand( loader, renderer ) {
72
+
73
+ const parse = loader.parse.bind( loader );
74
+ loader.parse = ( data, path, onLoad, onError ) => {
75
+
76
+ attachDecoders( loader, jsonText( data ), renderer ).then(
77
+ () => parse( data, path, onLoad, onError ),
78
+ ( error ) => onError?.( error ),
79
+ );
80
+
81
+ };
82
+
83
+ return loader;
84
+
85
+ }
@@ -0,0 +1,63 @@
1
+ import { Color, LinearSRGBColorSpace, SRGBColorSpace } from 'three';
2
+
3
+ export const DIFFUSE_TRANSMISSION_EXTENSION = 'KHR_materials_diffuse_transmission';
4
+
5
+ /**
6
+ * A GLTFLoader plugin for KHR_materials_diffuse_transmission, which three.js does not read: each material's factors
7
+ * and textures become the engine's `diffuseTransmission`, `diffuseTransmissionColor`, `diffuseTransmissionMap` (its
8
+ * alpha is the factor) and `diffuseTransmissionColorMap`. The engine folds the maps in like the other extension maps,
9
+ * through the albedo map's uv transform.
10
+ */
11
+ export function diffuseTransmissionPlugin( parser ) {
12
+
13
+ // The factor texture is read for its alpha alone and may be another slot's colour texture, so its colour space is
14
+ // left as it is.
15
+ const assign = ( material, key, info, colorSpace ) => {
16
+
17
+ const params = {};
18
+ return parser.assignTexture( params, key, info, colorSpace ).then( () => {
19
+
20
+ if ( params[ key ] ) material[ key ] = params[ key ];
21
+
22
+ } );
23
+
24
+ };
25
+
26
+ return {
27
+
28
+ name: DIFFUSE_TRANSMISSION_EXTENSION,
29
+
30
+ afterRoot( result ) {
31
+
32
+ const definitions = parser.json.materials ?? [];
33
+ const done = new Set();
34
+ const pending = [];
35
+
36
+ for ( const scene of result.scenes ?? [] ) scene.traverse( ( object ) => {
37
+
38
+ const materials = Array.isArray( object.material ) ? object.material : object.material ? [ object.material ] : [];
39
+ for ( const material of materials ) {
40
+
41
+ if ( done.has( material ) ) continue;
42
+ done.add( material );
43
+ const index = parser.associations.get( material )?.materials;
44
+ const extension = index === undefined ? null : definitions[ index ]?.extensions?.[ DIFFUSE_TRANSMISSION_EXTENSION ];
45
+ if ( ! extension ) continue;
46
+
47
+ material.diffuseTransmission = extension.diffuseTransmissionFactor ?? 0;
48
+ const [ r, g, b ] = extension.diffuseTransmissionColorFactor ?? [ 1, 1, 1 ];
49
+ material.diffuseTransmissionColor = new Color().setRGB( r, g, b, LinearSRGBColorSpace );
50
+ if ( extension.diffuseTransmissionTexture ) pending.push( assign( material, 'diffuseTransmissionMap', extension.diffuseTransmissionTexture ) );
51
+ if ( extension.diffuseTransmissionColorTexture ) pending.push( assign( material, 'diffuseTransmissionColorMap', extension.diffuseTransmissionColorTexture, SRGBColorSpace ) );
52
+
53
+ }
54
+
55
+ } );
56
+
57
+ return Promise.all( pending );
58
+
59
+ },
60
+
61
+ };
62
+
63
+ }
@@ -11,7 +11,7 @@ const log = createLogger( 'geometry' );
11
11
 
12
12
  const MAX_TEXTURES_LIMIT = TEXTURE_CONSTANTS.MAX_TEXTURES_LIMIT;
13
13
 
14
- const COLOR_DEFAULTS = new Set( [ 'color', 'emissive', 'attenuationColor', 'sheenColor', 'specularColor', 'subsurfaceColor' ] );
14
+ const COLOR_DEFAULTS = new Set( [ 'color', 'emissive', 'attenuationColor', 'sheenColor', 'specularColor', 'subsurfaceColor', 'diffuseTransmissionColor' ] );
15
15
 
16
16
  /**
17
17
  * Where a packed material value came from: the three.js material carried it, a legacy-type
@@ -58,6 +58,8 @@ export function resolveMaterialTextures( material ) {
58
58
  sheenRoughnessMap: material.sheenRoughnessMap,
59
59
  specularIntensityMap: material.specularIntensityMap,
60
60
  specularColorMap: material.specularColorMap,
61
+ diffuseTransmissionMap: material.diffuseTransmissionMap,
62
+ diffuseTransmissionColorMap: material.diffuseTransmissionColorMap,
61
63
  iridescenceMap: material.iridescenceMap,
62
64
  iridescenceThicknessMap: material.iridescenceThicknessMap,
63
65
  };
@@ -1110,6 +1112,9 @@ export class GeometryExtractor {
1110
1112
  specularIntensity: pick( 'specularIntensity' ),
1111
1113
  specularColor: pick( 'specularColor' ),
1112
1114
 
1115
+ diffuseTransmission: pick( 'diffuseTransmission' ),
1116
+ diffuseTransmissionColor: pick( 'diffuseTransmissionColor' ),
1117
+
1113
1118
  normalScale: pick( 'normalScale' ),
1114
1119
  bumpScale: pick( 'bumpScale' ),
1115
1120
  displacementScale: pick( 'displacementScale' ),
@@ -1141,6 +1146,8 @@ export class GeometryExtractor {
1141
1146
  sheenRoughnessMap: this.processTexture( textures.sheenRoughnessMap, this.sheenRoughnessMaps ),
1142
1147
  specularIntensityMap: this.processTexture( textures.specularIntensityMap, this.specularIntensityMaps ),
1143
1148
  specularColorMap: this.processTexture( textures.specularColorMap, this.specularColorMaps ),
1149
+ diffuseTransmissionMap: this.processTexture( textures.diffuseTransmissionMap, this.diffuseTransmissionMaps ),
1150
+ diffuseTransmissionColorMap: this.processTexture( textures.diffuseTransmissionColorMap, this.diffuseTransmissionColorMaps ),
1144
1151
  iridescenceMap: this.processTexture( textures.iridescenceMap, this.iridescenceMaps ),
1145
1152
  iridescenceThicknessMap: this.processTexture( textures.iridescenceThicknessMap, this.iridescenceThicknessMaps ),
1146
1153
 
@@ -1537,6 +1544,8 @@ export class GeometryExtractor {
1537
1544
  this.iridescenceThicknessMaps = [];
1538
1545
  this.specularIntensityMaps = [];
1539
1546
  this.specularColorMaps = [];
1547
+ this.diffuseTransmissionMaps = [];
1548
+ this.diffuseTransmissionColorMaps = [];
1540
1549
  this.directionalLights = [];
1541
1550
  this.cameras = [];
1542
1551
 
@@ -1578,6 +1587,8 @@ export class GeometryExtractor {
1578
1587
  iridescenceThicknessMaps: this.iridescenceThicknessMaps,
1579
1588
  specularIntensityMaps: this.specularIntensityMaps,
1580
1589
  specularColorMaps: this.specularColorMaps,
1590
+ diffuseTransmissionMaps: this.diffuseTransmissionMaps,
1591
+ diffuseTransmissionColorMaps: this.diffuseTransmissionColorMaps,
1581
1592
  directionalLights: this.directionalLights,
1582
1593
  cameras: this.cameras
1583
1594
  };
@@ -125,19 +125,39 @@ export function parseIES( text, name = 'ies' ) {
125
125
 
126
126
  }
127
127
 
128
+ /**
129
+ * The horizontal angle a type C profile stores for `hDeg` in [0, 360), by the symmetry its range implies
130
+ * (LM-63): one angle — the same all round; 0–90 — mirrored about the 0–180 and 90–270 planes; 0–180 — about the
131
+ * 0–180 plane; 90–270 — about the 90–270 plane; 0–360 — none.
132
+ */
133
+ function storedHorizontalAngle( hDeg, hMin, hMax ) {
134
+
135
+ if ( hMin === 0 && hMax === 90 ) {
136
+
137
+ const half = hDeg % 180;
138
+ return half > 90 ? 180 - half : half;
139
+
140
+ }
141
+
142
+ if ( hMin === 0 && hMax === 180 ) return hDeg > 180 ? 360 - hDeg : hDeg;
143
+ if ( hMin === 90 && hMax === 270 ) return hDeg >= 90 && hDeg <= 270 ? hDeg : ( ( 540 - hDeg ) % 360 );
144
+ return hDeg;
145
+
146
+ }
147
+
128
148
  /**
129
149
  * Resample an IESProfile onto a fixed-size 2D grid suitable for a
130
150
  * DataArrayTexture layer. Values are normalised to [0,1] by `maxCandela`
131
151
  * so the shader gets a pure shape multiplier; absolute intensity scaling
132
152
  * stays in `light.intensity`.
133
153
  *
134
- * UV convention:
154
+ * UV convention (TSL/LightsCore.js sampleIESProfile reads it so):
135
155
  * U = horizontal angle / 360 (0..1)
136
156
  * V = vertical angle / 180 (0..1, 0 = bulb axis, 1 = opposite axis)
137
157
  *
138
- * Profiles that are rotationally symmetric (single horizontal sample, or all
139
- * H angles identical) replicate the row across the U axis so the shader can
140
- * sample uniformly without a symmetry flag.
158
+ * The grid always spans the whole sphere: a downlight's 0–90° fills the lower half and leaves the upper dark, and
159
+ * a horizontal range with a symmetry is mirrored round (storedHorizontalAngle). Angles outside the file's range
160
+ * emit nothing. Type A and B profiles, whose angles mean something else, are stretched over the grid as before.
141
161
  *
142
162
  * @param {IESProfile} profile
143
163
  * @param {number} width - texture width in samples (horizontal/U axis)
@@ -147,20 +167,34 @@ export function parseIES( text, name = 'ies' ) {
147
167
  export function resampleIESToGrid( profile, width, height ) {
148
168
 
149
169
  const data = new Uint8Array( width * height );
150
- const { verticalAngles: vA, horizontalAngles: hA, candela, maxCandela } = profile;
170
+ const { verticalAngles: vA, horizontalAngles: hA, candela, maxCandela, photometricType } = profile;
151
171
 
152
172
  if ( maxCandela <= 0 ) return data; // dead profile → zeros
153
173
 
154
- const vMaxDeg = vA[ vA.length - 1 ];
155
- const hMaxDeg = hA[ hA.length - 1 ];
174
+ const typeC = photometricType === undefined || photometricType === 1;
175
+ const vMin = vA[ 0 ];
176
+ const vMax = vA[ vA.length - 1 ];
156
177
  const hMin = hA[ 0 ];
157
- const rotationallySymmetric = hA.length === 1 || hMaxDeg === hMin;
178
+ const hMax = hA[ hA.length - 1 ];
179
+ const rotationallySymmetric = hA.length === 1 || hMax === hMin;
180
+
181
+ const at = ( v0, v1, vt, hDeg ) => {
182
+
183
+ if ( rotationallySymmetric ) return lerp( candela[ v0 ][ 0 ], candela[ v1 ][ 0 ], vt );
184
+ if ( hDeg < hMin || hDeg > hMax ) return 0;
185
+ const h0 = lowerBoundIdx( hA, hDeg );
186
+ const h1 = Math.min( h0 + 1, hA.length - 1 );
187
+ const hSpan = hA[ h1 ] - hA[ h0 ];
188
+ const ht = hSpan > 0 ? ( hDeg - hA[ h0 ] ) / hSpan : 0;
189
+ return lerp( lerp( candela[ v0 ][ h0 ], candela[ v0 ][ h1 ], ht ), lerp( candela[ v1 ][ h0 ], candela[ v1 ][ h1 ], ht ), vt );
190
+
191
+ };
158
192
 
159
193
  for ( let py = 0; py < height; py ++ ) {
160
194
 
161
- // V coordinate → vertical angle in degrees. Texture covers [0, vMaxDeg].
162
195
  const tV = ( py + 0.5 ) / height;
163
- const vDeg = tV * vMaxDeg;
196
+ const vDeg = typeC ? tV * 180 : vMin + tV * ( vMax - vMin );
197
+ if ( vDeg < vMin || vDeg > vMax ) continue;
164
198
  const v0 = lowerBoundIdx( vA, vDeg );
165
199
  const v1 = Math.min( v0 + 1, vA.length - 1 );
166
200
  const vSpan = vA[ v1 ] - vA[ v0 ];
@@ -168,31 +202,9 @@ export function resampleIESToGrid( profile, width, height ) {
168
202
 
169
203
  for ( let px = 0; px < width; px ++ ) {
170
204
 
171
- let cd;
172
-
173
- if ( rotationallySymmetric ) {
174
-
175
- // Single column → just bilerp on V axis.
176
- cd = lerp( candela[ v0 ][ 0 ], candela[ v1 ][ 0 ], vt );
177
-
178
- } else {
179
-
180
- const tH = ( px + 0.5 ) / width;
181
- const hDeg = hMin + tH * ( hMaxDeg - hMin );
182
- const h0 = lowerBoundIdx( hA, hDeg );
183
- const h1 = Math.min( h0 + 1, hA.length - 1 );
184
- const hSpan = hA[ h1 ] - hA[ h0 ];
185
- const ht = hSpan > 0 ? ( hDeg - hA[ h0 ] ) / hSpan : 0;
186
-
187
- const c00 = candela[ v0 ][ h0 ];
188
- const c10 = candela[ v0 ][ h1 ];
189
- const c01 = candela[ v1 ][ h0 ];
190
- const c11 = candela[ v1 ][ h1 ];
191
- cd = lerp( lerp( c00, c10, ht ), lerp( c01, c11, ht ), vt );
192
-
193
- }
194
-
195
- const norm = Math.min( 1, Math.max( 0, cd / maxCandela ) );
205
+ const tH = ( px + 0.5 ) / width;
206
+ const hDeg = typeC ? storedHorizontalAngle( tH * 360, hMin, hMax ) : hMin + tH * ( hMax - hMin );
207
+ const norm = Math.min( 1, Math.max( 0, at( v0, v1, vt, hDeg ) / maxCandela ) );
196
208
  data[ py * width + px ] = Math.round( norm * 255 );
197
209
 
198
210
  }
@@ -95,11 +95,44 @@ export class KernelManager {
95
95
  * Register a pre-built compute node.
96
96
  * @param {string} name - Kernel name (e.g. 'generate', 'extend')
97
97
  * @param {ComputeNode} computeNode - Built via `Fn().compute([dx,dy,dz], [wgx,wgy,wgz])`
98
+ * @param {{eager?: boolean}} [options] - `eager: false` leaves it out of compile(): it compiles at its first dispatch
98
99
  */
99
- register( name, computeNode ) {
100
+ register( name, computeNode, { eager = true } = {} ) {
100
101
 
101
102
  this.kernels.set( name, computeNode );
102
- this.timing.set( name, { compiledOnce: false, lastDispatchMs: 0 } );
103
+ this.timing.set( name, { compiledOnce: false, lastDispatchMs: 0, eager } );
104
+
105
+ }
106
+
107
+ /**
108
+ * Whether compile() can run here: three yields between build steps through scheduler.yield or requestAnimationFrame,
109
+ * and Node has neither (nor a page to keep responsive).
110
+ */
111
+ static get canCompileInBackground() {
112
+
113
+ return typeof globalThis.requestAnimationFrame === 'function' || typeof globalThis.scheduler?.yield === 'function';
114
+
115
+ }
116
+
117
+ /**
118
+ * Compiles the eager kernels without blocking the page: three.js generates their WGSL in steps that yield and
119
+ * creates the GPU pipelines asynchronously. A dispatch after it compiles nothing.
120
+ * @returns {Promise<void>}
121
+ */
122
+ async compile() {
123
+
124
+ const eager = [ ...this.kernels ].filter( ( [ name ] ) => this.timing.get( name )?.eager );
125
+ const t0 = performance.now();
126
+ // One call per kernel, all at once: the GPU compiles them side by side rather than each after the last.
127
+ await Promise.all( eager.map( ( [ , node ] ) => this.renderer.compileComputeAsync( node ) ) );
128
+ for ( const [ name ] of eager ) {
129
+
130
+ const entry = this.timing.get( name );
131
+ if ( entry ) entry.compiledOnce = true;
132
+
133
+ }
134
+
135
+ log.info( `${eager.length} kernels compiled in the background in ${fmt.ms( performance.now() - t0 )}` );
103
136
 
104
137
  }
105
138
 
@@ -15,8 +15,7 @@ const TRANSFORMS = [
15
15
 
16
16
  /** Floats no field writes; the packer gives them fixed values. */
17
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,
18
+ M.VISIBLE, M.DISPLACEMENT_MAP_INDEX + 1, M.DIFFUSE_TRANSMISSION_COLOR_MAP_INDEX + 1, M.DIFFUSE_TRANSMISSION_COLOR_MAP_INDEX + 2,
20
19
  ] );
21
20
 
22
21
  /**
@@ -25,7 +24,7 @@ export const MATERIAL_RESERVED_FLOATS = Object.freeze( [
25
24
  */
26
25
  export const UNIT_RANGE_PROPERTIES = Object.freeze( new Set( [
27
26
  'metalness', 'roughness', 'transmission', 'opacity', 'clearcoat', 'clearcoatRoughness',
28
- 'sheen', 'sheenRoughness', 'specularIntensity', 'iridescence', 'subsurface', 'anisotropy',
27
+ 'sheen', 'sheenRoughness', 'specularIntensity', 'iridescence', 'subsurface', 'anisotropy', 'diffuseTransmission',
29
28
  ] ) );
30
29
 
31
30
  export const clampUnit = ( value ) => Math.min( Math.max( value, 0 ), 1 );
@@ -116,6 +115,7 @@ export function packMaterial( data, base, mat ) {
116
115
  u( M.SHEEN_ROUGHNESS, mat.sheenRoughness, D.sheenRoughness );
117
116
 
118
117
  writeTriple( data, base + M.SHEEN_COLOR, mat.sheenColor, D.sheenColor );
118
+ u( M.DIFFUSE_TRANSMISSION, mat.diffuseTransmission, D.diffuseTransmission );
119
119
 
120
120
  u( M.SPECULAR_INTENSITY, mat.specularIntensity, D.specularIntensity );
121
121
  writeTriple( data, base + M.SPECULAR_COLOR, mat.specularColor, D.specularColor );
@@ -157,12 +157,13 @@ export function packMaterial( data, base, mat ) {
157
157
  data[ base + M.SPECULAR_INTENSITY_MAP_INDEX ] = mapIndex( mat.specularIntensityMap );
158
158
 
159
159
  data[ base + M.SPECULAR_COLOR_MAP_INDEX ] = mapIndex( mat.specularColorMap );
160
+ writeTriple( data, base + M.DIFFUSE_TRANSMISSION_COLOR, mat.diffuseTransmissionColor, D.diffuseTransmissionColor );
161
+ data[ base + M.DIFFUSE_TRANSMISSION_MAP_INDEX ] = mapIndex( mat.diffuseTransmissionMap );
162
+ data[ base + M.DIFFUSE_TRANSMISSION_COLOR_MAP_INDEX ] = mapIndex( mat.diffuseTransmissionColorMap );
160
163
 
161
164
  data[ base + M.VISIBLE ] = 1;
162
- data[ base + M.SHEEN_COLOR + 3 ] = 1;
163
165
  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;
166
+ data[ base + M.DIFFUSE_TRANSMISSION_COLOR_MAP_INDEX + 1 ] = 0;
167
+ data[ base + M.DIFFUSE_TRANSMISSION_COLOR_MAP_INDEX + 2 ] = 0;
167
168
 
168
169
  }