@aura3d/assets 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AdvancedAssetCorpus.d.ts.map +1 -1
- package/dist/AdvancedAssetCorpus.js +7 -12
- package/dist/AdvancedAssetCorpus.js.map +1 -1
- package/dist/AssetDecoderRegistry.d.ts +56 -0
- package/dist/AssetDecoderRegistry.d.ts.map +1 -0
- package/dist/AssetDecoderRegistry.js +225 -0
- package/dist/AssetDecoderRegistry.js.map +1 -0
- package/dist/GLTFAnimationRuntime.d.ts +300 -6
- package/dist/GLTFAnimationRuntime.d.ts.map +1 -1
- package/dist/GLTFAnimationRuntime.js +981 -60
- package/dist/GLTFAnimationRuntime.js.map +1 -1
- package/dist/GLTFAssetParseCache.d.ts +24 -0
- package/dist/GLTFAssetParseCache.d.ts.map +1 -0
- package/dist/GLTFAssetParseCache.js +100 -0
- package/dist/GLTFAssetParseCache.js.map +1 -0
- package/dist/GLTFCompressionDecoders.d.ts +1 -0
- package/dist/GLTFCompressionDecoders.d.ts.map +1 -1
- package/dist/GLTFCompressionDecoders.js +20 -5
- package/dist/GLTFCompressionDecoders.js.map +1 -1
- package/dist/GLTFExtensionSupport.js +3 -3
- package/dist/GLTFExtensionSupport.js.map +1 -1
- package/dist/GLTFLoader.d.ts +28 -1
- package/dist/GLTFLoader.d.ts.map +1 -1
- package/dist/GLTFLoader.js +197 -30
- package/dist/GLTFLoader.js.map +1 -1
- package/dist/GLTFRenderResources.d.ts +77 -2
- package/dist/GLTFRenderResources.d.ts.map +1 -1
- package/dist/GLTFRenderResources.js +310 -153
- package/dist/GLTFRenderResources.js.map +1 -1
- package/dist/KTX2BasisTextureTranscoder.d.ts +70 -12
- package/dist/KTX2BasisTextureTranscoder.d.ts.map +1 -1
- package/dist/KTX2BasisTextureTranscoder.js +225 -184
- package/dist/KTX2BasisTextureTranscoder.js.map +1 -1
- package/dist/KTX2TargetSelection.d.ts +25 -0
- package/dist/KTX2TargetSelection.d.ts.map +1 -0
- package/dist/KTX2TargetSelection.js +36 -0
- package/dist/KTX2TargetSelection.js.map +1 -0
- package/dist/KTX2TranscodeDriver.d.ts +70 -0
- package/dist/KTX2TranscodeDriver.d.ts.map +1 -0
- package/dist/KTX2TranscodeDriver.js +104 -0
- package/dist/KTX2TranscodeDriver.js.map +1 -0
- package/dist/KTX2TranscodeWorker.d.ts +30 -0
- package/dist/KTX2TranscodeWorker.d.ts.map +1 -0
- package/dist/KTX2TranscodeWorker.js +230 -0
- package/dist/KTX2TranscodeWorker.js.map +1 -0
- package/dist/MikkTSpaceTangents.d.ts +43 -0
- package/dist/MikkTSpaceTangents.d.ts.map +1 -0
- package/dist/MikkTSpaceTangents.js +149 -0
- package/dist/MikkTSpaceTangents.js.map +1 -0
- package/dist/asset-corpus/ProductionAssetCorpus.d.ts.map +1 -1
- package/dist/asset-corpus/ProductionAssetCorpus.js +7 -13
- package/dist/asset-corpus/ProductionAssetCorpus.js.map +1 -1
- package/dist/asset-corpus/ProductionGLTFRenderPipeline.d.ts +43 -0
- package/dist/asset-corpus/ProductionGLTFRenderPipeline.d.ts.map +1 -1
- package/dist/asset-corpus/ProductionGLTFRenderPipeline.js +146 -22
- package/dist/asset-corpus/ProductionGLTFRenderPipeline.js.map +1 -1
- package/dist/assetPhaseMarks.d.ts +14 -0
- package/dist/assetPhaseMarks.d.ts.map +1 -0
- package/dist/assetPhaseMarks.js +23 -0
- package/dist/assetPhaseMarks.js.map +1 -0
- package/dist/browser-index.d.ts +5 -1
- package/dist/browser-index.d.ts.map +1 -1
- package/dist/browser-index.js +3 -1
- package/dist/browser-index.js.map +1 -1
- package/dist/contracts/decoders.d.ts +15 -0
- package/dist/contracts/decoders.d.ts.map +1 -0
- package/dist/contracts/decoders.js +8 -0
- package/dist/contracts/decoders.js.map +1 -0
- package/dist/decoderLoad.d.ts +28 -0
- package/dist/decoderLoad.d.ts.map +1 -0
- package/dist/decoderLoad.js +49 -0
- package/dist/decoderLoad.js.map +1 -0
- package/dist/gltf/ImageDecode.d.ts +18 -0
- package/dist/gltf/ImageDecode.d.ts.map +1 -0
- package/dist/gltf/ImageDecode.js +140 -0
- package/dist/gltf/ImageDecode.js.map +1 -0
- package/dist/gltf-runtime.d.ts +1 -1
- package/dist/gltf-runtime.d.ts.map +1 -1
- package/dist/gltf-runtime.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/lanes/index.d.ts +17 -0
- package/dist/lanes/index.d.ts.map +1 -0
- package/dist/lanes/index.js +17 -0
- package/dist/lanes/index.js.map +1 -0
- package/dist/lanes/prd01.d.ts +6 -0
- package/dist/lanes/prd01.d.ts.map +1 -0
- package/dist/lanes/prd01.js +6 -0
- package/dist/lanes/prd01.js.map +1 -0
- package/dist/lanes/prd02.d.ts +6 -0
- package/dist/lanes/prd02.d.ts.map +1 -0
- package/dist/lanes/prd02.js +6 -0
- package/dist/lanes/prd02.js.map +1 -0
- package/dist/lanes/prd03.d.ts +6 -0
- package/dist/lanes/prd03.d.ts.map +1 -0
- package/dist/lanes/prd03.js +6 -0
- package/dist/lanes/prd03.js.map +1 -0
- package/dist/lanes/prd04.d.ts +6 -0
- package/dist/lanes/prd04.d.ts.map +1 -0
- package/dist/lanes/prd04.js +6 -0
- package/dist/lanes/prd04.js.map +1 -0
- package/dist/lanes/prd05.d.ts +14 -0
- package/dist/lanes/prd05.d.ts.map +1 -0
- package/dist/lanes/prd05.js +14 -0
- package/dist/lanes/prd05.js.map +1 -0
- package/dist/lanes/prd06.d.ts +6 -0
- package/dist/lanes/prd06.d.ts.map +1 -0
- package/dist/lanes/prd06.js +6 -0
- package/dist/lanes/prd06.js.map +1 -0
- package/dist/lanes/prd07.d.ts +6 -0
- package/dist/lanes/prd07.d.ts.map +1 -0
- package/dist/lanes/prd07.js +6 -0
- package/dist/lanes/prd07.js.map +1 -0
- package/dist/lanes/prd08.d.ts +6 -0
- package/dist/lanes/prd08.d.ts.map +1 -0
- package/dist/lanes/prd08.js +6 -0
- package/dist/lanes/prd08.js.map +1 -0
- package/dist/lanes/prd09.d.ts +6 -0
- package/dist/lanes/prd09.d.ts.map +1 -0
- package/dist/lanes/prd09.js +6 -0
- package/dist/lanes/prd09.js.map +1 -0
- package/dist/lanes/prd10.d.ts +6 -0
- package/dist/lanes/prd10.d.ts.map +1 -0
- package/dist/lanes/prd10.js +6 -0
- package/dist/lanes/prd10.js.map +1 -0
- package/dist/lanes/prd11.d.ts +6 -0
- package/dist/lanes/prd11.d.ts.map +1 -0
- package/dist/lanes/prd11.js +6 -0
- package/dist/lanes/prd11.js.map +1 -0
- package/dist/lanes/prd12.d.ts +6 -0
- package/dist/lanes/prd12.d.ts.map +1 -0
- package/dist/lanes/prd12.js +6 -0
- package/dist/lanes/prd12.js.map +1 -0
- package/dist/lanes/prd13.d.ts +6 -0
- package/dist/lanes/prd13.d.ts.map +1 -0
- package/dist/lanes/prd13.js +6 -0
- package/dist/lanes/prd13.js.map +1 -0
- package/dist/lanes/prd14.d.ts +6 -0
- package/dist/lanes/prd14.d.ts.map +1 -0
- package/dist/lanes/prd14.js +6 -0
- package/dist/lanes/prd14.js.map +1 -0
- package/dist/lanes/prd15.d.ts +6 -0
- package/dist/lanes/prd15.d.ts.map +1 -0
- package/dist/lanes/prd15.js +6 -0
- package/dist/lanes/prd15.js.map +1 -0
- package/dist/vite/auraDecoders.d.ts +31 -0
- package/dist/vite/auraDecoders.d.ts.map +1 -0
- package/dist/vite/auraDecoders.js +89 -0
- package/dist/vite/auraDecoders.js.map +1 -0
- package/package.json +13 -5
- package/vendor/basis/README.md +25 -0
- package/vendor/basis/basis_transcoder.js +19 -0
- package/vendor/basis/basis_transcoder.wasm +0 -0
- package/vendor/draco/README.md +23 -0
- package/vendor/draco/draco_decoder.js +33 -0
- package/vendor/draco/draco_decoder.wasm +0 -0
- package/vendor/meshopt/README.md +21 -0
- package/vendor/meshopt/meshopt_decoder.cjs +199 -0
- package/vendor/meshopt/meshopt_decoder.mjs +196 -0
- package/vendor/meshopt_decoder.mjs +196 -0
|
@@ -1,7 +1,61 @@
|
|
|
1
|
-
import { Geometry, IndexBuffer, InstancedPBRMaterial, InstancedUnlitMaterial, Material, PBRMaterial, Sampler, SkinnedLitMaterial, Texture, TextureBinding, TexturedPBRMaterial, TexturedUnlitMaterial, UnlitMaterial, VertexBuffer, createExternalParityEnvironmentLighting, computePerspectiveCameraFrame, computeSkinnedMorphTargetEnvelopeBounds, VertexFormat, DEFAULT_PBR_ENVIRONMENT_INTENSITY, isTexturedPbrTextureSlotShaderActive, DEFAULT_TEXTURED_PBR_SHADER_NAME } from "@aura3d/rendering";
|
|
1
|
+
import { Geometry, IndexBuffer, InstancedPBRMaterial, InstancedUnlitMaterial, Material, PBRMaterial, Sampler, SkinnedLitMaterial, Texture, TextureBinding, TexturedPBRMaterial, TexturedUnlitMaterial, UnlitMaterial, VertexBuffer, createExternalParityEnvironmentLighting, computePerspectiveCameraFrame, computeSkinnedMorphTargetEnvelopeBounds, VertexFormat, DEFAULT_PBR_ENVIRONMENT_INTENSITY, isTexturedPbrTextureSlotShaderActive, DEFAULT_TEXTURED_PBR_SHADER_NAME, applyTextureBudget } from "@aura3d/rendering";
|
|
2
2
|
import { Bounds3 as SceneBounds3, multiplyMat4 } from "@aura3d/scene";
|
|
3
|
-
import { parseGLTFRuntimeMaterialKey } from "./GLTFLoader.js";
|
|
4
|
-
import {
|
|
3
|
+
import { gltfRuntimeMaterialKey, parseGLTFRuntimeMaterialKey } from "./GLTFLoader.js";
|
|
4
|
+
import { generateMikkTSpaceTangents, mikkTSpaceAvailable } from "./MikkTSpaceTangents.js";
|
|
5
|
+
import { decodeImageInBrowser } from "./gltf/ImageDecode.js";
|
|
6
|
+
/** Texture slots read sRGB-authored data; every other slot decodes linear. */
|
|
7
|
+
const IMAGE_COLORSPACE_SLOTS = [
|
|
8
|
+
{ slot: "baseColor", colorSpace: "srgb", texture: (material) => material.baseColorTexture },
|
|
9
|
+
{ slot: "emissive", colorSpace: "srgb", texture: (material) => material.emissiveTexture },
|
|
10
|
+
{ slot: "metallicRoughness", colorSpace: "linear", texture: (material) => material.metallicRoughnessTexture },
|
|
11
|
+
{ slot: "normal", colorSpace: "linear", texture: (material) => material.normalTexture },
|
|
12
|
+
{ slot: "occlusion", colorSpace: "linear", texture: (material) => material.occlusionTexture },
|
|
13
|
+
{ slot: "clearcoat", colorSpace: "linear", texture: (material) => material.clearcoat?.texture },
|
|
14
|
+
{ slot: "clearcoatRoughness", colorSpace: "linear", texture: (material) => material.clearcoat?.roughnessTexture },
|
|
15
|
+
{ slot: "clearcoatNormal", colorSpace: "linear", texture: (material) => material.clearcoat?.normalTexture },
|
|
16
|
+
{ slot: "sheenColor", colorSpace: "srgb", texture: (material) => material.sheen?.colorTexture },
|
|
17
|
+
{ slot: "sheenRoughness", colorSpace: "linear", texture: (material) => material.sheen?.roughnessTexture },
|
|
18
|
+
{ slot: "transmission", colorSpace: "linear", texture: (material) => material.transmission?.texture },
|
|
19
|
+
{ slot: "diffuseTransmission", colorSpace: "linear", texture: (material) => material.diffuseTransmission?.texture },
|
|
20
|
+
{ slot: "diffuseTransmissionColor", colorSpace: "srgb", texture: (material) => material.diffuseTransmission?.colorTexture },
|
|
21
|
+
{ slot: "volumeThickness", colorSpace: "linear", texture: (material) => material.volume?.thicknessTexture },
|
|
22
|
+
{ slot: "specular", colorSpace: "linear", texture: (material) => material.specular?.texture },
|
|
23
|
+
{ slot: "specularColor", colorSpace: "srgb", texture: (material) => material.specular?.colorTexture },
|
|
24
|
+
{ slot: "anisotropy", colorSpace: "linear", texture: (material) => material.anisotropy?.texture },
|
|
25
|
+
{ slot: "iridescence", colorSpace: "linear", texture: (material) => material.iridescence?.texture },
|
|
26
|
+
{ slot: "iridescenceThickness", colorSpace: "linear", texture: (material) => material.iridescence?.thicknessTexture },
|
|
27
|
+
{ slot: "sgDiffuse", colorSpace: "srgb", texture: (material) => material.pbrSpecularGlossiness?.diffuseTexture },
|
|
28
|
+
{ slot: "sgSpecularGlossiness", colorSpace: "linear", texture: (material) => material.pbrSpecularGlossiness?.specularGlossinessTexture }
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* PRD-04 P5-1: per-image colour-space intent derived from material slot usage, for the C-16
|
|
32
|
+
* decode path (a KTX2/Basis transcode target must be chosen before any slot samples the
|
|
33
|
+
* image). An image used both sRGB and linear records a `colorspace-conflict` in
|
|
34
|
+
* `conflicts`; its intent resolves `"linear"` so no gamma curve is baked into the decoded
|
|
35
|
+
* payload — the per-slot usage space still applies at the `Texture` level.
|
|
36
|
+
*/
|
|
37
|
+
export function imageColorSpaceIntent(asset) {
|
|
38
|
+
const usages = new Map();
|
|
39
|
+
for (const material of asset.materials) {
|
|
40
|
+
for (const slot of IMAGE_COLORSPACE_SLOTS) {
|
|
41
|
+
const info = slot.texture(material);
|
|
42
|
+
if (!info)
|
|
43
|
+
continue;
|
|
44
|
+
const usage = usages.get(info.image) ?? { srgb: [], linear: [] };
|
|
45
|
+
(slot.colorSpace === "srgb" ? usage.srgb : usage.linear).push(`${material.name}:${slot.slot}`);
|
|
46
|
+
usages.set(info.image, usage);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const intent = new Map();
|
|
50
|
+
const conflicts = [];
|
|
51
|
+
for (const [image, usage] of usages) {
|
|
52
|
+
const conflict = usage.srgb.length > 0 && usage.linear.length > 0;
|
|
53
|
+
intent.set(image, usage.linear.length > 0 || !conflict && usage.srgb.length === 0 ? "linear" : "srgb");
|
|
54
|
+
if (conflict)
|
|
55
|
+
conflicts.push({ image, srgb: usage.srgb.join(","), linear: usage.linear.join(",") });
|
|
56
|
+
}
|
|
57
|
+
return { intent, conflicts };
|
|
58
|
+
}
|
|
5
59
|
const GLTF_DIAGNOSTIC_TEXTURE_BINDINGS = [
|
|
6
60
|
["baseColor", "u_baseColorTexture", "u_baseColorTextureEnabled"],
|
|
7
61
|
["normal", "u_normalTexture", "u_normalTextureEnabled"],
|
|
@@ -119,6 +173,24 @@ export async function createGLTFRenderResources(asset, options = {}) {
|
|
|
119
173
|
...(options.sceneIndex !== undefined ? { sceneIndex: options.sceneIndex } : {}),
|
|
120
174
|
...(options.sceneName !== undefined ? { sceneName: options.sceneName } : {})
|
|
121
175
|
});
|
|
176
|
+
// P5-1: per-image colour-space intent, resolved once from material slot usage and passed
|
|
177
|
+
// into the C-16 decode options (`ktx2BasisTranscoderOptions.colorSpace`) so a
|
|
178
|
+
// once-per-image transcode knows the intended space. Conflicts become `loadIssues`.
|
|
179
|
+
const colorSpaceIntent = imageColorSpaceIntent(asset);
|
|
180
|
+
const loadIssues = colorSpaceIntent.conflicts.map((conflict) => ({
|
|
181
|
+
code: "colorspace-conflict",
|
|
182
|
+
detail: `glTF image ${conflict.image} is used as sRGB (${conflict.srgb}) and linear (${conflict.linear}); decoding as linear.`
|
|
183
|
+
}));
|
|
184
|
+
const decodeOptionsForImage = (imageIndex) => options.materialsR185 === true
|
|
185
|
+
? {
|
|
186
|
+
...options,
|
|
187
|
+
ktx2BasisTranscoderOptions: {
|
|
188
|
+
...options.ktx2BasisTranscoderOptions,
|
|
189
|
+
// Flag-on only: pre-intent decode options stay byte-identical off-flag.
|
|
190
|
+
colorSpace: colorSpaceIntent.intent.get(imageIndex) ?? "linear"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
: options;
|
|
122
194
|
const getTexture = async (info, colorSpace) => {
|
|
123
195
|
const textureAsset = asset.textures[info.texture];
|
|
124
196
|
const image = asset.images[info.image];
|
|
@@ -132,10 +204,10 @@ export async function createGLTFRenderResources(asset, options = {}) {
|
|
|
132
204
|
setTextureLibraryEntry(textureLibrary, textureAsset.name, colorSpace, texture);
|
|
133
205
|
return texture;
|
|
134
206
|
}
|
|
135
|
-
const decoder = options.imageDecoder ?? ((sourceImage, imageIndex, sourceAsset) => decodeImageInBrowser(sourceImage, imageIndex, sourceAsset,
|
|
207
|
+
const decoder = options.imageDecoder ?? ((sourceImage, imageIndex, sourceAsset) => decodeImageInBrowser(sourceImage, imageIndex, sourceAsset, decodeOptionsForImage(imageIndex)));
|
|
136
208
|
const texturePromise = (async () => {
|
|
137
209
|
const decoded = await decoder(image, info.image, asset);
|
|
138
|
-
|
|
210
|
+
const texture = new Texture({
|
|
139
211
|
width: decoded.width,
|
|
140
212
|
height: decoded.height,
|
|
141
213
|
...(decoded.format ? { format: decoded.format } : {}),
|
|
@@ -145,6 +217,16 @@ export async function createGLTFRenderResources(asset, options = {}) {
|
|
|
145
217
|
...(decoded.fallbackData ? { fallbackData: decoded.fallbackData } : {}),
|
|
146
218
|
...(decoded.fallbackMipLevels ? { fallbackMipLevels: decoded.fallbackMipLevels } : {})
|
|
147
219
|
});
|
|
220
|
+
// R16/P2-11: the C-27 policy arrives as `textureBudget`/`maxTextureSize` on
|
|
221
|
+
// GLTFRenderResourceOptions (forwarded by the actor/pipeline). No device exists at
|
|
222
|
+
// this layer — the shared ledger accounts these uploads.
|
|
223
|
+
if (options.textureBudget !== undefined || options.maxTextureSize !== undefined) {
|
|
224
|
+
return applyTextureBudget(texture, {
|
|
225
|
+
...(options.textureBudget !== undefined ? { textureBudgetBytes: options.textureBudget } : {}),
|
|
226
|
+
...(options.maxTextureSize !== undefined ? { maxTextureSize: options.maxTextureSize } : {})
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
return texture;
|
|
148
230
|
})();
|
|
149
231
|
textureByImage.set(cacheKey, texturePromise);
|
|
150
232
|
try {
|
|
@@ -166,9 +248,37 @@ export async function createGLTFRenderResources(asset, options = {}) {
|
|
|
166
248
|
return fallback;
|
|
167
249
|
}
|
|
168
250
|
};
|
|
251
|
+
// When deduplication is enabled, materials with identical definitions share one runtime instance
|
|
252
|
+
// so renderer static batching (which keys on material identity) can collapse them. Declared before
|
|
253
|
+
// the try block so `setMaterialVariant` (R11) can rebuild missing contract variants on rebind.
|
|
254
|
+
const sharedMaterials = options.deduplicateIdenticalMaterials
|
|
255
|
+
? new Map()
|
|
256
|
+
: null;
|
|
257
|
+
const buildMaterial = (material, contract) => {
|
|
258
|
+
if (!sharedMaterials) {
|
|
259
|
+
return createMaterial(asset, material, getTexture, contract, options.materialRenderStateOverrides, options.materialsR185, { materialsTransmission: options.materialsTransmission, transmission: options.transmission });
|
|
260
|
+
}
|
|
261
|
+
// The contract participates in the key: the same glTF material under two different runtime
|
|
262
|
+
// contracts (skinned vs instanced, for example) must not collapse into one instance.
|
|
263
|
+
const key = identicalMaterialKey(material, contract);
|
|
264
|
+
const existing = sharedMaterials.get(key);
|
|
265
|
+
if (existing)
|
|
266
|
+
return existing;
|
|
267
|
+
const created = createMaterial(asset, material, getTexture, contract, options.materialRenderStateOverrides, options.materialsR185, { materialsTransmission: options.materialsTransmission, transmission: options.transmission });
|
|
268
|
+
sharedMaterials.set(key, created);
|
|
269
|
+
return created;
|
|
270
|
+
};
|
|
169
271
|
try {
|
|
272
|
+
// P5-4: under `A3D_QR_MATERIALS` prefer MikkTSpace-generated tangents (r185's frame) over
|
|
273
|
+
// the legacy triangle-average generator, unless `tangents === "legacy"` or the MikkTSpace
|
|
274
|
+
// module is not registered. `tangent-derivative-fallback` is reported inside createGeometry
|
|
275
|
+
// only when a tangent-needing program lands on the derivative frame.
|
|
276
|
+
const tangentGeneration = {
|
|
277
|
+
useMikkTSpace: options.materialsR185 === true && options.tangents !== "legacy" && mikkTSpaceAvailable(),
|
|
278
|
+
mikkTSpaceWanted: options.materialsR185 === true && options.tangents !== "legacy"
|
|
279
|
+
};
|
|
170
280
|
for (const mesh of asset.meshes) {
|
|
171
|
-
geometryLibrary.set(mesh.name, createGeometry(mesh, materialForMesh(asset, mesh, options.materialVariant)));
|
|
281
|
+
geometryLibrary.set(mesh.name, await createGeometry(mesh, materialForMesh(asset, mesh, options.materialVariant), tangentGeneration, loadIssues));
|
|
172
282
|
if (mesh.morphTargets.length > 0) {
|
|
173
283
|
morphTargetLibrary.set(mesh.name, mesh.morphTargets.map((target) => ({
|
|
174
284
|
...(target.name ? { name: target.name } : {}),
|
|
@@ -191,25 +301,6 @@ export async function createGLTFRenderResources(asset, options = {}) {
|
|
|
191
301
|
});
|
|
192
302
|
}
|
|
193
303
|
const materialTasks = [];
|
|
194
|
-
// When deduplication is enabled, materials with identical definitions share one runtime instance
|
|
195
|
-
// so renderer static batching (which keys on material identity) can collapse them.
|
|
196
|
-
const sharedMaterials = options.deduplicateIdenticalMaterials
|
|
197
|
-
? new Map()
|
|
198
|
-
: null;
|
|
199
|
-
const buildMaterial = (material, contract) => {
|
|
200
|
-
if (!sharedMaterials) {
|
|
201
|
-
return createMaterial(asset, material, getTexture, contract, options.materialRenderStateOverrides);
|
|
202
|
-
}
|
|
203
|
-
// The contract participates in the key: the same glTF material under two different runtime
|
|
204
|
-
// contracts (skinned vs instanced, for example) must not collapse into one instance.
|
|
205
|
-
const key = identicalMaterialKey(material, contract);
|
|
206
|
-
const existing = sharedMaterials.get(key);
|
|
207
|
-
if (existing)
|
|
208
|
-
return existing;
|
|
209
|
-
const created = createMaterial(asset, material, getTexture, contract, options.materialRenderStateOverrides);
|
|
210
|
-
sharedMaterials.set(key, created);
|
|
211
|
-
return created;
|
|
212
|
-
};
|
|
213
304
|
for (const material of asset.materials) {
|
|
214
305
|
const runtimeKeys = [...runtimeMaterialContracts.entries()].filter(([, runtime]) => runtime.material === material.name);
|
|
215
306
|
if (runtimeKeys.length === 0) {
|
|
@@ -228,11 +319,11 @@ export async function createGLTFRenderResources(asset, options = {}) {
|
|
|
228
319
|
for (const mesh of asset.meshes) {
|
|
229
320
|
for (const [key, runtime] of runtimeMaterialContracts) {
|
|
230
321
|
if (runtime.material === mesh.material && !materialLibrary.has(key)) {
|
|
231
|
-
materialLibrary.set(key, createDefaultGLTFMaterial(mesh, runtime.contract));
|
|
322
|
+
materialLibrary.set(key, createDefaultGLTFMaterial(mesh, runtime.contract, options.materialsR185));
|
|
232
323
|
}
|
|
233
324
|
}
|
|
234
325
|
if (!materialLibrary.has(mesh.material)) {
|
|
235
|
-
materialLibrary.set(mesh.material, createDefaultGLTFMaterial(mesh));
|
|
326
|
+
materialLibrary.set(mesh.material, createDefaultGLTFMaterial(mesh, {}, options.materialsR185));
|
|
236
327
|
}
|
|
237
328
|
}
|
|
238
329
|
}
|
|
@@ -249,6 +340,7 @@ export async function createGLTFRenderResources(asset, options = {}) {
|
|
|
249
340
|
materialLibrary,
|
|
250
341
|
renderableBindings,
|
|
251
342
|
materialFidelityDiagnostics,
|
|
343
|
+
loadIssues,
|
|
252
344
|
morphTargetLibrary,
|
|
253
345
|
textureLibrary,
|
|
254
346
|
bounds,
|
|
@@ -287,9 +379,55 @@ export async function createGLTFRenderResources(asset, options = {}) {
|
|
|
287
379
|
bounds
|
|
288
380
|
};
|
|
289
381
|
},
|
|
382
|
+
setMaterialVariant: (variant) => applyGLTFMaterialVariant({ asset, scene, materialLibrary, renderableBindings, buildMaterial, variant }),
|
|
290
383
|
dispose: () => disposeGLTFRenderResourceMaps(geometryLibrary, textureLibrary)
|
|
291
384
|
};
|
|
292
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* PRD-04 P2/R11: KHR_materials_variants rebinding without a pipeline rebuild — the actor's variant
|
|
388
|
+
* switch remaps each renderable's material key. Meshes lacking a mapping for the active variant
|
|
389
|
+
* keep their authored material per the glTF spec; `null` restores the authored mapping.
|
|
390
|
+
*/
|
|
391
|
+
async function applyGLTFMaterialVariant(options) {
|
|
392
|
+
const { asset, scene, materialLibrary, renderableBindings, buildMaterial, variant } = options;
|
|
393
|
+
if (variant !== null
|
|
394
|
+
&& !renderableBindings.some((binding) => binding.materialVariants.some((entry) => entry.variant === variant))) {
|
|
395
|
+
return "variant-unknown";
|
|
396
|
+
}
|
|
397
|
+
const meshByGeometry = new Map(asset.meshes.map((mesh) => [mesh.name, mesh]));
|
|
398
|
+
const tasks = [];
|
|
399
|
+
scene.collectRenderables().forEach(({ renderable }, index) => {
|
|
400
|
+
const binding = renderableBindings[index];
|
|
401
|
+
const mesh = meshByGeometry.get(renderable.geometry);
|
|
402
|
+
if (!binding || !mesh)
|
|
403
|
+
return;
|
|
404
|
+
const parsed = parseGLTFRuntimeMaterialKey(renderable.material);
|
|
405
|
+
const contract = {
|
|
406
|
+
...parsed.contract,
|
|
407
|
+
...(renderable.skinning ? { skinned: true } : {}),
|
|
408
|
+
...(renderable.instanceTransforms ? { instanced: true } : {})
|
|
409
|
+
};
|
|
410
|
+
const mapping = variant === null
|
|
411
|
+
? undefined
|
|
412
|
+
: mesh.materialVariants.find((entry) => entry.variant === variant);
|
|
413
|
+
const targetName = mapping?.material ?? binding.sourceMaterialName;
|
|
414
|
+
const key = gltfRuntimeMaterialKey(targetName, contract);
|
|
415
|
+
if (key === renderable.material)
|
|
416
|
+
return;
|
|
417
|
+
if (!materialLibrary.has(key)) {
|
|
418
|
+
const materialDef = asset.materials.find((entry) => entry.name === targetName);
|
|
419
|
+
tasks.push((materialDef
|
|
420
|
+
? buildMaterial(materialDef, contract)
|
|
421
|
+
: Promise.resolve(createDefaultGLTFMaterial({ ...mesh, material: targetName }, contract))).then((runtimeMaterial) => {
|
|
422
|
+
materialLibrary.set(key, runtimeMaterial);
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
// Renderable.material is readonly at the type level; the rebind mutates the key in place.
|
|
426
|
+
renderable.material = key;
|
|
427
|
+
});
|
|
428
|
+
await Promise.all(tasks);
|
|
429
|
+
return "applied";
|
|
430
|
+
}
|
|
293
431
|
function createGLTFRenderableBindings(asset, scene) {
|
|
294
432
|
const meshByGeometry = new Map(asset.meshes.map((mesh) => [mesh.name, mesh]));
|
|
295
433
|
return scene.collectRenderables().map(({ node, renderable }) => {
|
|
@@ -814,7 +952,7 @@ function materialForMesh(asset, mesh, materialVariant) {
|
|
|
814
952
|
: mesh.materialIndex;
|
|
815
953
|
return materialIndex === undefined ? undefined : asset.materials[materialIndex];
|
|
816
954
|
}
|
|
817
|
-
function createGeometry(mesh, material) {
|
|
955
|
+
async function createGeometry(mesh, material, tangentGeneration, loadIssues) {
|
|
818
956
|
const usedTexCoordSets = usedRenderTexCoordSets(material);
|
|
819
957
|
const texcoords = selectRenderTexcoords(mesh, material, 0);
|
|
820
958
|
const texcoords1 = usedTexCoordSets.has(1) ? selectRenderTexcoords(mesh, material, 1) : [];
|
|
@@ -829,8 +967,20 @@ function createGeometry(mesh, material) {
|
|
|
829
967
|
? generateMeshNormals(mesh.positions, mesh.indices, mesh.topology)
|
|
830
968
|
: mesh.normals;
|
|
831
969
|
const renderTangents = needsTangent && mesh.tangents.length === 0
|
|
832
|
-
?
|
|
970
|
+
? await generateRenderTangents(mesh, texcoords, renderNormals, tangentGeneration)
|
|
833
971
|
: mesh.tangents;
|
|
972
|
+
/*
|
|
973
|
+
* P5-4: `tangent-derivative-fallback` is reported only when a generated program will
|
|
974
|
+
* actually use the derivative frame — the material needs a tangent frame but the produced
|
|
975
|
+
* vertex format carries no tangent attribute (e.g. a normal-mapped mesh without UVs).
|
|
976
|
+
* A successful MikkTSpace->legacy fallback still produces the generated-attribute path.
|
|
977
|
+
*/
|
|
978
|
+
if (tangentGeneration.mikkTSpaceWanted && materialNeedsTangents(material) && !format.hasAttribute("tangent")) {
|
|
979
|
+
loadIssues.push({
|
|
980
|
+
code: "tangent-derivative-fallback",
|
|
981
|
+
detail: `glTF mesh ${mesh.name}: material needs a tangent frame but no tangent attribute was produced; the generated program uses the derivative frame.`
|
|
982
|
+
});
|
|
983
|
+
}
|
|
834
984
|
const vertices = new VertexBuffer(format, mesh.positions.length);
|
|
835
985
|
for (let index = 0; index < mesh.positions.length; index += 1) {
|
|
836
986
|
vertices.setAttribute(index, "position", mesh.positions[index]);
|
|
@@ -879,6 +1029,54 @@ function generateMeshNormals(positions, indices, topology) {
|
|
|
879
1029
|
}
|
|
880
1030
|
return normals.map((normal) => normalize3(normal));
|
|
881
1031
|
}
|
|
1032
|
+
/**
|
|
1033
|
+
* P5-4: tangent generation for a mesh with no authored TANGENT. MikkTSpace when the lane flag
|
|
1034
|
+
* asked for it and the module is registered; otherwise the legacy triangle-average generator.
|
|
1035
|
+
* The `tangent-derivative-fallback` load issue is reported by the caller — only when the
|
|
1036
|
+
* produced vertex format leaves a tangent-needing program on the derivative frame.
|
|
1037
|
+
*/
|
|
1038
|
+
async function generateRenderTangents(mesh, texcoords, renderNormals, tangentGeneration) {
|
|
1039
|
+
if (tangentGeneration.useMikkTSpace) {
|
|
1040
|
+
try {
|
|
1041
|
+
const generated = await generateMikkTSpaceTangents({
|
|
1042
|
+
positions: flatten3(mesh.positions),
|
|
1043
|
+
normals: flatten3(renderNormals),
|
|
1044
|
+
uvs: flatten2(texcoords),
|
|
1045
|
+
...(mesh.indices && mesh.indices.length > 0 ? { indices: new Uint32Array(mesh.indices) } : {})
|
|
1046
|
+
});
|
|
1047
|
+
// The MikkTSpace helper unwelds indexed input (output is per-index, xyzw); scatter back
|
|
1048
|
+
// per vertex — last write wins, matching how indexed TANGENT attributes behave.
|
|
1049
|
+
const count = mesh.positions.length;
|
|
1050
|
+
const rows = Array.from({ length: count }, () => [0, 0, 0, 1]);
|
|
1051
|
+
if (mesh.indices && mesh.indices.length > 0) {
|
|
1052
|
+
mesh.indices.forEach((vertexIndex, i) => {
|
|
1053
|
+
rows[vertexIndex] = [generated[i * 4] ?? 0, generated[i * 4 + 1] ?? 0, generated[i * 4 + 2] ?? 0, generated[i * 4 + 3] ?? 1];
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
else {
|
|
1057
|
+
for (let i = 0; i < count; i += 1) {
|
|
1058
|
+
rows[i] = [generated[i * 4] ?? 0, generated[i * 4 + 1] ?? 0, generated[i * 4 + 2] ?? 0, generated[i * 4 + 3] ?? 1];
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return rows;
|
|
1062
|
+
}
|
|
1063
|
+
catch {
|
|
1064
|
+
// MikkTSpace failure falls back to the legacy generator silently — the program still
|
|
1065
|
+
// runs the generated-attribute path (P5-4 reports the issue only for the derivative path).
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return generateMeshTangents(mesh.positions, texcoords, mesh.indices, mesh.topology, renderNormals);
|
|
1069
|
+
}
|
|
1070
|
+
function flatten3(rows) {
|
|
1071
|
+
const flat = new Float32Array(rows.length * 3);
|
|
1072
|
+
rows.forEach(([x, y, z], i) => { flat[i * 3] = x; flat[i * 3 + 1] = y; flat[i * 3 + 2] = z; });
|
|
1073
|
+
return flat;
|
|
1074
|
+
}
|
|
1075
|
+
function flatten2(rows) {
|
|
1076
|
+
const flat = new Float32Array(rows.length * 2);
|
|
1077
|
+
rows.forEach(([x, y], i) => { flat[i * 2] = x; flat[i * 2 + 1] = y; });
|
|
1078
|
+
return flat;
|
|
1079
|
+
}
|
|
882
1080
|
function generateMeshTangents(positions, texcoords, indices, topology, normals) {
|
|
883
1081
|
const tangents = Array.from({ length: positions.length }, () => [0, 0, 0]);
|
|
884
1082
|
const bitangents = Array.from({ length: positions.length }, () => [0, 0, 0]);
|
|
@@ -1075,8 +1273,9 @@ function stableJson(value) {
|
|
|
1075
1273
|
return nested;
|
|
1076
1274
|
});
|
|
1077
1275
|
}
|
|
1078
|
-
async function createMaterial(asset, material, getTexture, options = {}, renderStateOverrides = []) {
|
|
1079
|
-
const
|
|
1276
|
+
async function createMaterial(asset, material, getTexture, options = {}, renderStateOverrides = [], materialsR185 = false, qrTransmission) {
|
|
1277
|
+
const qr = { ...qrTransmission, materialsR185 };
|
|
1278
|
+
const renderState = renderStateForGLTFMaterial(material, renderStateOverrides, materialsR185, qrTransmission);
|
|
1080
1279
|
if (options.skinned && !material.unlit) {
|
|
1081
1280
|
const [baseColorTexture, normalTexture, metallicRoughnessTexture, occlusionTexture, emissiveTexture, clearcoatTexture, clearcoatNormalTexture, sheenColorTexture, sheenRoughnessTexture, transmissionTexture, iridescenceTexture, iridescenceThicknessTexture, anisotropyTexture, volumeThicknessTexture] = await Promise.all([
|
|
1082
1281
|
material.baseColorTexture
|
|
@@ -1149,10 +1348,10 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
|
|
|
1149
1348
|
iridescenceThicknessTexture,
|
|
1150
1349
|
anisotropyTexture,
|
|
1151
1350
|
volumeThicknessTexture,
|
|
1152
|
-
...pbrExtensionScalarOptions(material)
|
|
1351
|
+
...pbrExtensionScalarOptions(material, qr)
|
|
1153
1352
|
});
|
|
1154
1353
|
applyAlphaCutoff(runtimeMaterial, material);
|
|
1155
|
-
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
|
|
1354
|
+
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
|
|
1156
1355
|
return runtimeMaterial;
|
|
1157
1356
|
}
|
|
1158
1357
|
if (options.instanced && material.unlit && !material.baseColorTexture) {
|
|
@@ -1162,7 +1361,7 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
|
|
|
1162
1361
|
renderState
|
|
1163
1362
|
});
|
|
1164
1363
|
applyAlphaCutoff(runtimeMaterial, material);
|
|
1165
|
-
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
|
|
1364
|
+
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
|
|
1166
1365
|
return runtimeMaterial;
|
|
1167
1366
|
}
|
|
1168
1367
|
if (material.unlit) {
|
|
@@ -1177,7 +1376,7 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
|
|
|
1177
1376
|
renderState
|
|
1178
1377
|
});
|
|
1179
1378
|
applyAlphaCutoff(runtimeMaterial, material);
|
|
1180
|
-
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
|
|
1379
|
+
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
|
|
1181
1380
|
return runtimeMaterial;
|
|
1182
1381
|
}
|
|
1183
1382
|
const runtimeMaterial = new UnlitMaterial({
|
|
@@ -1186,7 +1385,7 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
|
|
|
1186
1385
|
renderState
|
|
1187
1386
|
});
|
|
1188
1387
|
applyAlphaCutoff(runtimeMaterial, material);
|
|
1189
|
-
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
|
|
1388
|
+
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
|
|
1190
1389
|
return runtimeMaterial;
|
|
1191
1390
|
}
|
|
1192
1391
|
if (options.instanced && !requiresTexturedPBRMaterial(material) && !material.baseColorTexture) {
|
|
@@ -1200,7 +1399,7 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
|
|
|
1200
1399
|
emissiveStrength: material.emissiveStrength
|
|
1201
1400
|
});
|
|
1202
1401
|
applyAlphaCutoff(runtimeMaterial, material);
|
|
1203
|
-
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
|
|
1402
|
+
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
|
|
1204
1403
|
return runtimeMaterial;
|
|
1205
1404
|
}
|
|
1206
1405
|
if (requiresTexturedPBRMaterial(material)) {
|
|
@@ -1213,13 +1412,13 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
|
|
|
1213
1412
|
const runtimeMaterial = new TexturedPBRMaterial({
|
|
1214
1413
|
name: material.name,
|
|
1215
1414
|
renderState,
|
|
1216
|
-
baseColor: renderPbrBaseColorFactor(material),
|
|
1415
|
+
baseColor: renderPbrBaseColorFactor(material, qr),
|
|
1217
1416
|
metallic: material.metallicFactor,
|
|
1218
|
-
roughness: renderPbrRoughnessFactor(material),
|
|
1417
|
+
roughness: renderPbrRoughnessFactor(material, qr),
|
|
1219
1418
|
emissiveColor: material.emissiveFactor,
|
|
1220
1419
|
emissiveStrength: material.emissiveStrength,
|
|
1221
1420
|
textureTexCoords: pbrTextureTexCoords(material),
|
|
1222
|
-
...pbrExtensionScalarOptions(material),
|
|
1421
|
+
...pbrExtensionScalarOptions(material, qr),
|
|
1223
1422
|
...extensionTextureOptions,
|
|
1224
1423
|
baseColorTexture,
|
|
1225
1424
|
baseColorSampler: createSampler(material.baseColorTexture ? asset.textures[material.baseColorTexture.texture] : undefined),
|
|
@@ -1240,7 +1439,7 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
|
|
|
1240
1439
|
emissiveTextureTransform: material.emissiveTexture?.transform
|
|
1241
1440
|
});
|
|
1242
1441
|
applyAlphaCutoff(runtimeMaterial, material);
|
|
1243
|
-
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
|
|
1442
|
+
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
|
|
1244
1443
|
return runtimeMaterial;
|
|
1245
1444
|
}
|
|
1246
1445
|
if (material.baseColorTexture) {
|
|
@@ -1254,27 +1453,37 @@ async function createMaterial(asset, material, getTexture, options = {}, renderS
|
|
|
1254
1453
|
renderState
|
|
1255
1454
|
});
|
|
1256
1455
|
applyAlphaCutoff(runtimeMaterial, material);
|
|
1257
|
-
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
|
|
1456
|
+
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
|
|
1258
1457
|
return runtimeMaterial;
|
|
1259
1458
|
}
|
|
1260
1459
|
const runtimeMaterial = new PBRMaterial({
|
|
1261
1460
|
name: material.name,
|
|
1262
1461
|
renderState,
|
|
1263
|
-
baseColor: renderPbrBaseColorFactor(material),
|
|
1462
|
+
baseColor: renderPbrBaseColorFactor(material, qr),
|
|
1264
1463
|
metallic: material.metallicFactor,
|
|
1265
|
-
roughness: renderPbrRoughnessFactor(material),
|
|
1464
|
+
roughness: renderPbrRoughnessFactor(material, qr),
|
|
1266
1465
|
emissiveColor: material.emissiveFactor,
|
|
1267
1466
|
emissiveStrength: material.emissiveStrength,
|
|
1268
|
-
...pbrExtensionScalarOptions(material)
|
|
1467
|
+
...pbrExtensionScalarOptions(material, qr)
|
|
1269
1468
|
});
|
|
1270
1469
|
applyAlphaCutoff(runtimeMaterial, material);
|
|
1271
|
-
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture);
|
|
1470
|
+
await applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr);
|
|
1272
1471
|
return runtimeMaterial;
|
|
1273
1472
|
}
|
|
1274
|
-
function createDefaultGLTFMaterial(mesh, options = {}) {
|
|
1473
|
+
function createDefaultGLTFMaterial(mesh, options = {}, materialsR185 = false) {
|
|
1275
1474
|
// Use a neutral light-gray base color instead of glTF-spec white ([1,1,1,1])
|
|
1276
1475
|
// so missing-material fallbacks do not blow out as bright white artifacts.
|
|
1277
|
-
|
|
1476
|
+
// PRD-04 P2-12/E23: flag-on uses the glTF-spec default ([1,1,1,1], roughness 1).
|
|
1477
|
+
// T0-18(a): metallic stays 0, not the spec's 1 — a fully metallic surface has
|
|
1478
|
+
// no diffuse response and renders near-black until a C-09 probe/IBL is bound;
|
|
1479
|
+
// restore spec metallic once environment lighting is guaranteed on this path.
|
|
1480
|
+
const defaults = materialsR185 ? {
|
|
1481
|
+
name: mesh.material,
|
|
1482
|
+
baseColor: [1, 1, 1, 1],
|
|
1483
|
+
metallic: 0,
|
|
1484
|
+
roughness: 1,
|
|
1485
|
+
environmentIntensity: DEFAULT_PBR_ENVIRONMENT_INTENSITY
|
|
1486
|
+
} : {
|
|
1278
1487
|
name: mesh.material,
|
|
1279
1488
|
baseColor: [0.76, 0.74, 0.72, 1],
|
|
1280
1489
|
metallic: 0,
|
|
@@ -1296,23 +1505,38 @@ function createDefaultGLTFMaterial(mesh, options = {}) {
|
|
|
1296
1505
|
* so we allow a small epsilon while still catching genuine fades.
|
|
1297
1506
|
*/
|
|
1298
1507
|
const BLEND_OPAQUE_ALPHA_THRESHOLD = 0.996;
|
|
1299
|
-
export function renderStateForGLTFMaterial(material, overrides = []) {
|
|
1300
|
-
const
|
|
1301
|
-
const
|
|
1508
|
+
export function renderStateForGLTFMaterial(material, overrides = [], materialsR185 = false, qrTransmission) {
|
|
1509
|
+
const qr = { ...qrTransmission, materialsR185 };
|
|
1510
|
+
const blend = requiresTransparentRenderState(material, qr);
|
|
1511
|
+
// PRD-04 P2-12/E23: the double-sided + metallic + clearcoat back-face cull is a fudge;
|
|
1512
|
+
// flag-on honours authored doubleSided.
|
|
1513
|
+
const cullBack = usesUnbackedScalarTransmission(material, qr)
|
|
1514
|
+
|| (!materialsR185 && usesOpaqueDoubleSidedClearcoatShell(material));
|
|
1515
|
+
// P5-5 (C-04): under the lane flag a MASK material requests alpha-to-coverage. The material
|
|
1516
|
+
// RenderState type predates C-04's `RenderCommandState.alphaToCoverage`, so the field rides
|
|
1517
|
+
// the state object at runtime (validateRenderState preserves unknown keys); the
|
|
1518
|
+
// `prd04.alphaToCoverage` feature reads it and only honours it when the tier has MSAA
|
|
1519
|
+
// samples. Flag-off omits the key entirely.
|
|
1302
1520
|
const baseState = {
|
|
1303
1521
|
cullMode: cullBack ? "back" : material.doubleSided ? "none" : "back",
|
|
1304
1522
|
blend,
|
|
1305
|
-
depthWrite: !blend
|
|
1523
|
+
depthWrite: !blend,
|
|
1524
|
+
...(materialsR185 && material.alphaMode === "MASK" ? { alphaToCoverage: true } : {})
|
|
1306
1525
|
};
|
|
1307
1526
|
const override = overrides.find((entry) => matchesTextOrPattern(material.name, entry.materialName));
|
|
1308
1527
|
return override ? { ...baseState, ...override.renderState } : baseState;
|
|
1309
1528
|
}
|
|
1310
|
-
function requiresTransparentRenderState(material) {
|
|
1529
|
+
function requiresTransparentRenderState(material, qr) {
|
|
1311
1530
|
if (material.alphaMode === "BLEND")
|
|
1312
1531
|
return !isEffectivelyOpaqueBlendMaterial(material);
|
|
1313
1532
|
if (material.alphaMode !== "OPAQUE")
|
|
1314
1533
|
return false;
|
|
1315
|
-
if (usesUnbackedScalarTransmission(material))
|
|
1534
|
+
if (usesUnbackedScalarTransmission(material, qr))
|
|
1535
|
+
return false;
|
|
1536
|
+
// E22: on the real path transmissive items ride the C-01 `transmission` phase
|
|
1537
|
+
// queue (blend must stay off — the sorter rejects blended transmission), not
|
|
1538
|
+
// the legacy transparent queue.
|
|
1539
|
+
if (qrTransmissionRealPath(qr))
|
|
1316
1540
|
return false;
|
|
1317
1541
|
return materialHasTransmissionOrVolume(material);
|
|
1318
1542
|
}
|
|
@@ -1356,12 +1580,31 @@ function materialHasTransmissionOrVolume(material) {
|
|
|
1356
1580
|
|| (material.volume?.thicknessFactor ?? 0) > 0.001
|
|
1357
1581
|
|| material.volume?.thicknessTexture !== undefined;
|
|
1358
1582
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1583
|
+
/**
|
|
1584
|
+
* E22 gate (PRD-04 P4-3): real transmission rendering exists — so the legacy
|
|
1585
|
+
* unbacked-scalar-transmission rewrite must not run — exactly when
|
|
1586
|
+
* `A3D_QR_MATERIALS` **and** `A3D_QR_MATERIALS_TRANSMISSION` are on **and**
|
|
1587
|
+
* `renderer.transmission === "env"` (the env-refraction path).
|
|
1588
|
+
* T0-18(b): `programCacheSlot.provided` is not evidence — it is true whenever
|
|
1589
|
+
* the prd01 barrel loaded, which skipped E22 even though the scene-copy path
|
|
1590
|
+
* did not draw. `"auto"` keeps the E22 rewrite until the transmission target
|
|
1591
|
+
* is bound end-to-end on generated programs (04-S10).
|
|
1592
|
+
*/
|
|
1593
|
+
function qrTransmissionRealPath(qr) {
|
|
1594
|
+
return qr?.materialsR185 === true
|
|
1595
|
+
&& qr?.materialsTransmission === true
|
|
1596
|
+
&& qr.transmission === "env";
|
|
1597
|
+
}
|
|
1598
|
+
function usesUnbackedScalarTransmission(material, qr) {
|
|
1599
|
+
const unbacked = material.alphaMode === "OPAQUE"
|
|
1361
1600
|
&& (material.transmission?.factor ?? 0) > 0.001
|
|
1362
1601
|
&& material.transmission?.texture === undefined
|
|
1363
1602
|
&& material.diffuseTransmission === undefined
|
|
1364
1603
|
&& material.volume === undefined;
|
|
1604
|
+
// E22 path-gated skip: real transmission handles the material; no rewrite.
|
|
1605
|
+
if (unbacked && qrTransmissionRealPath(qr))
|
|
1606
|
+
return false;
|
|
1607
|
+
return unbacked;
|
|
1365
1608
|
}
|
|
1366
1609
|
function usesOpaqueDoubleSidedClearcoatShell(material) {
|
|
1367
1610
|
return material.alphaMode === "OPAQUE"
|
|
@@ -1370,16 +1613,16 @@ function usesOpaqueDoubleSidedClearcoatShell(material) {
|
|
|
1370
1613
|
&& material.metallicFactor > 0.35
|
|
1371
1614
|
&& material.roughnessFactor <= 0.42;
|
|
1372
1615
|
}
|
|
1373
|
-
function renderPbrBaseColorFactor(material) {
|
|
1374
|
-
if (!usesUnbackedScalarTransmission(material))
|
|
1616
|
+
function renderPbrBaseColorFactor(material, qr) {
|
|
1617
|
+
if (!usesUnbackedScalarTransmission(material, qr))
|
|
1375
1618
|
return material.baseColorFactor;
|
|
1376
1619
|
const maxColor = Math.max(material.baseColorFactor[0], material.baseColorFactor[1], material.baseColorFactor[2]);
|
|
1377
1620
|
if (maxColor < 0.8)
|
|
1378
1621
|
return material.baseColorFactor;
|
|
1379
1622
|
return [0.028, 0.036, 0.044, material.baseColorFactor[3]];
|
|
1380
1623
|
}
|
|
1381
|
-
function renderPbrRoughnessFactor(material) {
|
|
1382
|
-
if (!usesUnbackedScalarTransmission(material))
|
|
1624
|
+
function renderPbrRoughnessFactor(material, qr) {
|
|
1625
|
+
if (!usesUnbackedScalarTransmission(material, qr))
|
|
1383
1626
|
return material.roughnessFactor;
|
|
1384
1627
|
return Math.max(material.roughnessFactor, 0.72);
|
|
1385
1628
|
}
|
|
@@ -1418,15 +1661,15 @@ function usesNearestSampler(texture) {
|
|
|
1418
1661
|
const sampler = createSampler(texture);
|
|
1419
1662
|
return sampler.magFilter === "nearest" || sampler.minFilter.startsWith("nearest");
|
|
1420
1663
|
}
|
|
1421
|
-
function pbrExtensionScalarOptions(material) {
|
|
1664
|
+
function pbrExtensionScalarOptions(material, qr) {
|
|
1422
1665
|
return {
|
|
1423
1666
|
...(material.clearcoat ? {
|
|
1424
1667
|
clearcoatFactor: material.clearcoat.factor,
|
|
1425
1668
|
clearcoatRoughnessFactor: renderClearcoatRoughnessFactor(material)
|
|
1426
1669
|
} : {}),
|
|
1427
1670
|
...(material.transmission ? {
|
|
1428
|
-
transmissionFactor: usesUnbackedScalarTransmission(material) ? 0 : material.transmission.factor,
|
|
1429
|
-
transmissionFallbackEnergy: renderTransmissionFallbackEnergy(material)
|
|
1671
|
+
transmissionFactor: usesUnbackedScalarTransmission(material, qr) ? 0 : material.transmission.factor,
|
|
1672
|
+
transmissionFallbackEnergy: renderTransmissionFallbackEnergy(material, qr)
|
|
1430
1673
|
} : {}),
|
|
1431
1674
|
...(material.diffuseTransmission ? {
|
|
1432
1675
|
diffuseTransmissionFactor: material.diffuseTransmission.factor,
|
|
@@ -1550,9 +1793,9 @@ async function pbrExtensionTextureOptions(asset, material, getTexture) {
|
|
|
1550
1793
|
} : {})
|
|
1551
1794
|
};
|
|
1552
1795
|
}
|
|
1553
|
-
function renderTransmissionFallbackEnergy(material) {
|
|
1796
|
+
function renderTransmissionFallbackEnergy(material, qr) {
|
|
1554
1797
|
const usesUnbackedCutoutTransmission = material.alphaMode === "MASK" && material.transmission?.texture === undefined && material.volume === undefined;
|
|
1555
|
-
return usesUnbackedCutoutTransmission || usesUnbackedScalarTransmission(material) ? 0 : 0.08;
|
|
1798
|
+
return usesUnbackedCutoutTransmission || usesUnbackedScalarTransmission(material, qr) ? 0 : 0.08;
|
|
1556
1799
|
}
|
|
1557
1800
|
function pbrTextureTexCoords(material) {
|
|
1558
1801
|
return {
|
|
@@ -1580,7 +1823,7 @@ function pbrTextureTexCoords(material) {
|
|
|
1580
1823
|
function renderTexCoord(texCoord) {
|
|
1581
1824
|
return texCoord === 1 ? 1 : 0;
|
|
1582
1825
|
}
|
|
1583
|
-
async function applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture) {
|
|
1826
|
+
async function applyPBRExtensionParameters(asset, runtimeMaterial, material, getTexture, qr) {
|
|
1584
1827
|
if (material.clearcoat) {
|
|
1585
1828
|
runtimeMaterial.setParameter("u_clearcoatFactor", material.clearcoat.factor);
|
|
1586
1829
|
runtimeMaterial.setParameter("u_clearcoatRoughnessFactor", renderClearcoatRoughnessFactor(material));
|
|
@@ -1596,7 +1839,7 @@ async function applyPBRExtensionParameters(asset, runtimeMaterial, material, get
|
|
|
1596
1839
|
}
|
|
1597
1840
|
}
|
|
1598
1841
|
if (material.transmission) {
|
|
1599
|
-
runtimeMaterial.setParameter("u_transmissionFactor", usesUnbackedScalarTransmission(material) ? 0 : material.transmission.factor);
|
|
1842
|
+
runtimeMaterial.setParameter("u_transmissionFactor", usesUnbackedScalarTransmission(material, qr) ? 0 : material.transmission.factor);
|
|
1600
1843
|
if (material.transmission.texture) {
|
|
1601
1844
|
await setTextureParameter(asset, runtimeMaterial, "u_transmissionTexture", material.transmission.texture, "linear", getTexture);
|
|
1602
1845
|
}
|
|
@@ -1758,61 +2001,9 @@ function createSampler(texture) {
|
|
|
1758
2001
|
};
|
|
1759
2002
|
return new Sampler(samplerDescriptor);
|
|
1760
2003
|
}
|
|
1761
|
-
async function decodeImageInBrowser(image, _imageIndex, asset, options = {}) {
|
|
1762
|
-
if (isKTX2BasisImage(image)) {
|
|
1763
|
-
const bytes = await readImageBytes(asset, image);
|
|
1764
|
-
return transcodeKTX2BasisTexture(bytes, {
|
|
1765
|
-
...options.ktx2BasisTranscoderOptions,
|
|
1766
|
-
targetFormat: options.ktx2BasisTargetFormat ?? options.ktx2BasisTranscoderOptions?.targetFormat
|
|
1767
|
-
});
|
|
1768
|
-
}
|
|
1769
|
-
if (typeof createImageBitmap === "function") {
|
|
1770
|
-
const blob = image.data
|
|
1771
|
-
? new Blob([image.data], { type: image.mimeType ?? "application/octet-stream" })
|
|
1772
|
-
: await fetchImageBlob(asset, image);
|
|
1773
|
-
const bitmap = await createImageBitmap(blob, {
|
|
1774
|
-
colorSpaceConversion: "none",
|
|
1775
|
-
premultiplyAlpha: "none"
|
|
1776
|
-
});
|
|
1777
|
-
return { width: bitmap.width, height: bitmap.height, source: bitmap, colorSpace: "srgb" };
|
|
1778
|
-
}
|
|
1779
|
-
const ImageCtor = globalThis.Image;
|
|
1780
|
-
if (!ImageCtor || !image.uri) {
|
|
1781
|
-
throw new Error("glTF image decoding requires createImageBitmap, HTMLImageElement, or a custom imageDecoder");
|
|
1782
|
-
}
|
|
1783
|
-
const imageElement = new ImageCtor();
|
|
1784
|
-
const url = resolveImageUrl(asset.url, image.uri);
|
|
1785
|
-
return new Promise((resolve, reject) => {
|
|
1786
|
-
imageElement.onload = () => resolve({ width: imageElement.width, height: imageElement.height, source: imageElement, colorSpace: "srgb" });
|
|
1787
|
-
imageElement.onerror = () => reject(new Error(`glTF image decode failed for ${url}`));
|
|
1788
|
-
imageElement.src = url;
|
|
1789
|
-
});
|
|
1790
|
-
}
|
|
1791
2004
|
function createSamplerForInfo(asset, info) {
|
|
1792
2005
|
return createSampler(asset.textures[info.texture]);
|
|
1793
2006
|
}
|
|
1794
|
-
async function readImageBytes(asset, image) {
|
|
1795
|
-
if (image.data)
|
|
1796
|
-
return image.data.slice(0);
|
|
1797
|
-
const blob = await fetchImageBlob(asset, image);
|
|
1798
|
-
return blob.arrayBuffer();
|
|
1799
|
-
}
|
|
1800
|
-
async function fetchImageBlob(asset, image) {
|
|
1801
|
-
if (!image.uri) {
|
|
1802
|
-
throw new Error("glTF image has no uri or embedded data");
|
|
1803
|
-
}
|
|
1804
|
-
if (typeof fetch !== "function") {
|
|
1805
|
-
throw new Error("glTF image fetch requires fetch or a custom imageDecoder");
|
|
1806
|
-
}
|
|
1807
|
-
const response = await fetch(resolveImageUrl(asset.url, image.uri));
|
|
1808
|
-
if (!response.ok) {
|
|
1809
|
-
throw new Error(`glTF image request failed with ${response.status}`);
|
|
1810
|
-
}
|
|
1811
|
-
return response.blob();
|
|
1812
|
-
}
|
|
1813
|
-
function isKTX2BasisImage(image) {
|
|
1814
|
-
return image.mimeType === "image/ktx2" || /\.ktx2(?:[?#]|$)/i.test(image.uri ?? "");
|
|
1815
|
-
}
|
|
1816
2007
|
function createFallbackTexture(label, colorSpace) {
|
|
1817
2008
|
return new Texture({
|
|
1818
2009
|
width: 1,
|
|
@@ -1828,38 +2019,4 @@ function warnMissingGLTFTexture(textureName, imageUri, reason) {
|
|
|
1828
2019
|
const source = imageUri ? ` (${imageUri})` : "";
|
|
1829
2020
|
console.warn(`glTF texture "${textureName}"${source} could not be loaded; falling back to baseColorFactor. Reason: ${reason}`);
|
|
1830
2021
|
}
|
|
1831
|
-
function resolveImageUrl(assetUrl, imageUri) {
|
|
1832
|
-
if (/^(?:data:|blob:|https?:|file:)/i.test(imageUri))
|
|
1833
|
-
return imageUri;
|
|
1834
|
-
if (assetUrl.startsWith("data:")) {
|
|
1835
|
-
throw new Error(`Relative glTF image uri ${imageUri} cannot be resolved from a data URL asset`);
|
|
1836
|
-
}
|
|
1837
|
-
return new URL(imageUri, resolveAbsoluteAssetBase(assetUrl)).toString();
|
|
1838
|
-
}
|
|
1839
|
-
/**
|
|
1840
|
-
* Derive an absolute base URL for resolving relative texture URIs.
|
|
1841
|
-
*
|
|
1842
|
-
* Catalog assets are often loaded from root-relative paths (e.g.
|
|
1843
|
-
* `/aura-assets/x.glb`). `new URL(uri, base)` requires `base` to be absolute, so
|
|
1844
|
-
* a bare relative `assetUrl` throws `Failed to construct 'URL': Invalid base URL`.
|
|
1845
|
-
* If the asset URL is already absolute we use it directly; otherwise we resolve it
|
|
1846
|
-
* against the document/origin base when running in a browser, falling back to a
|
|
1847
|
-
* synthetic `file:///` origin so resolution never throws in non-browser contexts.
|
|
1848
|
-
*/
|
|
1849
|
-
function resolveAbsoluteAssetBase(assetUrl) {
|
|
1850
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(assetUrl))
|
|
1851
|
-
return assetUrl;
|
|
1852
|
-
const documentBase = (typeof document !== "undefined" && document.baseURI) ||
|
|
1853
|
-
(typeof location !== "undefined" && location.href) ||
|
|
1854
|
-
undefined;
|
|
1855
|
-
if (documentBase) {
|
|
1856
|
-
try {
|
|
1857
|
-
return new URL(assetUrl, documentBase).toString();
|
|
1858
|
-
}
|
|
1859
|
-
catch {
|
|
1860
|
-
// fall through to synthetic base below
|
|
1861
|
-
}
|
|
1862
|
-
}
|
|
1863
|
-
return new URL(assetUrl, "file:///").toString();
|
|
1864
|
-
}
|
|
1865
2022
|
//# sourceMappingURL=GLTFRenderResources.js.map
|