1"use strict";(self.webpackChunkwebsite_docusaurus=self.webpackChunkwebsite_docusaurus||[]).push([["17644"],{3203(e,n,i){i.r(n),i.d(n,{metadata:()=>s,default:()=>p,frontMatter:()=>d,contentTitle:()=>l,toc:()=>o,assets:()=>c});var s=JSON.parse('{"id":"api-reference/gltf/gltf-native-extensions","title":"Native glTF Extensions","description":"glTF extensions should describe what an asset actually does, not merely which JSON properties","source":"@site/../docs/api-reference/gltf/gltf-native-extensions.md","sourceDirName":"api-reference/gltf","slug":"/api-reference/gltf/gltf-native-extensions","permalink":"/docs/api-reference/gltf/gltf-native-extensions","draft":false,"unlisted":false,"editUrl":"https://github.com/visgl/luma.gl/tree/master/docs/../docs/api-reference/gltf/gltf-native-extensions.md","tags":[],"version":"current","frontMatter":{},"sidebar":"defaultSidebar","previous":{"title":"glTF Materials, Textures, and Lighting","permalink":"/docs/api-reference/gltf/gltf-materials"},"next":{"title":"glTF Animation and Deformation","permalink":"/docs/api-reference/gltf/gltf-animation"}}'),t=i(62540),r=i(43023),a=i(91845);let d={},l="Native glTF Extensions",c={},o=[{value:"Load a standards-native scene",id:"load-a-standards-native-scene",level:2},{value:"Material variants",id:"material-variants",level:2},{value:"Mesh GPU instancing",id:"mesh-gpu-instancing",level:2},{value:"Recursive node visibility",id:"recursive-node-visibility",level:2},{value:"Animate visibility",id:"animate-visibility",level:3},{value:"Typed animation pointers",id:"typed-animation-pointers",level:2},{value:"Inspect discriminated channel types",id:"inspect-discriminated-channel-types",level:3},{value:"Strict extension capability checks",id:"strict-extension-capability-checks",level:2},{value:"Automatic animation and deformation integration",id:"automatic-animation-and-deformation-integration",level:2},{value:"Official fixture coverage",id:"official-fixture-coverage",level:2},{value:"Architecture and ownership",id:"architecture-and-ownership",level:2},{value:"Boundaries and current limitations",id:"boundaries-and-current-limitations",level:2}];function h(e){let n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"native-gltf-extensions",children:"Native glTF Extensions"})}),"\n",(0,t.jsx)(a.x,{active:"native-extensions"}),"\n",(0,t.jsxs)(n.p,{children:["glTF extensions should describe what an asset actually does, not merely which JSON properties\nsurvived loading. ",(0,t.jsx)(n.code,{children:"@luma.gl/gltf"})," connects authored material variants, GPU instance transforms,\nrecursive node visibility, typed animation pointers, and required-extension capabilities to the\nexisting luma.gl scenegraph and animation runtime."]}),"\n",(0,t.jsxs)(n.p,{children:["The implementation remains format-owned: ",(0,t.jsx)(n.code,{children:"@loaders.gl/gltf"})," reads and postprocesses the asset,\n",(0,t.jsx)(n.code,{children:"@luma.gl/gltf"})," resolves glTF extension semantics, and ",(0,t.jsx)(n.code,{children:"@luma.gl/engine"})," owns generic scenegraph,\nmodel, and animation behavior. There is no parallel loader, material system, or renderer."]}),"\n",(0,t.jsx)(n.h2,{id:"load-a-standards-native-scene",children:"Load a standards-native scene"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"import {load} from '@loaders.gl/core';\nimport {GLTFLoader, postProcessGLTF} from '@loaders.gl/gltf';\nimport {createScenegraphsFromGLTF} from '@luma.gl/gltf';\n\nconst asset = await load('/models/product.glb', GLTFLoader);\nconst gltf = postProcessGLTF(asset);\n\nconst scenegraphs = createScenegraphsFromGLTF(device, gltf, {\n strictExtensions: true,\n useByteColors: false\n});\n\nconsole.log(scenegraphs.variants.names);\nconsole.log(scenegraphs.extensionSupport);\nconsole.log(scenegraphs.animations.map(animation => animation.name));\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"postProcessGLTF()"})," is explicit: loaders.gl v4 does not support the historical\n",(0,t.jsx)(n.code,{children:"{gltf: {postProcess: true}}"})," loader option. Use ",(0,t.jsx)(n.code,{children:"useByteColors: false"})," when consuming punctual\nlight colors as glTF-authored linear RGB values. Strict extension handling is described below."]}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Extension"}),(0,t.jsx)(n.th,{children:"Runtime behavior"}),(0,t.jsx)(n.th,{children:"Scenegraph access"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"KHR_materials_variants"})}),(0,t.jsx)(n.td,{children:"Selects authored materials without replacing scene nodes."}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"scenegraphs.variants"})})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"EXT_mesh_gpu_instancing"})}),(0,t.jsx)(n.td,{children:"Draws authored mesh instances in a single instanced draw per primitive."}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"getGLTFNodeInstancing(gltf, node)"})})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"KHR_node_visibility"})}),(0,t.jsx)(n.td,{children:"Recursively hides descendants and attached punctual lights."}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"scenegraphs.gltfNodeIndexToNodeMap"})})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"KHR_animation_pointer"})}),(0,t.jsx)(n.td,{children:"Drives node, material, texture, camera, and punctual-light properties."}),(0,t.jsxs)(n.td,{children:[(0,t.jsx)(n.code,{children:"scenegraphs.animations"})," and ",(0,t.jsx)(n.code,{children:"scenegraphs.animator"})]})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"KHR_materials_dispersion"})}),(0,t.jsx)(n.td,{children:"Preserves and animates physically based chromatic dispersion."}),(0,t.jsx)(n.td,{children:"Canonical material uniforms and material-pointer channels."})]})]})]}),"\n",(0,t.jsx)(n.h2,{id:"material-variants",children:"Material variants"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"KHR_materials_variants"})," stores application-visible variant names at the document root and maps\nindividual mesh primitives to alternate source materials:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'{\n "extensions": {\n "KHR_materials_variants": {\
1n "variants": [{"name": "Midnight"}, {"name": "Sunrise"}]\n }\n },\n "meshes": [\n {\n "primitives": [\n {\n "material": 0,\n "extensions": {\n "KHR_materials_variants": {\n "mappings": [\n {"material": 1, "variants": [0]},\n {"material": 2, "variants": [1]}\n ]\n }\n }\n }\n ]\n }\n ]\n}\n'})}),"\n",(0,t.jsx)(n.p,{children:"The scenegraph exposes one format-aware controller:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"const {variants} = scenegraphs;\n\nconsole.log(variants.names); // ['Midnight', 'Sunrise']\nconsole.log(variants.activeVariant); // null\n\nvariants.selectVariant('Midnight');\nconsole.log(variants.activeVariant); // 'Midnight'\n\nvariants.selectVariant('Sunrise');\nvariants.resetVariant();\n\nconsole.log(variants.activeVariant); // null\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Selection preserves existing ",(0,t.jsx)(n.code,{children:"GroupNode"}),", ",(0,t.jsx)(n.code,{children:"ModelNode"}),", and ",(0,t.jsx)(n.code,{children:"Model"})," identities. Each mapped\nprimitive receives its authored material and source-derived pipeline parameters; primitives without\na mapping for the selected variant return to their original material. Unknown names are rejected\nbefore any primitive changes."]}),"\n",(0,t.jsx)(n.p,{children:"The parsed definitions are also available in authored order:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"for (const variant of scenegraphs.variants.variants) {\n console.log(variant.index, variant.name);\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Material-layout constraint:"})," selection updates existing material and pipeline state; it does not\nrebuild an existing model's shader feature layout. Alternate materials should remain compatible with\nthe primitive's original shader specialization, particularly when introducing a previously absent\ntexture or alpha-cutoff define."]}),"\n",(0,t.jsx)(n.h2,{id:"mesh-gpu-instancing",children:"Mesh GPU instancing"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"EXT_mesh_gpu_instancing"})," associates accessor-backed transforms with a source mesh node. Each\nsource primitive becomes one real instanced model on WebGL and WebGPU:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'{\n "nodes": [\n {\n "mesh": 0,\n "extensions": {\n "EXT_mesh_gpu_instancing": {\n "attributes": {\n "TRANSLATION": 3,\n "ROTATION": 4,\n "SCALE": 5,\n "_FEATURE_ID": 6\n }\n }\n }\n }\n ]\n}\n'})}),"\n",(0,t.jsx)(n.p,{children:"Inspect resolved instance transforms and exact authored accessor metadata:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"import {getGLTFNodeInstancing} from '@luma.gl/gltf';\n\nconst instancing = getGLTFNodeInstancing(gltf, gltf.nodes[0]);\n\nif (instancing) {\n console.log(instancing.matrices.length);\n console.log(instancing.matrices[0]);\n\n for (const [semantic, attribute] of Object.entries(instancing.attributes)) {\n console.log(semantic, {\n values: attribute.value,\n components: attribute.size,\n count: attribute.count,\n normalized: attribute.normalized\n });\n }\n}\n"})}),"\n",(0,t.jsx)(n.p,{children:"Omitted translation, rotation, and scale components receive their glTF identity defaults. Signed\nand unsigned normalized integer accessors are decoded correctly, and authored quaternions are\nnormalized before matrix composition. Mismatched accessor counts fail instead of producing partial\nor incorrectly indexed draws."}),"\n",(0,t.jsx)(n.p,{children:"Generated model nodes expose the same underlying instance data:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"import {ModelNode} from '@luma.gl/engine';\n\nscenegraphs.scenes[0].traverse(node => {\n if (node instanceof ModelNode && node.model.isInstanced) {\n console.log(node.model.instanceCount);\n console.log(node.instanceMatrices);\n }\n});\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Each instance matrix is uploaded through four per
1-instance vector attributes. Bounds include every\ninstance, so ",(0,t.jsx)(n.code,{children:"scenegraphs.modelBounds"})," and ",(0,t.jsx)(n.code,{children:"scenegraphs.sceneBounds"})," remain useful for initial\ncamera framing. Custom ",(0,t.jsx)(n.code,{children:"_NAME"})," accessors are preserved in ",(0,t.jsx)(n.code,{children:"instancing.attributes"}),"; applications\nmust explicitly bind custom semantics if their own shaders consume them."]}),"\n",(0,t.jsx)(n.p,{children:"Instancing reduces repeated mesh draws, not the number of distinct source primitives: a mesh with\nthree primitives still produces three instanced draws."}),"\n",(0,t.jsx)(n.h2,{id:"recursive-node-visibility",children:"Recursive node visibility"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"KHR_node_visibility"})," contributes its authored boolean directly to the existing generic\n",(0,t.jsx)(n.code,{children:"GroupNode.display"})," state:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'{\n "nodes": [\n {\n "children": [1, 2],\n "extensions": {\n "KHR_node_visibility": {"visible": false}\n }\n },\n {"mesh": 0},\n {"extensions": {"KHR_lights_punctual": {"light": 0}}}\n ]\n}\n'})}),"\n",(0,t.jsxs)(n.p,{children:["The entire descendant subtree is hidden during rendering. Punctual lights attached anywhere inside\nthat subtree are omitted from ",(0,t.jsx)(n.code,{children:"scenegraphs.lights"}),":"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"const node = scenegraphs.gltfNodeIndexToNodeMap.get(0);\n\nconsole.log(node?.display); // false\nconsole.log(scenegraphs.lights.length); // excludes hidden descendants\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"GroupNode.traverse()"})," skips hidden nodes and descendants. ",(0,t.jsx)(n.code,{children:"preorderTraversal()"})," deliberately\nretains structural traversal, allowing applications and internal controllers to inspect or update\nhidden nodes. ",(0,t.jsx)(n.code,{children:"GroupNode.getBounds()"})," follows visible traversal; the initial\n",(0,t.jsx)(n.code,{children:"scenegraphs.sceneBounds"})," and ",(0,t.jsx)(n.code,{children:"scenegraphs.modelBounds"})," are load-time snapshots."]}),"\n",(0,t.jsx)(n.h3,{id:"animate-visibility",children:"Animate visibility"}),"\n",(0,t.jsx)(n.p,{children:"The ratified pointer targets the extension's boolean field:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-json",children:'{\n "samplers": [{"input": 0, "output": 1, "interpolation": "STEP"}],\n "channels": [\n {\n "sampler": 0,\n "target": {\n "path": "pointer",\n "extensions": {\n "KHR_animation_pointer": {\n "pointer": "/nodes/3/extensions/KHR_node_visibility/visible"\n }\n }\n }\n }\n ]\n}\n'})}),"\n",(0,t.jsxs)(n.p,{children:["Boolean visibility requires ",(0,t.jsx)(n.code,{children:"STEP"})," interpolation. When a visibility channel evaluates,\n",(0,t.jsx)(n.code,{children:"GLTFAnimator"})," updates the existing node and refreshes punctual lights in place: the\n",(0,t.jsx)(n.code,{children:"scenegraphs.lights"})," array keeps its identity while its contents reflect the newly visible scene."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"const originalLights = scenegraphs.lights;\n\nscenegraphs.animator.setTime(1000);\n\nconsole.log(scenegraphs.gltfNodeIndexToNodeMap.get(3)?.display);\nconsole.log(scenegraphs.lights === originalLights); // true\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Changing ",(0,t.jsx)(n.code,{children:"node.display"})," directly affects generic scenegraph traversal, but does not automatically\nrefresh a previously parsed punctual-light array. The glTF animation controller handles that\nrefresh for source-authored visibility channels."]}),"\n",(0,t.jsx)(n.h2,{id:"typed-animation-pointers",children:"Typed animation pointers"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"KHR_animation_pointer"})," targets precise JSON properties while reusing the existing shared\n",(0,t.jsx)(n.code,{children:"AnimationSampler"}),", ",(0,t.jsx)(n.code,{children:"AnimationTrack"}),", ",(0,t.jsx)(n.code,{children:"AnimationClip"}),", and ",(0,t.jsx)(n.code,{children:"AnimationMixer"})," implementations."]}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Target family"}),(0,t.jsx)(n.th,{children:"Example pointer"}),(0,t.jsx)(n.th,{children:"Result"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Node transform"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/nodes/2/translation"})}),(0,t.jsx)(n.td,{children:"Updates the existing node transform."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Morph weights"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/nodes/2/weights"})}),(0,t.jsx)(n.td,{children:"Updates existing morph vertex buffers."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Recursive visibility"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/nodes/2/extensions/KHR_node_visibility/visible"})}),(0,t.jsxs)(n.td,{children:["Updates ",(0,t.jsx)(n.code,{children:"display"})," and refreshes punctual lights."]})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Perspective camera"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/cameras/0/perspective/yfov"})}),(0,t.jsx)(n.td,{children:"Updates a runtime projection copy."})]}
1),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Orthographic camera"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/cameras/1/orthographic/xmag"})}),(0,t.jsx)(n.td,{children:"Updates a runtime projection copy."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Light intensity"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/extensions/KHR_lights_punctual/lights/0/intensity"})}),(0,t.jsx)(n.td,{children:"Refreshes the corresponding runtime light."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Light RGB channel"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/extensions/KHR_lights_punctual/lights/0/color/2"})}),(0,t.jsx)(n.td,{children:"Updates one authored linear color component."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Spotlight cone"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/extensions/KHR_lights_punctual/lights/0/spot/innerConeAngle"})}),(0,t.jsx)(n.td,{children:"Preserves distinct inner and outer cones."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Chromatic dispersion"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/materials/0/extensions/KHR_materials_dispersion/dispersion"})}),(0,t.jsx)(n.td,{children:"Updates the canonical physical-material uniform."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:"Texture transform"}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"/materials/0/pbrMetallicRoughness/baseColorTexture/extensions/KHR_texture_transform/offset/0"})}),(0,t.jsx)(n.td,{children:"Updates the existing material's UV transform."})]})]})]}),"\n",(0,t.jsxs)(n.p,{children:["Supported perspective properties are ",(0,t.jsx)(n.code,{children:"aspectRatio"}),", ",(0,t.jsx)(n.code,{children:"yfov"}),", ",(0,t.jsx)(n.code,{children:"znear"}),", and ",(0,t.jsx)(n.code,{children:"zfar"}),"; orthographic\nproperties are ",(0,t.jsx)(n.code,{children:"xmag"}),", ",(0,t.jsx)(n.code,{children:"ymag"}),", ",(0,t.jsx)(n.code,{children:"znear"}),", and ",(0,t.jsx)(n.code,{children:"zfar"}),". Punctual lights support ",(0,t.jsx)(n.code,{children:"color"}),", individual\nRGB components, ",(0,t.jsx)(n.code,{children:"intensity"}),", ",(0,t.jsx)(n.code,{children:"range"}),", ",(0,t.jsx)(n.code,{children:"innerConeAngle"}),", and ",(0,t.jsx)(n.code,{children:"outerConeAngle"}),"."]}),"\n",(0,t.jsxs)(n.p,{children:["Camera projections are cloned into ",(0,t.jsx)(n.code,{children:"scenegraphs.cameras"})," before animation. Light definitions are\nalso copied, and the exported light array is refreshed in place. Animation therefore never mutates\nthe original postprocessed glTF camera or light document."]}),"\n",(0,t.jsx)(n.h3,{id:"inspect-discriminated-channel-types",children:"Inspect discriminated channel types"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"import {\n parseGLTFAnimations,\n type GLTFCameraAnimationChannel,\n type GLTFLightAnimationChannel\n} from '@luma.gl/gltf';\n\nfor (const animation of parseGLTFAnimations(gltf)) {\n for (const channel of animation.channels) {\n if (channel.type === 'camera') {\n const cameraChannel: GLTFCameraAnimationChannel = channel;\n console.log(cameraChannel.targetCameraIndex, cameraChannel.projection, cameraChannel.property);\n }\n\n if (channel.type === 'light') {\n const lightChannel: GLTFLightAnimationChannel = channel;\n console.log(lightChannel.targetLightIndex, lightChannel.property, lightChannel.component);\n }\n\n if (channel.type === 'material' && channel.property === 'dispersion') {\n console.log(channel.targetMaterialIndex, channel.pointer);\n }\n }\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Physical dispersion is meaningful when the source material enables its authored\n",(0,t.jsx)(n.code,{children:"KHR_materials_dispersion"})," extension. The shared experimental ",(0,t.jsx)(n.code,{children:"SceneRenderer"})," and ANARI facade\ncombine the animated canonical uniform with captured opaque-scene refraction. The standalone glTF\nmodel path preserves and animates the same factor but does not capture scene color."]}),"\n",(0,t.jsxs)(n.p,{children:["See ",(0,t.jsx)(n.a,{href:"/docs/api-reference/gltf/gltf-animation",children:"glTF animation and deformation"})," for clip selection,\ncrossfading, automatic skin palettes, and morph-target playback."]}),"\n",(0,t.jsx)(n.h2,{id:"strict-extension-capability-checks",children:"Strict extension capability checks"}),"\n",(0,t.jsxs)(n.p,{children:["The glTF distinction between ",(0,t.jsx)(n.code,{children:"extensionsUsed"})," and ",(0,t.jsx)(n.code,{children:"extensionsRequired"})," matters: optional\nunsupported features may degrade gracefully, while required unsupported features should reject the\nasset before GPU models are created."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"import {\n assertSupportedGLTFExtensions,\n getGLTFExtensionSupport,\n getUnsupportedRequiredGLTFExtensions\n} from '@luma.gl/gltf';\n\nconst support = getGLTFExtensionSupport(gltf);\n\nfor (const extension of support.values()) {\n console.log({\n name: extension.extensionName,\n required: extension.required,\n supported: extension.supported,\n level: extension.supportLevel,\n explanation: extension.comment\n });\n}\n\nconst unsupportedRequired = getUnsupportedRequiredGLTFExtensions(gltf);\n\nif (unsupportedRequired.length > 0) {\n assertSupportedGLTFExtensions(gltf);\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"createScenegraphsFromGLTF(device, gltf, {strictExtensions: true})"})," performs a device-aware version\nof the same assertion ",(0,t.jsx)(n.strong,{children:"before"})," parsing GPU resources. In particular, required\n",(0,t.jsx)(n.code,{children:"KHR_texture_basisu"})," fails when loaders.gl selected a compressed GPU format that the active device\ncannot create. The returned ",(0,t.jsx)(n.code,{children:"scenegraphs.extensionSupport"})," uses the same device-specific capability\nmodel."]}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Support level"}),(0,t.jsx)(n.th,{children:"Meaning"}),(0,t.jsx)(n.th,{children:"Accepted when required?"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"built-in"})}),(0,t.jsx)(n.td,{children:"A complete decoder or runtime path handles the feature."}),(0,t.jsx)(n.td,{children:"Yes"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"parsed-and-wired"})}),(0,t.jsx)(n.td,{children:"Parsed source data is connected to the existing runtime."}),(0,t.jsx)(n.td,{children:"Yes"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"loader-only"})}),(0,t.jsx)(n.td,{children:"Loader data survives, but device/application support is not guaranteed."}),(0,t.jsx)(n.td,{children:"No"})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"none"})}),(0,t.jsx)(n.td,{children:"No complete built-in runtime behavior is available."}),(0,t.jsx)(n.td,{children:"No"})]})]})]}),"\n",(0,t.jsxs)(n.p,{children:["For example, required ",(0,t.jsx)(n.code,{children:"KHR_node_visibility"}),", ",(0,t.jsx)(n.code,{children:"EXT_mesh_gpu_instancing"}),", material variants, and\nphysically implemented ",(0,t.jsx)(n.code,{children:"KHR_materials_dispersion"})," pass strict checks. A required unknown vendor\nextension fails. Required WebP remains conservative because image decoding depends on the browser;\nrequired AVIF fails because the installed glTF loader does not implement ",(0,t.jsx)(n.code,{children:"EXT_texture_avif"})," source\nselection."]}),"\n",(0,t.jsx)(n.p,{children:"Capability collection includes declared used/required extensions, source root extension entries,\nextensions moved during loaders.gl postprocessing, and detected punctual lights."}),"\n",(0,t.jsx)(n.h2,{id:"automatic-animation-and-deformation-integration",children:"Automatic animation and deformation integration"}),"\n",(0,t.jsx)(n.p,{children:"Native pointers participate in the same animation frame as transforms, skinning, and morphing:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-ts",children:"scenegraphs.animator.selectClip('Walk');\nscenegraphs.animator.selectClip('Run', {crossFadeDuration: 0.35});\n\nfunction renderFrame(timestampMilliseconds: number): void {\n scenegraphs.animator.setTime(timestampMilliseconds);\n\n for (const binding of scenegraphs.skins.bindings) {\n console.log(binding.nodeIndex, binding.joints.length, binding.jointMatrices);\n }\n\n requestAnimationFrame(renderFrame);\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.code,{children:"setTime()"})," accepts absolute milliseconds; ",(0,t.jsx)(n.code,{children:"selectClip()"})," crossfade duration and\n",(0,t.jsx)(n.code,{children:"animator.update(deltaSeconds)"})," use seconds. Imported skin palettes are updated automatically once\nafter all channels evaluate, using existing mesh-local joint matrices and the canonical shared skin\nshader. ",(0,t.jsx)(n.code,{children:"scenegraphs.skins.getBinding(nodeIndex)"})," exposes a specific reusable skin binding."]}),"\n",(0,t.jsx)(n.h2,{id:"official-fixture-coverage",children:"Official fixture coverage"}),"\n",(0,t.jsxs)(n.p,{children:["The runtime is tested against compact, unmodified CC0 assets from Khronos glTF Sample Assets,\npinned to source commit ",(0,t.jsx)(n.code,{children:"2bac6f8c57bf471df0d2a1e8a8ec023c7801dddf"}),":"]}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Official fixture"}),(0,t.jsx)(n.th,{children:"Size"}),(0,t.jsx)(n.th,{children:"What is checked"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"SimpleInstancing.glb"})}),(0,t.jsx)(n.td,{children:"7.2 KB"}),(0,t.jsx)(n.td,{children:"Real WebGL/WebGPU instanced draws, source TRS accessors, instance counts, matrices, and aggregate bounds."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"CubeVisibility.glb"})}),(0,t.jsx)(n.td,{children:"3.2 KB"}),(0,t.jsxs)(n.td,{children:["Recursive hidden meshes, boolean ",(0,t.jsx)(n.code,{children:"STEP"})," animation, strict capability checks, and derived variant/dispersion mutation cases."]})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"LightVisibility.glb"})}),(0,t.jsx)(n.td,{children:"2.9 KB"}),(0,t.jsx)(n.td,{children:"Recursive hidden punctual lights, stable light arrays, animated camera projections, and authored light properties."})]})]})]}),"\n",(0,t.jsxs)(n.p,{children:["CPU tests inspect parsed source behavior and scenegraph identity using ",(0,t.jsx)(n.code,{children:"NullDevice"}),". Browser tests\nexecute real WebGL and WebGPU draw calls against the same official instancing fixture. Mutation\ntests derive material-variant and camera/light/dispersion-pointer cases from the small official\ndocuments instead of adding large synthetic assets."]}),"\n",(0,t.jsxs)(n.p,{children:["The fixture attribution and pinned source revision are recorded in\n",(0,t.jsx)(n.code,{children:"modules/gltf/test/data/README.md"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"architecture-and-ownership",children:"Architecture and ownership"}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Package"}),(0,t.jsx)(n.th,{children:"Responsibility"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"@loaders.gl/gltf"})}),(0,t.jsx)(n.td,{children:"Container/resource decoding, Draco and EXT meshopt decompression, accessor/image postprocessing, feature metadata, and GLB container encoding."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"@luma.gl/gltf"})}),(0,t.jsx)(n.td,{children:"Extension interpretation, source-material mappings, typed pointers, skin ownership, and capability reporting."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"@luma.gl/engine"})}),(0,t.jsxs)(n.td,{children:["Generic ",(0,t.jsx)(n.code,{children:"GroupNode"})," visibility, reusable ",(0,t.jsx)(n.code,{children:"Model"})," instancing, animation mixing, and deformation utilities."]})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"@luma.gl/sha
1dertools"})}),(0,t.jsx)(n.td,{children:"Canonical PBR material uniforms, physical shading, and reusable skinning."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"@luma.gl/experimental"})}),(0,t.jsx)(n.td,{children:"Optional format-independent physical scene rendering and captured-scene transmission."})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"@luma.gl/scene"})}),(0,t.jsxs)(n.td,{children:["Optional thin retained-object orchestration through ",(0,t.jsx)(n.code,{children:"@luma.gl/scene/gltf"}),"; no loader or BRDF ownership."]})]})]})]}),"\n",(0,t.jsx)(n.p,{children:"The core ANARI package does not import glTF. Its optional glTF adapter imports the existing glTF\nparsers and engine animation primitives. Camera, punctual-light, and visibility pointer playback\ncurrently belongs to the canonical glTF scenegraph; the thin ANARI animation adapter safely ignores\nunsupported target families instead of creating an independent extension runtime."}),"\n",(0,t.jsx)(n.h2,{id:"boundaries-and-current-limitations",children:"Boundaries and current limitations"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["Visibility booleans require ",(0,t.jsx)(n.code,{children:"STEP"}),"; interpolating a boolean channel is invalid."]}),"\n",(0,t.jsxs)(n.li,{children:["Texture transforms support ",(0,t.jsx)(n.code,{children:"TEXCOORD_0"})," and ",(0,t.jsx)(n.code,{children:"TEXCOORD_1"}),", not animated ",(0,t.jsx)(n.code,{children:"texCoord"})," or\n",(0,t.jsx)(n.code,{children:"TEXCOORD_2+"}),"."]}),"\n",(0,t.jsxs)(n.li,{children:["Structural material switches such as ",(0,t.jsx)(n.code,{children:"alphaMode"}),", ",(0,t.jsx)(n.code,{children:"doubleSided"}),", and ",(0,t.jsx)(n.code,{children:"unlit"})," are not animation\npointer targets."]}),"\n",(0,t.jsx)(n.li,{children:"Material variants do not construct a new shader feature layout when the alternate material adds\npreviously absent texture bindings."}),"\n",(0,t.jsxs)(n.li,{children:["Custom ",(0,t.jsx)(n.code,{children:"_NAME"})," instance accessors remain available without being automatically bound to a custom\nshader."]}),"\n",(0,t.jsx)(n.li,{children:"Standalone glTF scenegraphs do not own an opaque-scene capture pass; true transmitted scene-color\nrefraction is provided by the shared experimental renderer and ANARI facade."}),"\n",(0,t.jsxs)(n.li,{children:["Generic ",(0,t.jsx)(n.code,{children:"extras"}),", unsupported vendor metadata, automatic image-based-light extension ingestion,\nand video-texture extensions are not silently presented as supported required features."]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["For the complete extension-by-extension capability matrix, see\n",(0,t.jsx)(n.a,{href:"/docs/api-reference/gltf/gltf-extensions",children:"glTF extension support"}),"."]})]})}function p(e={}){let{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(h,{...e})}):h(e)}},79183(e,n,i){i.d(n,{N:()=>r});var s=i(62540);i(63696);var t=i(11083);function r(e){let{active:n,group:i}=e;return(0,s.jsx)("nav",{className:"docs-page-tabs","aria-label":i.label,children:i.tabs.map(e=>(0,s.jsx)(t.A,{className:e.id===n?"docs-page-tabs__tab docs-page-tabs__tab--active":"docs-page-tabs__tab",to:e.href,"aria-current":e.id===n?"page":void 0,children:e.label},e.id))})}},91845(e,n,i){i.d(n,{x:()=>a});var s=i(62540);i(63696);var t=i(79183);let r=[{id:"overview",label:"Overview",href:"/docs/api-reference/gltf"},{id:"materials",label:"Materials",href:"/docs/api-reference/gltf/gltf-materials"},{id:"native-extensions",label:"Native Extensions",href:"/docs/api-reference/gltf/gltf-native-extensions"},{id:"animation",label:"Animation",href:"/docs/api-reference/gltf/gltf-animation"},{id:"animated-crowd",label:"Animated Crowd",href:"/docs/api-reference/gltf/gltf-animated-crowd"},{id:"interchange",label:"Interchange",href:"/docs/api-reference/gltf/gltf-interchange"},{id:"extensions",label:"Extensions",href:"/docs/api-reference/gltf/gltf-extensions"}];function a(e){let{active:n}=e;return(0,s.jsx)(t.N,{active:n,group:{label:"glTF documentation",tabs:r}})}},43023(e,n,i){i.d(n,{R:()=>a,x:()=>d});var s=i(63696);let t={},r=s.createContext(t);function a(e){let n=s.useContext(r);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.