@onerjs/serializers 9.0.2 → 9.0.4

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 (37) hide show
  1. package/glTF/2.0/Extensions/KHR_materials_anisotropy.pure.js +12 -10
  2. package/glTF/2.0/Extensions/KHR_materials_anisotropy.pure.js.map +1 -1
  3. package/glTF/2.0/Extensions/KHR_materials_coat.pure.js +11 -9
  4. package/glTF/2.0/Extensions/KHR_materials_coat.pure.js.map +1 -1
  5. package/glTF/2.0/Extensions/KHR_materials_scatter.d.ts +5 -0
  6. package/glTF/2.0/Extensions/KHR_materials_scatter.js +8 -0
  7. package/glTF/2.0/Extensions/KHR_materials_scatter.js.map +1 -0
  8. package/glTF/2.0/Extensions/KHR_materials_scatter.pure.d.ts +120 -0
  9. package/glTF/2.0/Extensions/KHR_materials_scatter.pure.js +396 -0
  10. package/glTF/2.0/Extensions/KHR_materials_scatter.pure.js.map +1 -0
  11. package/glTF/2.0/Extensions/KHR_materials_transmission.pure.d.ts +2 -0
  12. package/glTF/2.0/Extensions/KHR_materials_transmission.pure.js +141 -39
  13. package/glTF/2.0/Extensions/KHR_materials_transmission.pure.js.map +1 -1
  14. package/glTF/2.0/Extensions/KHR_materials_volume.pure.js +2 -48
  15. package/glTF/2.0/Extensions/KHR_materials_volume.pure.js.map +1 -1
  16. package/glTF/2.0/Extensions/index.d.ts +1 -1
  17. package/glTF/2.0/Extensions/index.js +1 -1
  18. package/glTF/2.0/Extensions/index.js.map +1 -1
  19. package/glTF/2.0/Extensions/pure.d.ts +1 -1
  20. package/glTF/2.0/Extensions/pure.js +1 -1
  21. package/glTF/2.0/Extensions/pure.js.map +1 -1
  22. package/glTF/2.0/glTFExporter.d.ts +6 -0
  23. package/glTF/2.0/glTFExporter.js +9 -0
  24. package/glTF/2.0/glTFExporter.js.map +1 -1
  25. package/glTF/2.0/glTFMaterialExporter.d.ts +2 -1
  26. package/glTF/2.0/glTFMaterialExporter.js +68 -31
  27. package/glTF/2.0/glTFMaterialExporter.js.map +1 -1
  28. package/glTF/2.0/glTFUtilities.d.ts +37 -1
  29. package/glTF/2.0/glTFUtilities.js +99 -0
  30. package/glTF/2.0/glTFUtilities.js.map +1 -1
  31. package/package.json +2 -2
  32. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.d.ts +0 -5
  33. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.js +0 -8
  34. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.js.map +0 -1
  35. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.pure.d.ts +0 -45
  36. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.pure.js +0 -145
  37. package/glTF/2.0/Extensions/KHR_materials_volume_scatter.pure.js.map +0 -1
@@ -0,0 +1,396 @@
1
+ import { GLTFExporter } from "../glTFExporter.js";
2
+ import { OpenPBRMaterial } from "@onerjs/core/Materials/PBR/openpbrMaterial.pure.js";
3
+ import { Color3, Color4 } from "@onerjs/core/Maths/math.color.pure.js";
4
+ import { ChannelMask, CreateFactorOperand, CreateTextureOperand, CreateTextureWithFactorOperand, ExtractChannelAsync, InvertTextureAsync, LerpTexturesAsync, MultiplyTexturesAsync, SingleScatterToMultiScatterAlbedoAsync, TextureChannel, TextureColorSpace, TransmissionFractionAsync, TransmissionScatterToSingleScatterAlbedoAsync, } from "@onerjs/core/Materials/Textures/textureProcessor.js";
5
+ import { Constants } from "@onerjs/core/Engines/constants.js";
6
+ import { MergeTexturesAsync, CreateRGBAConfiguration, CreateTextureInput, CreateConstantInput } from "@onerjs/core/Materials/Textures/textureMerger.js";
7
+ import { Texture } from "@onerjs/core/Materials/Textures/texture.pure.js";
8
+ import { CopyTextureSampling, GetOpenPBRVolumeAttenuation, MinTransmissionColor } from "../glTFUtilities.js";
9
+ const NAME = "KHR_materials_scatter";
10
+ /**
11
+ * Render-target options for intermediate passes: floating point when the engine can render to it, so
12
+ * only the final exported textures are quantized to 8 bits.
13
+ * @param scene the scene whose engine renders the passes
14
+ * @returns output options for an intermediate texture processor pass
15
+ */
16
+ function IntermediateOutputOptions(scene) {
17
+ const caps = scene.getEngine().getCaps();
18
+ return {
19
+ textureType: caps.textureFloatRender ? Constants.TEXTURETYPE_FLOAT : caps.textureHalfFloatRender ? Constants.TEXTURETYPE_HALF_FLOAT : Constants.TEXTURETYPE_UNSIGNED_BYTE,
20
+ samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE,
21
+ };
22
+ }
23
+ /**
24
+ * Creates a texture that references an existing internal texture with the sampling parameters of another texture,
25
+ * so several glTF texture references can share one image while keeping their own UV set and transform.
26
+ * @param name the name of the new texture
27
+ * @param internalTexture the internal texture to reference
28
+ * @param samplingSource the texture to copy the UV set, wrap modes and transform from
29
+ * @returns the new texture, which must be disposed by the caller
30
+ */
31
+ function CreateTextureReference(name, internalTexture, samplingSource) {
32
+ const reference = new Texture(null, samplingSource.getScene());
33
+ reference.name = name;
34
+ reference._texture = internalTexture;
35
+ internalTexture.incrementReferences();
36
+ CopyTextureSampling(samplingSource, reference);
37
+ return reference;
38
+ }
39
+ /**
40
+ * TODO: In-progress specification
41
+ * [Specification](https://github.com/KhronosGroup/glTF/blob/7ea427ed55d44427e83c0a6d1c87068b1a4151c5/extensions/2.0/Khronos/KHR_materials_scatter/README.md)
42
+ */
43
+ // eslint-disable-next-line @typescript-eslint/naming-convention
44
+ export class KHR_materials_scatter {
45
+ constructor(exporter) {
46
+ /** Name of this extension */
47
+ this.name = NAME;
48
+ /** Defines whether this extension is enabled */
49
+ this.enabled = true;
50
+ /** Defines whether this extension is required */
51
+ this.required = false;
52
+ this._wasUsed = false;
53
+ this._scatterResults = new Map();
54
+ this._exporter = exporter;
55
+ }
56
+ dispose() {
57
+ for (const result of this._scatterResults.values()) {
58
+ for (const texture of result.ownedTextures) {
59
+ texture.dispose();
60
+ }
61
+ }
62
+ this._scatterResults.clear();
63
+ }
64
+ /** @internal */
65
+ get wasUsed() {
66
+ return this._wasUsed;
67
+ }
68
+ /**
69
+ * After exporting a material, bake the combined multi-scatter color texture (if any).
70
+ * @param context GLTF context of the material
71
+ * @param node exported GLTF node
72
+ * @param babylonMaterial corresponding babylon material
73
+ * @returns array of additional textures to export
74
+ */
75
+ async postExportMaterialAdditionalTexturesAsync(context, node, babylonMaterial) {
76
+ const additionalTextures = [];
77
+ if (babylonMaterial instanceof OpenPBRMaterial && this._isExtensionEnabled(babylonMaterial)) {
78
+ const result = await this._bakeScatterAsync(babylonMaterial);
79
+ this._scatterResults.set(babylonMaterial, result);
80
+ if (result.strengthTexture) {
81
+ additionalTextures.push(result.strengthTexture);
82
+ }
83
+ if (result.colorTexture) {
84
+ additionalTextures.push(result.colorTexture);
85
+ }
86
+ }
87
+ return additionalTextures;
88
+ }
89
+ _isExtensionEnabled(mat) {
90
+ // This extension must not be used on a material that also uses KHR_materials_unlit
91
+ if (mat.unlit) {
92
+ return false;
93
+ }
94
+ // Scattering is enabled if either subsurface or transmission scattering is enabled.
95
+ // Transmission scattering is only enabled if the material is not thin-walled, and has a non-zero transmission depth.
96
+ const transmissionVolume = mat.transmissionWeight > 0 && !mat.geometryThinWalled && mat.transmissionDepth > 0;
97
+ const transmissionScatter = transmissionVolume && !mat.transmissionScatter.equals(Color3.Black());
98
+ if (transmissionScatter || mat.subsurfaceWeight > 0) {
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+ /**
104
+ * Bake the KHR_materials_scatter data for an OpenPBR material, dispatching on thin-walled vs volumetric.
105
+ * @param mat the OpenPBR material to bake scatter data for
106
+ * @returns the scatter strength, multi-scatter color, and anisotropy (constants and/or baked textures)
107
+ */
108
+ async _bakeScatterAsync(mat) {
109
+ return mat.geometryThinWalled ? await this._bakeThinWalledScatterAsync(mat) : await this._bakeVolumetricScatterAsync(mat);
110
+ }
111
+ /**
112
+ * Per-pixel transmission fraction operand `T / (T + S(1 - T))` (= `T / (1 - (1 - T)(1 - S))`) from the
113
+ * (possibly textured) transmission weight `T` and subsurface weight `S`. Folds to a constant when
114
+ * neither weight has a texture. The subsurface fraction is `1 - transmissionFraction`. Weights are read
115
+ * from the glTF-convention channels (transmission R; subsurface A when packed in alpha, otherwise R).
116
+ * The fraction is computed in one fused pass into a floating-point target (when supported), so small
117
+ * weights don't cancel out in 8-bit intermediates; callers quantize only their final output.
118
+ * @param mat the OpenPBR material whose weights drive the fraction
119
+ * @returns an operand carrying the transmission fraction (constant or baked texture)
120
+ */
121
+ async _computeTransmissionFractionAsync(mat) {
122
+ const scene = mat.getScene();
123
+ const transmissionWeight = mat.transmissionWeight;
124
+ const subsurfaceWeight = mat.subsurfaceWeight;
125
+ const subsurfaceChannel = mat._useSubsurfaceWeightFromTextureAlpha ? TextureChannel.A : TextureChannel.R;
126
+ const transmissionWeightOp = CreateTextureWithFactorOperand(mat.transmissionWeightTexture, new Color4(transmissionWeight, transmissionWeight, transmissionWeight, 1.0), TextureChannel.R);
127
+ const subsurfaceWeightOp = CreateTextureWithFactorOperand(mat.subsurfaceWeightTexture, new Color4(subsurfaceWeight, subsurfaceWeight, subsurfaceWeight, 1.0), subsurfaceChannel);
128
+ return await TransmissionFractionAsync(`scatter transmission fraction (${mat.name})`, transmissionWeightOp, subsurfaceWeightOp, scene, undefined, undefined, IntermediateOutputOptions(scene));
129
+ }
130
+ /**
131
+ * Bake the constant slab fractions used only for the scatter anisotropy blend (glTF has no
132
+ * anisotropy texture, so anisotropy always uses the constant weights).
133
+ * @param mat the OpenPBR material whose constant weights drive the fractions
134
+ * @returns the constant transmission and subsurface fractions of the dielectric
135
+ */
136
+ _constantFractions(mat) {
137
+ const subsurfaceFractionOfDielectric = (1.0 - mat.transmissionWeight) * mat.subsurfaceWeight;
138
+ const denominator = subsurfaceFractionOfDielectric + mat.transmissionWeight;
139
+ const reciprocal = 1.0 / Math.max(denominator, 1e-6);
140
+ return { transmissionFraction: mat.transmissionWeight * reciprocal, subsurfaceFraction: subsurfaceFractionOfDielectric * reciprocal };
141
+ }
142
+ /**
143
+ * Volumetric scatter: glTF stores a single multi-scatter color blended from the transmission and
144
+ * subsurface slabs, with `scatterStrength = 1`.
145
+ *
146
+ * OpenPBR represents the two slabs as:
147
+ * - The transmission slab stores `transmission_scatter = extinction * singleScatterAlbedo` (OpenPBR 1.1),
148
+ * where the renderer's extinction is `-log(transmissionColor)` with the absorption shifted to be
149
+ * non-negative. We recover the single-scatter albedo by dividing the scattering coefficient by the
150
+ * extinction emitted by KHR_materials_volume (which the importer multiplies back), then convert it
151
+ * to a multi-scatter albedo (the quantity KHR_materials_scatter stores). glTF attenuation can't be
152
+ * textured, so the emitted extinction is derived from the untextured factors; texels whose scattering
153
+ * coefficient exceeds it are approximated by clamping the albedo to one. Without a transmission depth
154
+ * no extinction is emitted, so the per-texel renderer extinction is used instead.
155
+ * - The subsurface slab stores `subsurface_color`, which is already a multi-scatter albedo.
156
+ *
157
+ * The two are blended by `lerp(subsurface, transmission, transmissionFraction)` (the two fractions
158
+ * sum to one). The whole computation is exact per-pixel when any slab or weight carries a texture,
159
+ * and folds to a constant factor when none do.
160
+ *
161
+ * TODO: OpenPBR 1.2 will define `transmission_scatter` as the single-scatter albedo directly,
162
+ * which removes the extinction division below.
163
+ * @param mat the OpenPBR material to bake volumetric scatter data for
164
+ * @returns scatter strength (always 1), the blended multi-scatter color, and blended anisotropy
165
+ */
166
+ async _bakeVolumetricScatterAsync(mat) {
167
+ const scene = mat.getScene();
168
+ const transmissionWeight = mat.transmissionWeight;
169
+ const subsurfaceWeight = mat.subsurfaceWeight;
170
+ // Transmission slab → multi-scatter albedo.
171
+ let transmissionMultiOp;
172
+ if (transmissionWeight > 0) {
173
+ const scatter = mat.transmissionScatter;
174
+ const transmissionScatterOp = CreateTextureWithFactorOperand(mat.transmissionScatterTexture, new Color4(scatter.r, scatter.g, scatter.b, 1.0));
175
+ const intermediateOptions = IntermediateOutputOptions(scene);
176
+ let singleScatterOp;
177
+ if (mat.transmissionDepth > 0) {
178
+ // Divide by the extinction that KHR_materials_volume actually emits, so the importer's
179
+ // `extinction * albedo` reproduces the scattering coefficient.
180
+ const { attenuationColor, attenuationDistance } = GetOpenPBRVolumeAttenuation(mat, true, subsurfaceWeight > 0);
181
+ const scale = (scatterChannel, attenuationChannel) => {
182
+ const extinction = -Math.log(Math.max(attenuationChannel, MinTransmissionColor)) / attenuationDistance;
183
+ return extinction > 0 ? scatterChannel / (mat.transmissionDepth * extinction) : 0;
184
+ };
185
+ singleScatterOp = CreateTextureWithFactorOperand(mat.transmissionScatterTexture, new Color4(scale(scatter.r, attenuationColor[0]), scale(scatter.g, attenuationColor[1]), scale(scatter.b, attenuationColor[2]), 1.0));
186
+ }
187
+ else {
188
+ const color = mat.transmissionColor;
189
+ // Uses the renderer's extinction, which shifts negative absorption (per texel when textured).
190
+ singleScatterOp = await TransmissionScatterToSingleScatterAlbedoAsync(`scatter single-scatter (${mat.name})`, CreateFactorOperand(new Color4(color.r, color.g, color.b, 1.0)), transmissionScatterOp, scene, undefined, undefined, intermediateOptions);
191
+ }
192
+ transmissionMultiOp = await SingleScatterToMultiScatterAlbedoAsync(`scatter multi-scatter (${mat.name})`, singleScatterOp, scene, mat.transmissionScatterAnisotropy, intermediateOptions);
193
+ }
194
+ else {
195
+ transmissionMultiOp = CreateFactorOperand(new Color4(0, 0, 0, 1.0));
196
+ }
197
+ // Subsurface slab → multi-scatter albedo (subsurface_color already is one).
198
+ let subsurfaceMultiOp;
199
+ if (subsurfaceWeight > 0) {
200
+ const color = mat.subsurfaceColor;
201
+ const colorSpace = mat.subsurfaceColorTexture?.gammaSpace ? TextureColorSpace.SRGB : TextureColorSpace.Linear;
202
+ subsurfaceMultiOp = CreateTextureWithFactorOperand(mat.subsurfaceColorTexture, new Color4(color.r, color.g, color.b, 1.0), TextureChannel.RGBA, colorSpace);
203
+ }
204
+ else {
205
+ subsurfaceMultiOp = CreateFactorOperand(new Color4(0, 0, 0, 1.0));
206
+ }
207
+ // multiscatterColor = lerp(subsurface, transmission, transmissionFraction), kept linear in a floating-point
208
+ // target (when supported) so that tiny albedos, such as those produced near anisotropy 1, survive until
209
+ // the final bake.
210
+ const transmissionFractionOp = await this._computeTransmissionFractionAsync(mat);
211
+ const combined = await LerpTexturesAsync(`scatter multiscatter color (${mat.name})`, subsurfaceMultiOp, transmissionMultiOp, transmissionFractionOp, scene, undefined, ChannelMask.RGB, IntermediateOutputOptions(scene));
212
+ let colorFactor = [combined.factor?.r ?? 0, combined.factor?.g ?? 0, combined.factor?.b ?? 0];
213
+ let colorTexture = null;
214
+ if (combined.texture) {
215
+ // The exported texture is 8-bit sRGB, which rounds albedos below about 1e-4 to black. Move each
216
+ // channel's maximum into multiscatterColorFactor and store the texels normalized to it, so the
217
+ // magnitude survives export. Force opaque alpha and output sRGB, as the loader reads it as sRGB.
218
+ const max = await this._readChannelMaximaAsync(combined.texture);
219
+ colorFactor = [max[0], max[1], max[2]];
220
+ const normalize = new Color4(max[0] > 0 ? 1 / max[0] : 1, max[1] > 0 ? 1 / max[1] : 1, max[2] > 0 ? 1 / max[2] : 1, 1.0);
221
+ colorTexture = (await MultiplyTexturesAsync(`scatter multiscatter color (${mat.name})`, { texture: combined.texture, factor: normalize, dispose: combined.dispose }, CreateFactorOperand(new Color4(1, 1, 1, 1)), scene, TextureColorSpace.SRGB, ChannelMask.RGB)).texture;
222
+ }
223
+ const fractions = this._constantFractions(mat);
224
+ const anisotropy = mat.transmissionScatterAnisotropy * fractions.transmissionFraction + mat.subsurfaceScatterAnisotropy * fractions.subsurfaceFraction;
225
+ const ownedTextures = [];
226
+ if (colorTexture) {
227
+ ownedTextures.push(colorTexture);
228
+ }
229
+ return {
230
+ strengthFactor: 1.0,
231
+ strengthTexture: null,
232
+ colorFactor,
233
+ colorTexture,
234
+ anisotropy,
235
+ ownedTextures,
236
+ };
237
+ }
238
+ /**
239
+ * Read back a texture and return the maximum of each RGB channel.
240
+ * @param texture the texture to read (unsigned-byte, or floating-point read back as 32-bit floats)
241
+ * @returns the per-channel maxima, normalized to [0, 1] for unsigned-byte textures
242
+ */
243
+ async _readChannelMaximaAsync(texture) {
244
+ const pixels = await texture.readPixels();
245
+ const max = [0, 0, 0];
246
+ if (!pixels) {
247
+ return [1, 1, 1];
248
+ }
249
+ const data = pixels instanceof Uint8Array ? pixels : new Float32Array(pixels.buffer, pixels.byteOffset, pixels.byteLength / 4);
250
+ const scale = pixels instanceof Uint8Array ? 1 / 255 : 1;
251
+ for (let i = 0; i < data.length; i += 4) {
252
+ for (let c = 0; c < 3; c++) {
253
+ const value = data[i + c] * scale;
254
+ if (value > max[c]) {
255
+ max[c] = value;
256
+ }
257
+ }
258
+ }
259
+ return max;
260
+ }
261
+ /**
262
+ * Thin-walled scatter: there is no volumetric slab, so the multi-scatter color is simply
263
+ * `subsurface_color` and the scatter strength is the subsurface fraction of the dielectric
264
+ * `S(1-T) / (1 - (1-T)(1-S))` (= `1 - transmissionFraction`). This is the exact inverse of the
265
+ * loader's thin-walled `ThinWalledScatterWeightsAsync`, so it round-trips.
266
+ * @param mat the OpenPBR material to bake thin-walled scatter data for
267
+ * @returns the subsurface fraction as scatter strength, subsurface_color as multi-scatter color, and subsurface anisotropy
268
+ */
269
+ async _bakeThinWalledScatterAsync(mat) {
270
+ const scene = mat.getScene();
271
+ // scatterStrength = subsurfaceFraction = 1 - transmissionFraction.
272
+ const transmissionFractionOp = await this._computeTransmissionFractionAsync(mat);
273
+ const subsurfaceFractionOp = await InvertTextureAsync(`scatter strength (${mat.name})`, transmissionFractionOp, scene, ChannelMask.RGB);
274
+ const ownedTextures = [];
275
+ const strengthFactor = subsurfaceFractionOp.texture ? 1.0 : (subsurfaceFractionOp.factor?.r ?? 0);
276
+ let strengthTexture = null;
277
+ // multiscatterColor is subsurface_color directly (factor and/or the raw material texture, not owned).
278
+ let colorTexture = mat.subsurfaceColorTexture;
279
+ if (subsurfaceFractionOp.texture) {
280
+ // KHR_materials_scatter reads scatterStrengthTexture from the alpha channel and multiscatterColorTexture
281
+ // from RGB, so pack both into one image. They are referenced separately, so each keeps its own sampling.
282
+ const fractionTexture = subsurfaceFractionOp.texture;
283
+ const colorSource = colorTexture
284
+ ? ((await this._encodeColorTextureAsSrgbAsync(`scatter color (${mat.name})`, colorTexture, IntermediateOutputOptions(scene))) ?? colorTexture)
285
+ : null;
286
+ const packed = await MergeTexturesAsync(`scatter color and strength (${mat.name})`, CreateRGBAConfiguration(colorSource ? CreateTextureInput(colorSource, 0) : CreateConstantInput(1.0), colorSource ? CreateTextureInput(colorSource, 1) : CreateConstantInput(1.0), colorSource ? CreateTextureInput(colorSource, 2) : CreateConstantInput(1.0), CreateTextureInput(fractionTexture, 0)), scene);
287
+ if (colorSource && colorSource !== colorTexture) {
288
+ colorSource.dispose();
289
+ }
290
+ ownedTextures.push(packed);
291
+ const packedInternal = packed.getInternalTexture();
292
+ if (colorTexture && packedInternal) {
293
+ // Keep the source color filtering rather than the merger's nearest sampling.
294
+ scene.getEngine().updateTextureSamplingMode(colorTexture.samplingMode, packedInternal);
295
+ const colorReference = CreateTextureReference(`scatter color (${mat.name})`, packedInternal, colorTexture);
296
+ strengthTexture = CreateTextureReference(`scatter strength (${mat.name})`, packedInternal, fractionTexture);
297
+ ownedTextures.push(colorReference, strengthTexture);
298
+ colorTexture = colorReference;
299
+ }
300
+ else {
301
+ strengthTexture = packed;
302
+ }
303
+ fractionTexture.dispose(); // intermediate; the packed texture replaces it
304
+ }
305
+ else if (colorTexture) {
306
+ // Exported without packing: still re-encode linear data, keeping the source filtering.
307
+ const encoded = await this._encodeColorTextureAsSrgbAsync(`scatter color (${mat.name})`, colorTexture, { samplingMode: colorTexture.samplingMode });
308
+ if (encoded) {
309
+ ownedTextures.push(encoded);
310
+ colorTexture = encoded;
311
+ }
312
+ }
313
+ const color = mat.subsurfaceColor;
314
+ return {
315
+ strengthFactor,
316
+ strengthTexture,
317
+ colorFactor: [color.r, color.g, color.b],
318
+ colorTexture,
319
+ anisotropy: mat.subsurfaceScatterAnisotropy,
320
+ ownedTextures,
321
+ };
322
+ }
323
+ /**
324
+ * glTF reads multiscatterColorTexture as sRGB, but the exporter writes texels as stored. Linear textures and
325
+ * hardware sRGB buffers both sample as linear values, so bake their RGB into a new sRGB-encoded texture that
326
+ * keeps the source UV set, wrap modes and transform. Textures that already store sRGB bytes need no change.
327
+ * @param name the name of the encoded texture
328
+ * @param texture the source color texture, which is not modified or disposed
329
+ * @param outputOptions render-target options for the encoded texture
330
+ * @returns a new texture owned by the caller, or null when the source can be exported as-is
331
+ */
332
+ async _encodeColorTextureAsSrgbAsync(name, texture, outputOptions) {
333
+ if (texture.gammaSpace && !texture.getInternalTexture()?._useSRGBBuffer) {
334
+ return null;
335
+ }
336
+ const encoded = await ExtractChannelAsync(name, CreateTextureOperand(texture), TextureChannel.RGBA, texture.getScene(), TextureColorSpace.SRGB, undefined, outputOptions);
337
+ return encoded.texture;
338
+ }
339
+ /**
340
+ * After exporting a material
341
+ * @param context GLTF context of the material
342
+ * @param node exported GLTF node
343
+ * @param babylonMaterial corresponding babylon material
344
+ * @returns promise that resolves with the updated node
345
+ */
346
+ // eslint-disable-next-line no-restricted-syntax
347
+ postExportMaterialAsync(context, node, babylonMaterial) {
348
+ return new Promise((resolve) => {
349
+ if (babylonMaterial instanceof OpenPBRMaterial && this._isExtensionEnabled(babylonMaterial)) {
350
+ this._wasUsed = true;
351
+ const result = this._scatterResults.get(babylonMaterial);
352
+ const scatterInfo = {
353
+ scatterStrengthFactor: result?.strengthFactor ?? 1.0,
354
+ scatterAnisotropy: result?.anisotropy ?? 0,
355
+ };
356
+ // Scatter strength (thin-walled bakes a subsurface-fraction texture; volumetric is always 1).
357
+ if (result?.strengthTexture) {
358
+ this._exporter._materialNeedsUVsSet.add(babylonMaterial);
359
+ const strengthTextureInfo = this._exporter._materialExporter.getTextureInfo(result.strengthTexture);
360
+ if (strengthTextureInfo) {
361
+ scatterInfo.scatterStrengthFactor = 1.0;
362
+ scatterInfo.scatterStrengthTexture = strengthTextureInfo;
363
+ }
364
+ }
365
+ // Multi-scatter color (blended albedo for volumetric; subsurface_color for thin-walled).
366
+ if (result) {
367
+ scatterInfo.multiscatterColorFactor = result.colorFactor;
368
+ }
369
+ if (result?.colorTexture) {
370
+ this._exporter._materialNeedsUVsSet.add(babylonMaterial);
371
+ const colorTextureInfo = this._exporter._materialExporter.getTextureInfo(result.colorTexture);
372
+ if (colorTextureInfo) {
373
+ scatterInfo.multiscatterColorTexture = colorTextureInfo;
374
+ }
375
+ }
376
+ node.extensions = node.extensions || {};
377
+ node.extensions[NAME] = scatterInfo;
378
+ }
379
+ resolve(node);
380
+ });
381
+ }
382
+ }
383
+ let _Registered = false;
384
+ /**
385
+ * Registers the KHR_materials_scatter glTF serializer extension with the {@link GLTFExporter}.
386
+ * Safe to call multiple times; only the first call has an effect.
387
+ */
388
+ // eslint-disable-next-line @typescript-eslint/naming-convention
389
+ export function RegisterKHR_materials_scatter() {
390
+ if (_Registered) {
391
+ return;
392
+ }
393
+ _Registered = true;
394
+ GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_scatter(exporter), 101);
395
+ }
396
+ //# sourceMappingURL=KHR_materials_scatter.pure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KHR_materials_scatter.pure.js","sourceRoot":"","sources":["../../../../../../dev/serializers/src/glTF/2.0/Extensions/KHR_materials_scatter.pure.ts"],"names":[],"mappings":"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","sourcesContent":["import { type IMaterial, type IKHRMaterialsScatter } from \"babylonjs-gltf2interface\";\nimport { type IGLTFExporterExtensionV2 } from \"../glTFExporterExtension\";\nimport { GLTFExporter } from \"../glTFExporter\";\nimport { type Material } from \"core/Materials/material\";\nimport { OpenPBRMaterial } from \"core/Materials/PBR/openpbrMaterial.pure\";\nimport { type BaseTexture } from \"core/Materials/Textures/baseTexture\";\nimport { Color3, Color4 } from \"core/Maths/math.color.pure\";\nimport { type Nullable } from \"core/types\";\nimport {\n type ITextureProcessOperand,\n type ITextureProcessorOutputOptions,\n ChannelMask,\n CreateFactorOperand,\n CreateTextureOperand,\n CreateTextureWithFactorOperand,\n ExtractChannelAsync,\n InvertTextureAsync,\n LerpTexturesAsync,\n MultiplyTexturesAsync,\n SingleScatterToMultiScatterAlbedoAsync,\n TextureChannel,\n TextureColorSpace,\n TransmissionFractionAsync,\n TransmissionScatterToSingleScatterAlbedoAsync,\n} from \"core/Materials/Textures/textureProcessor\";\nimport { Constants } from \"core/Engines/constants\";\nimport { type Scene } from \"core/scene\";\nimport { MergeTexturesAsync, CreateRGBAConfiguration, CreateTextureInput, CreateConstantInput } from \"core/Materials/Textures/textureMerger\";\nimport { Texture } from \"core/Materials/Textures/texture.pure\";\nimport { type InternalTexture } from \"core/Materials/Textures/internalTexture\";\nimport { CopyTextureSampling, GetOpenPBRVolumeAttenuation, MinTransmissionColor } from \"../glTFUtilities\";\n\nconst NAME = \"KHR_materials_scatter\";\n\n/**\n * Render-target options for intermediate passes: floating point when the engine can render to it, so\n * only the final exported textures are quantized to 8 bits.\n * @param scene the scene whose engine renders the passes\n * @returns output options for an intermediate texture processor pass\n */\nfunction IntermediateOutputOptions(scene: Scene): ITextureProcessorOutputOptions {\n const caps = scene.getEngine().getCaps();\n return {\n textureType: caps.textureFloatRender ? Constants.TEXTURETYPE_FLOAT : caps.textureHalfFloatRender ? Constants.TEXTURETYPE_HALF_FLOAT : Constants.TEXTURETYPE_UNSIGNED_BYTE,\n samplingMode: Constants.TEXTURE_NEAREST_SAMPLINGMODE,\n };\n}\n\n/**\n * Creates a texture that references an existing internal texture with the sampling parameters of another texture,\n * so several glTF texture references can share one image while keeping their own UV set and transform.\n * @param name the name of the new texture\n * @param internalTexture the internal texture to reference\n * @param samplingSource the texture to copy the UV set, wrap modes and transform from\n * @returns the new texture, which must be disposed by the caller\n */\nfunction CreateTextureReference(name: string, internalTexture: InternalTexture, samplingSource: BaseTexture): Texture {\n const reference = new Texture(null, samplingSource.getScene());\n reference.name = name;\n reference._texture = internalTexture;\n internalTexture.incrementReferences();\n CopyTextureSampling(samplingSource, reference);\n return reference;\n}\n\n// Scatter result cached per material between postExportMaterialAdditionalTexturesAsync (where it is\n// baked) and postExportMaterialAsync (where it is referenced). `ownedTextures` holds the textures this\n// extension created (as opposed to raw material textures it references) and are disposed in dispose().\ntype ScatterResult = {\n strengthFactor: number;\n strengthTexture: Nullable<BaseTexture>;\n colorFactor: [number, number, number];\n colorTexture: Nullable<BaseTexture>;\n anisotropy: number;\n ownedTextures: BaseTexture[];\n};\n\n/**\n * TODO: In-progress specification\n * [Specification](https://github.com/KhronosGroup/glTF/blob/7ea427ed55d44427e83c0a6d1c87068b1a4151c5/extensions/2.0/Khronos/KHR_materials_scatter/README.md)\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport class KHR_materials_scatter implements IGLTFExporterExtensionV2 {\n /** Name of this extension */\n public readonly name = NAME;\n\n /** Defines whether this extension is enabled */\n public enabled = true;\n\n /** Defines whether this extension is required */\n public required = false;\n\n private _exporter: GLTFExporter;\n\n private _wasUsed = false;\n\n private _scatterResults = new Map<Material, ScatterResult>();\n\n constructor(exporter: GLTFExporter) {\n this._exporter = exporter;\n }\n\n public dispose() {\n for (const result of this._scatterResults.values()) {\n for (const texture of result.ownedTextures) {\n texture.dispose();\n }\n }\n this._scatterResults.clear();\n }\n\n /** @internal */\n public get wasUsed() {\n return this._wasUsed;\n }\n\n /**\n * After exporting a material, bake the combined multi-scatter color texture (if any).\n * @param context GLTF context of the material\n * @param node exported GLTF node\n * @param babylonMaterial corresponding babylon material\n * @returns array of additional textures to export\n */\n public async postExportMaterialAdditionalTexturesAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise<BaseTexture[]> {\n const additionalTextures: BaseTexture[] = [];\n\n if (babylonMaterial instanceof OpenPBRMaterial && this._isExtensionEnabled(babylonMaterial)) {\n const result = await this._bakeScatterAsync(babylonMaterial);\n this._scatterResults.set(babylonMaterial, result);\n if (result.strengthTexture) {\n additionalTextures.push(result.strengthTexture);\n }\n if (result.colorTexture) {\n additionalTextures.push(result.colorTexture);\n }\n }\n\n return additionalTextures;\n }\n\n private _isExtensionEnabled(mat: OpenPBRMaterial): boolean {\n // This extension must not be used on a material that also uses KHR_materials_unlit\n if (mat.unlit) {\n return false;\n }\n // Scattering is enabled if either subsurface or transmission scattering is enabled.\n // Transmission scattering is only enabled if the material is not thin-walled, and has a non-zero transmission depth.\n const transmissionVolume = mat.transmissionWeight > 0 && !mat.geometryThinWalled && mat.transmissionDepth > 0;\n const transmissionScatter = transmissionVolume && !mat.transmissionScatter.equals(Color3.Black());\n if (transmissionScatter || mat.subsurfaceWeight > 0) {\n return true;\n }\n return false;\n }\n\n /**\n * Bake the KHR_materials_scatter data for an OpenPBR material, dispatching on thin-walled vs volumetric.\n * @param mat the OpenPBR material to bake scatter data for\n * @returns the scatter strength, multi-scatter color, and anisotropy (constants and/or baked textures)\n */\n private async _bakeScatterAsync(mat: OpenPBRMaterial): Promise<ScatterResult> {\n return mat.geometryThinWalled ? await this._bakeThinWalledScatterAsync(mat) : await this._bakeVolumetricScatterAsync(mat);\n }\n\n /**\n * Per-pixel transmission fraction operand `T / (T + S(1 - T))` (= `T / (1 - (1 - T)(1 - S))`) from the\n * (possibly textured) transmission weight `T` and subsurface weight `S`. Folds to a constant when\n * neither weight has a texture. The subsurface fraction is `1 - transmissionFraction`. Weights are read\n * from the glTF-convention channels (transmission R; subsurface A when packed in alpha, otherwise R).\n * The fraction is computed in one fused pass into a floating-point target (when supported), so small\n * weights don't cancel out in 8-bit intermediates; callers quantize only their final output.\n * @param mat the OpenPBR material whose weights drive the fraction\n * @returns an operand carrying the transmission fraction (constant or baked texture)\n */\n private async _computeTransmissionFractionAsync(mat: OpenPBRMaterial): Promise<ITextureProcessOperand> {\n const scene = mat.getScene();\n const transmissionWeight = mat.transmissionWeight;\n const subsurfaceWeight = mat.subsurfaceWeight;\n const subsurfaceChannel = mat._useSubsurfaceWeightFromTextureAlpha ? TextureChannel.A : TextureChannel.R;\n const transmissionWeightOp = CreateTextureWithFactorOperand(\n mat.transmissionWeightTexture,\n new Color4(transmissionWeight, transmissionWeight, transmissionWeight, 1.0),\n TextureChannel.R\n );\n const subsurfaceWeightOp = CreateTextureWithFactorOperand(\n mat.subsurfaceWeightTexture,\n new Color4(subsurfaceWeight, subsurfaceWeight, subsurfaceWeight, 1.0),\n subsurfaceChannel\n );\n\n return await TransmissionFractionAsync(\n `scatter transmission fraction (${mat.name})`,\n transmissionWeightOp,\n subsurfaceWeightOp,\n scene,\n undefined,\n undefined,\n IntermediateOutputOptions(scene)\n );\n }\n\n /**\n * Bake the constant slab fractions used only for the scatter anisotropy blend (glTF has no\n * anisotropy texture, so anisotropy always uses the constant weights).\n * @param mat the OpenPBR material whose constant weights drive the fractions\n * @returns the constant transmission and subsurface fractions of the dielectric\n */\n private _constantFractions(mat: OpenPBRMaterial): { transmissionFraction: number; subsurfaceFraction: number } {\n const subsurfaceFractionOfDielectric = (1.0 - mat.transmissionWeight) * mat.subsurfaceWeight;\n const denominator = subsurfaceFractionOfDielectric + mat.transmissionWeight;\n const reciprocal = 1.0 / Math.max(denominator, 1e-6);\n return { transmissionFraction: mat.transmissionWeight * reciprocal, subsurfaceFraction: subsurfaceFractionOfDielectric * reciprocal };\n }\n\n /**\n * Volumetric scatter: glTF stores a single multi-scatter color blended from the transmission and\n * subsurface slabs, with `scatterStrength = 1`.\n *\n * OpenPBR represents the two slabs as:\n * - The transmission slab stores `transmission_scatter = extinction * singleScatterAlbedo` (OpenPBR 1.1),\n * where the renderer's extinction is `-log(transmissionColor)` with the absorption shifted to be\n * non-negative. We recover the single-scatter albedo by dividing the scattering coefficient by the\n * extinction emitted by KHR_materials_volume (which the importer multiplies back), then convert it\n * to a multi-scatter albedo (the quantity KHR_materials_scatter stores). glTF attenuation can't be\n * textured, so the emitted extinction is derived from the untextured factors; texels whose scattering\n * coefficient exceeds it are approximated by clamping the albedo to one. Without a transmission depth\n * no extinction is emitted, so the per-texel renderer extinction is used instead.\n * - The subsurface slab stores `subsurface_color`, which is already a multi-scatter albedo.\n *\n * The two are blended by `lerp(subsurface, transmission, transmissionFraction)` (the two fractions\n * sum to one). The whole computation is exact per-pixel when any slab or weight carries a texture,\n * and folds to a constant factor when none do.\n *\n * TODO: OpenPBR 1.2 will define `transmission_scatter` as the single-scatter albedo directly,\n * which removes the extinction division below.\n * @param mat the OpenPBR material to bake volumetric scatter data for\n * @returns scatter strength (always 1), the blended multi-scatter color, and blended anisotropy\n */\n private async _bakeVolumetricScatterAsync(mat: OpenPBRMaterial): Promise<ScatterResult> {\n const scene = mat.getScene();\n const transmissionWeight = mat.transmissionWeight;\n const subsurfaceWeight = mat.subsurfaceWeight;\n\n // Transmission slab → multi-scatter albedo.\n let transmissionMultiOp: ITextureProcessOperand;\n if (transmissionWeight > 0) {\n const scatter = mat.transmissionScatter;\n const transmissionScatterOp = CreateTextureWithFactorOperand(mat.transmissionScatterTexture, new Color4(scatter.r, scatter.g, scatter.b, 1.0));\n const intermediateOptions = IntermediateOutputOptions(scene);\n let singleScatterOp: ITextureProcessOperand;\n if (mat.transmissionDepth > 0) {\n // Divide by the extinction that KHR_materials_volume actually emits, so the importer's\n // `extinction * albedo` reproduces the scattering coefficient.\n const { attenuationColor, attenuationDistance } = GetOpenPBRVolumeAttenuation(mat, true, subsurfaceWeight > 0);\n const scale = (scatterChannel: number, attenuationChannel: number) => {\n const extinction = -Math.log(Math.max(attenuationChannel, MinTransmissionColor)) / attenuationDistance;\n return extinction > 0 ? scatterChannel / (mat.transmissionDepth * extinction) : 0;\n };\n singleScatterOp = CreateTextureWithFactorOperand(\n mat.transmissionScatterTexture,\n new Color4(scale(scatter.r, attenuationColor[0]), scale(scatter.g, attenuationColor[1]), scale(scatter.b, attenuationColor[2]), 1.0)\n );\n } else {\n const color = mat.transmissionColor;\n // Uses the renderer's extinction, which shifts negative absorption (per texel when textured).\n singleScatterOp = await TransmissionScatterToSingleScatterAlbedoAsync(\n `scatter single-scatter (${mat.name})`,\n CreateFactorOperand(new Color4(color.r, color.g, color.b, 1.0)),\n transmissionScatterOp,\n scene,\n undefined,\n undefined,\n intermediateOptions\n );\n }\n transmissionMultiOp = await SingleScatterToMultiScatterAlbedoAsync(\n `scatter multi-scatter (${mat.name})`,\n singleScatterOp,\n scene,\n mat.transmissionScatterAnisotropy,\n intermediateOptions\n );\n } else {\n transmissionMultiOp = CreateFactorOperand(new Color4(0, 0, 0, 1.0));\n }\n\n // Subsurface slab → multi-scatter albedo (subsurface_color already is one).\n let subsurfaceMultiOp: ITextureProcessOperand;\n if (subsurfaceWeight > 0) {\n const color = mat.subsurfaceColor;\n const colorSpace = mat.subsurfaceColorTexture?.gammaSpace ? TextureColorSpace.SRGB : TextureColorSpace.Linear;\n subsurfaceMultiOp = CreateTextureWithFactorOperand(mat.subsurfaceColorTexture, new Color4(color.r, color.g, color.b, 1.0), TextureChannel.RGBA, colorSpace);\n } else {\n subsurfaceMultiOp = CreateFactorOperand(new Color4(0, 0, 0, 1.0));\n }\n\n // multiscatterColor = lerp(subsurface, transmission, transmissionFraction), kept linear in a floating-point\n // target (when supported) so that tiny albedos, such as those produced near anisotropy 1, survive until\n // the final bake.\n const transmissionFractionOp = await this._computeTransmissionFractionAsync(mat);\n const combined = await LerpTexturesAsync(\n `scatter multiscatter color (${mat.name})`,\n subsurfaceMultiOp,\n transmissionMultiOp,\n transmissionFractionOp,\n scene,\n undefined,\n ChannelMask.RGB,\n IntermediateOutputOptions(scene)\n );\n let colorFactor: [number, number, number] = [combined.factor?.r ?? 0, combined.factor?.g ?? 0, combined.factor?.b ?? 0];\n let colorTexture: Nullable<BaseTexture> = null;\n if (combined.texture) {\n // The exported texture is 8-bit sRGB, which rounds albedos below about 1e-4 to black. Move each\n // channel's maximum into multiscatterColorFactor and store the texels normalized to it, so the\n // magnitude survives export. Force opaque alpha and output sRGB, as the loader reads it as sRGB.\n const max = await this._readChannelMaximaAsync(combined.texture);\n colorFactor = [max[0], max[1], max[2]];\n const normalize = new Color4(max[0] > 0 ? 1 / max[0] : 1, max[1] > 0 ? 1 / max[1] : 1, max[2] > 0 ? 1 / max[2] : 1, 1.0);\n colorTexture = (\n await MultiplyTexturesAsync(\n `scatter multiscatter color (${mat.name})`,\n { texture: combined.texture, factor: normalize, dispose: combined.dispose },\n CreateFactorOperand(new Color4(1, 1, 1, 1)),\n scene,\n TextureColorSpace.SRGB,\n ChannelMask.RGB\n )\n ).texture;\n }\n\n const fractions = this._constantFractions(mat);\n const anisotropy = mat.transmissionScatterAnisotropy * fractions.transmissionFraction + mat.subsurfaceScatterAnisotropy * fractions.subsurfaceFraction;\n const ownedTextures: BaseTexture[] = [];\n if (colorTexture) {\n ownedTextures.push(colorTexture);\n }\n return {\n strengthFactor: 1.0,\n strengthTexture: null,\n colorFactor,\n colorTexture,\n anisotropy,\n ownedTextures,\n };\n }\n\n /**\n * Read back a texture and return the maximum of each RGB channel.\n * @param texture the texture to read (unsigned-byte, or floating-point read back as 32-bit floats)\n * @returns the per-channel maxima, normalized to [0, 1] for unsigned-byte textures\n */\n private async _readChannelMaximaAsync(texture: BaseTexture): Promise<[number, number, number]> {\n const pixels = await texture.readPixels();\n const max: [number, number, number] = [0, 0, 0];\n if (!pixels) {\n return [1, 1, 1];\n }\n const data = pixels instanceof Uint8Array ? pixels : new Float32Array(pixels.buffer, pixels.byteOffset, pixels.byteLength / 4);\n const scale = pixels instanceof Uint8Array ? 1 / 255 : 1;\n for (let i = 0; i < data.length; i += 4) {\n for (let c = 0; c < 3; c++) {\n const value = data[i + c] * scale;\n if (value > max[c]) {\n max[c] = value;\n }\n }\n }\n return max;\n }\n\n /**\n * Thin-walled scatter: there is no volumetric slab, so the multi-scatter color is simply\n * `subsurface_color` and the scatter strength is the subsurface fraction of the dielectric\n * `S(1-T) / (1 - (1-T)(1-S))` (= `1 - transmissionFraction`). This is the exact inverse of the\n * loader's thin-walled `ThinWalledScatterWeightsAsync`, so it round-trips.\n * @param mat the OpenPBR material to bake thin-walled scatter data for\n * @returns the subsurface fraction as scatter strength, subsurface_color as multi-scatter color, and subsurface anisotropy\n */\n private async _bakeThinWalledScatterAsync(mat: OpenPBRMaterial): Promise<ScatterResult> {\n const scene = mat.getScene();\n\n // scatterStrength = subsurfaceFraction = 1 - transmissionFraction.\n const transmissionFractionOp = await this._computeTransmissionFractionAsync(mat);\n const subsurfaceFractionOp = await InvertTextureAsync(`scatter strength (${mat.name})`, transmissionFractionOp, scene, ChannelMask.RGB);\n\n const ownedTextures: BaseTexture[] = [];\n const strengthFactor = subsurfaceFractionOp.texture ? 1.0 : (subsurfaceFractionOp.factor?.r ?? 0);\n let strengthTexture: Nullable<BaseTexture> = null;\n // multiscatterColor is subsurface_color directly (factor and/or the raw material texture, not owned).\n let colorTexture: Nullable<BaseTexture> = mat.subsurfaceColorTexture;\n if (subsurfaceFractionOp.texture) {\n // KHR_materials_scatter reads scatterStrengthTexture from the alpha channel and multiscatterColorTexture\n // from RGB, so pack both into one image. They are referenced separately, so each keeps its own sampling.\n const fractionTexture = subsurfaceFractionOp.texture;\n const colorSource: Nullable<BaseTexture> = colorTexture\n ? ((await this._encodeColorTextureAsSrgbAsync(`scatter color (${mat.name})`, colorTexture, IntermediateOutputOptions(scene))) ?? colorTexture)\n : null;\n const packed = await MergeTexturesAsync(\n `scatter color and strength (${mat.name})`,\n CreateRGBAConfiguration(\n colorSource ? CreateTextureInput(colorSource, 0) : CreateConstantInput(1.0),\n colorSource ? CreateTextureInput(colorSource, 1) : CreateConstantInput(1.0),\n colorSource ? CreateTextureInput(colorSource, 2) : CreateConstantInput(1.0),\n CreateTextureInput(fractionTexture, 0)\n ),\n scene\n );\n if (colorSource && colorSource !== colorTexture) {\n colorSource.dispose();\n }\n ownedTextures.push(packed);\n\n const packedInternal = packed.getInternalTexture();\n if (colorTexture && packedInternal) {\n // Keep the source color filtering rather than the merger's nearest sampling.\n scene.getEngine().updateTextureSamplingMode(colorTexture.samplingMode, packedInternal);\n const colorReference = CreateTextureReference(`scatter color (${mat.name})`, packedInternal, colorTexture);\n strengthTexture = CreateTextureReference(`scatter strength (${mat.name})`, packedInternal, fractionTexture);\n ownedTextures.push(colorReference, strengthTexture);\n colorTexture = colorReference;\n } else {\n strengthTexture = packed;\n }\n fractionTexture.dispose(); // intermediate; the packed texture replaces it\n } else if (colorTexture) {\n // Exported without packing: still re-encode linear data, keeping the source filtering.\n const encoded = await this._encodeColorTextureAsSrgbAsync(`scatter color (${mat.name})`, colorTexture, { samplingMode: colorTexture.samplingMode });\n if (encoded) {\n ownedTextures.push(encoded);\n colorTexture = encoded;\n }\n }\n\n const color = mat.subsurfaceColor;\n return {\n strengthFactor,\n strengthTexture,\n colorFactor: [color.r, color.g, color.b],\n colorTexture,\n anisotropy: mat.subsurfaceScatterAnisotropy,\n ownedTextures,\n };\n }\n\n /**\n * glTF reads multiscatterColorTexture as sRGB, but the exporter writes texels as stored. Linear textures and\n * hardware sRGB buffers both sample as linear values, so bake their RGB into a new sRGB-encoded texture that\n * keeps the source UV set, wrap modes and transform. Textures that already store sRGB bytes need no change.\n * @param name the name of the encoded texture\n * @param texture the source color texture, which is not modified or disposed\n * @param outputOptions render-target options for the encoded texture\n * @returns a new texture owned by the caller, or null when the source can be exported as-is\n */\n private async _encodeColorTextureAsSrgbAsync(name: string, texture: BaseTexture, outputOptions: ITextureProcessorOutputOptions): Promise<Nullable<BaseTexture>> {\n if (texture.gammaSpace && !texture.getInternalTexture()?._useSRGBBuffer) {\n return null;\n }\n const encoded = await ExtractChannelAsync(name, CreateTextureOperand(texture), TextureChannel.RGBA, texture.getScene()!, TextureColorSpace.SRGB, undefined, outputOptions);\n return encoded.texture;\n }\n\n /**\n * After exporting a material\n * @param context GLTF context of the material\n * @param node exported GLTF node\n * @param babylonMaterial corresponding babylon material\n * @returns promise that resolves with the updated node\n */\n // eslint-disable-next-line no-restricted-syntax\n public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise<IMaterial> {\n return new Promise((resolve) => {\n if (babylonMaterial instanceof OpenPBRMaterial && this._isExtensionEnabled(babylonMaterial)) {\n this._wasUsed = true;\n\n const result = this._scatterResults.get(babylonMaterial);\n\n const scatterInfo: IKHRMaterialsScatter = {\n scatterStrengthFactor: result?.strengthFactor ?? 1.0,\n scatterAnisotropy: result?.anisotropy ?? 0,\n };\n\n // Scatter strength (thin-walled bakes a subsurface-fraction texture; volumetric is always 1).\n if (result?.strengthTexture) {\n this._exporter._materialNeedsUVsSet.add(babylonMaterial);\n const strengthTextureInfo = this._exporter._materialExporter.getTextureInfo(result.strengthTexture);\n if (strengthTextureInfo) {\n scatterInfo.scatterStrengthFactor = 1.0;\n scatterInfo.scatterStrengthTexture = strengthTextureInfo;\n }\n }\n\n // Multi-scatter color (blended albedo for volumetric; subsurface_color for thin-walled).\n if (result) {\n scatterInfo.multiscatterColorFactor = result.colorFactor;\n }\n if (result?.colorTexture) {\n this._exporter._materialNeedsUVsSet.add(babylonMaterial);\n const colorTextureInfo = this._exporter._materialExporter.getTextureInfo(result.colorTexture);\n if (colorTextureInfo) {\n scatterInfo.multiscatterColorTexture = colorTextureInfo;\n }\n }\n\n node.extensions = node.extensions || {};\n node.extensions[NAME] = scatterInfo;\n }\n resolve(node);\n });\n }\n}\n\nlet _Registered = false;\n/**\n * Registers the KHR_materials_scatter glTF serializer extension with the {@link GLTFExporter}.\n * Safe to call multiple times; only the first call has an effect.\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport function RegisterKHR_materials_scatter(): void {\n if (_Registered) {\n return;\n }\n _Registered = true;\n\n GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_scatter(exporter), 101);\n}\n"]}
@@ -15,6 +15,8 @@ export declare class KHR_materials_transmission implements IGLTFExporterExtensio
15
15
  required: boolean;
16
16
  private _exporter;
17
17
  private _wasUsed;
18
+ private _transmissionOperands;
19
+ private _baseColorOperands;
18
20
  constructor(exporter: GLTFExporter);
19
21
  /** Dispose */
20
22
  dispose(): void;