glTF Extension Support
This page summarizes the glTF extensions that @luma.gl/gltf handles in its
default loader-to-scenegraph path.
Each extension name links to its official Khronos-managed specification page in
the glTF extension registry repository.
For a complete application walkthrough, including source JSON, public TypeScript APIs, real instanced draws, material selection, recursive mesh/light visibility, typed camera and light animation, capability diagnostics, and official sample coverage, see native glTF extensions.
Status meanings:
✅: works end-to-end in the default luma.gl glTF pipeline.✅ *️⃣: works in the default pipeline, with low-level decoding handled by@loaders.gl/gltfduring load.🚧: partially implemented; the linked row states which paths are available and which limitations remain.*️⃣: extension data is preserved or exposed, but applications still need to decide how to use it at runtime.App: applications are responsible for selecting and applying the feature.❌: no built-in handling today.
GLTFLoader already owns container parsing, linked resources, typed/deinterleaved accessor
postprocessing, Draco and EXT_meshopt_compression decoding, Basis/KTX2 texture selection,
conditional WebP selection, and supported 3D Tiles feature/structural metadata. The rendering
package consumes those results; it does not need independent mesh codecs, accessor decoders, or
GLB container serialization. postProcessGLTF() must still be called explicitly in loaders.gl v4.
| Extension | Support | Notes |
|---|---|---|
| ✅ *️⃣ |
| |
| ✅ *️⃣ |
| |
| ❌ | The newer Khronos release-candidate spelling is not implemented by the installed
| |
| ✅ *️⃣ | loaders.gl materializes typed quantized accessor values and normalization metadata; luma.gl preserves the GPU attribute format or expands morph bases only when needed. | |
| *️⃣ |
| |
| *️⃣ |
| |
| ✅ | Accessor-backed translation, rotation, and scale become one real instanced draw
per source primitive on WebGL and WebGPU. Aggregate bounds include every
instance, and application-defined | |
| ✅ | loaders.gl exposes the authored light definitions; luma.gl creates directional,
point, and spot lights that preserve intensity, range,
source-node world transforms, and spotlight inner/outer cones. Use
| |
| ✅ | loaders.gl preserves the unlit marker; luma.gl bypasses the default lighting calculations when creating the material. | |
| ✅ 🚧 | Emissive strength now multiplies the emissive contribution in the stock PBR shader. | |
| ✅ 🚧 | The stock shader now applies specular intensity and color factors and textures to the dielectric F0 term. | |
| ✅ 🚧 | The stock shader now drives dielectric reflectance from the glTF IOR value. | |
| ✅ 🚧 | Material factors and textures are preserved. The shared experimental
| |
| ✅ 🚧 | Experimental, unratified proposal. Linear red-channel height textures perturb the existing tangent-space normal, preserve per-slot UV transforms, and compose with conventional normal maps. | |
| ✅ 🚧 | Khronos release candidate, not ratified. Canonical WebGL and WebGPU PBR shading implements opposite-hemisphere punctual lighting and IBL, energy-conserving front-diffuse reduction, linear alpha factor maps, and sRGB transmission colors. | |
| ✅ 🚧 | Thickness factor plus attenuation color and distance now tint transmitted light in the stock shader. | |
| ✅ 🚧 | Authored dispersion is parsed into the canonical PBR material. Shared experimental and ANARI rendering separate transmitted RGB wavelengths using the ratified wavelength-dependent IOR. A supported material animation pointer can update the same physical dispersion uniform. | |
| 🚧 | Active, unratified draft. A local thickness-aware, anisotropic single-scattering
approximation is wired into diffuse transmission. Requires
| |
| ✅ 🚧 | The stock shader now adds a secondary clearcoat lobe from the clearcoat and clearcoat-roughness inputs. | |
| ✅ 🚧 | The stock shader now adds a sheen lobe using the sheen color and roughness inputs. | |
| ✅ 🚧 | The stock shader now tints specular response with a view-dependent thin-film iridescence approximation driven by the extension inputs. | |
| ✅ 🚧 | The stock shader now shapes highlights and IBL response with an anisotropy-direction approximation. | |
| *️⃣ | loaders.gl can preserve the extension data, but | |
| ✅ |
| |
| ✅ *️⃣ | BasisU / KTX2 textures are passed through as compressed textures when supported
by the device. The runtime checks the transcoded GPU format before upload; optional
unsupported formats use a 1×1 fallback, and required BasisU assets fail
| |
| ✅ *️⃣ |
| |
| ❌ | The generic image loader can decode AVIF when the browser supports it, but the installed glTF loader does not select texture sources from this extension. | |
| ✅ | All 21 supported material slots retain independent offset, rotation, scale,
and authored | |
| ✅ 🚧 | Node TRS, morph-weight, and recursive boolean visibility pointers; selected
material factor pointers; and animated | |
| ✅ | Source-authored visibility recursively hides mesh descendants and punctual lights. Visibility-pointer animation updates existing scenegraph nodes and preserves the identity of the exported light array. | |
| ❌ | Metadata payloads remain in the loaded glTF, but luma.gl does not interpret them. | |
| ❌ | Metadata is available in the loaded glTF, but luma.gl does not interpret it. | |
| ❌ | Use | |
| ❌ | Video textures are not created automatically by the glTF pipeline. | |
| ✅ | Animated crowds resolve authored node levels and
|
Inspect runtime capabilities
Source extension declarations can be inspected before scenegraph creation:
import {
assertSupportedGLTFExtensions,
createScenegraphsFromGLTF,
getGLTFExtensionSupport,
getGLTFExtensionSupportSummary,
getRegisteredGLTFExtensions,
getUnsupportedRequiredGLTFExtensions
} from '@luma.gl/gltf';
const registrySummary = getGLTFExtensionSupportSummary();
console.log(
`${registrySummary.supported}/${registrySummary.total} registered extensions have complete runtime support`
);
for (const registeredExtension of getRegisteredGLTFExtensions()) {
console.log(
registeredExtension.extensionName,
registeredExtension.supportLevel,
registeredExtension.standardStatus
);
}
for (const capability of getGLTFExtensionSupport(gltf).values()) {
console.log(capability.extensionName, {
required: capability.required,
supported: capability.supported,
level: capability.supportLevel,
standardStatus: capability.standardStatus,
explanation: capability.comment
});
}
const unsupportedRequired = getUnsupportedRequiredGLTFExtensions(gltf);
if (unsupportedRequired.length > 0) {
assertSupportedGLTFExtensions(gltf);
}
const scenegraphs = createScenegraphsFromGLTF(device, gltf, {
strictExtensions: true
});
built-in and parsed-and-wired capabilities satisfy required-extension checks. loader-only
capabilities, including browser-dependent WebP selection and decoded feature/structural metadata,
do not promise complete portable runtime support. AVIF source selection, unknown extensions, and
other none capabilities also fail when required. Optional unsupported extensions remain visible
in the report without preventing scene creation.
The returned scenegraphs.extensionSupport preserves the document-specific capability report.
Strict checks run before model creation, so an unsupported required feature does not leave a
partially constructed GPU scene behind.
Use authored runtime extensions
import {getGLTFNodeInstancing} from '@luma.gl/gltf';
scenegraphs.variants.selectVariant('Midnight');
scenegraphs.variants.resetVariant();
const instancing = getGLTFNodeInstancing(gltf, gltf.nodes[0]);
console.log(instancing?.matrices.length);
scenegraphs.animator.selectClip('Night lighting', {
crossFadeDuration: 0.3
});
scenegraphs.animator.setTime(1000);
console.log(scenegraphs.cameras[0]);
console.log(scenegraphs.lights);
Variant selection preserves existing scenegraph node/model identities and restores unmapped primitives to their authored default materials. Instancing submits one GPU draw per source primitive on both WebGL and WebGPU. Visibility and punctual-light pointers update stable retained scene objects through the existing shared animation mixer. Automatic source skin palettes and morph targets are evaluated in the same frame.
Notes
getGLTFExtensionSupport(gltf)distinguishes optional and required extensions. UsegetUnsupportedRequiredGLTFExtensions(gltf),assertSupportedGLTFExtensions(gltf), orcreateScenegraphsFromGLTF(device, gltf, {strictExtensions: true})to reject unsupported required features instead of silently degrading them.getGLTFNodeInstancing(gltf, node)exposes resolved source matrices and all authored instance-accessor semantics, including application-defined_NAMEattributes.- The built-in material extension rows reuse the shared
pbrMaterialshader. The experimentalSceneRendererand ANARI capture opaque scene color for screen-space transmission/refraction; the standalonecreateScenegraphsFromGLTF()rendering path does not perform that capture and retains its alpha fallback. Iridescence and anisotropy still use pragmatic shared-shader approximations. - The supported material extensions wire all 21 canonical core/extension texture slots, including auxiliary specular, clearcoat, sheen, iridescence, anisotropy, transmission, and volume maps. The physical response of some advanced effects remains an approximation.
@luma.gl/gltfrelies on@loaders.gl/gltffor low-level extension decoding, decompression, accessor post-processing, and binary GLB container encoding. The loader's legacy CPU texture-transform preprocessing and the runtime's animated shader transforms must not both apply the same authored UV transform.- Core glTF skin attributes and animated morph targets are supported independently of these extension rows; source skin palettes are updated automatically once per animation frame. See glTF animation and deformation, native glTF extensions, and glTF materials and textures.