Repository navigation
glTF extensions
Support status for the extensions handled by three.js's GLTFLoader, plus the others found in Khronos's sample assets. An extension is supported when running objtools import or objtools inspect on a .gltf or .glb that uses it works as expected.
Status measured at commit 0ff8c03 on 2026-11-03. glTF and GLB input describes what import does with the rest of the file.
| Status | Count | Meaning |
|---|---|---|
| Supported | 18 |
import keeps the extension in its output and inspect reports the right figures |
| Incomplete | 2 | Works, except for --convert-images true, which must decode the image |
| Not supported yet | 3 | Geometry compression. A file that needs it gets a clear error. |
"Blender checks" counts the end-to-end comparisons of a sample using the extension whose render matched its source after import. See how this was tested for what those can and can't show.
| Extension | Applies to | Status | Blender checks | Notes |
|---|---|---|---|---|
KHR_lights_punctual |
Nodes | Supported | 40 |
--center moves lights with the model |
KHR_materials_anisotropy |
Material | Supported | 24 | |
KHR_materials_clearcoat |
Material | Supported | 49 | |
KHR_materials_diffuse_roughness |
Material | Supported | None | No sample. Checked on one generated file. |
KHR_materials_dispersion |
Material | Supported | 16 | |
KHR_materials_emissive_strength |
Material | Supported | 22 | |
KHR_materials_ior |
Material | Supported | 65 | |
KHR_materials_iridescence |
Material | Supported | 36 | |
KHR_materials_sheen (1) |
Material | Supported | 35 | |
KHR_materials_specular |
Material | Supported | 36 | |
KHR_materials_transmission |
Material | Supported | 129 | |
KHR_materials_unlit |
Material | Supported | 20 | |
KHR_materials_volume |
Material | Supported | 95 | |
EXT_materials_bump |
Material | Supported | None | No sample. Checked on one generated file. |
KHR_mesh_quantization |
Geometry | Supported | 4 | Attributes keep their encoding |
KHR_texture_transform |
Textures | Supported | 60 | Always listed in extensionsRequired
|
EXT_mesh_gpu_instancing |
Nodes | Supported | 4 | Instance transforms count in the --center bounds |
EXT_texture_webp |
Textures | Supported | 6 |
--convert-images true converts it |
KHR_texture_basisu |
Textures | Incomplete | None | Blender can't load these files. --convert-images true is an error: it needs a KTX2 decoder. |
EXT_texture_avif |
Textures | Incomplete | None | No sample, covered by the test suite. --convert-images true is an error: it needs an AVIF decoder. |
EXT_meshopt_compression |
Buffer views | Not supported yet | Refused | Meshopt decoder, medium difficulty |
KHR_meshopt_compression |
Buffer views | Not supported yet | Refused | Same decoder, plus the v1 format and COLOR filter. Low difficulty once the EXT_ decoder exists. |
KHR_draco_mesh_compression |
Primitives | Not supported yet | Refused | Draco decoder, high difficulty |
(1) KHR_materials_sheen is registered by GLTFLoader but missing from its documented list.
inspect only measures base-colour textures, so it ignores what the material extensions add. Textures using KHR_texture_basisu, EXT_texture_webp or EXT_texture_avif are measured from the extension's image, in the order GLTFLoader registers its plugins, and only their headers are read.
- An unsupported entry in
extensionsRequiredis an error, because the file can't be read correctly. That includes Draco and meshopt. - An optional Draco or meshopt extension with uncompressed data available uses that data.
importdrops the extension from its output with a warning. - An optional Draco extension without uncompressed data is an error.
BoxDraco.gltfis such a file: it doesn't list Draco as required, and none of its accessors have abufferView. The spec reads an accessor without abufferViewas zeros, which here would give zeroed geometry. - Any other unsupported extension is dropped with one counted warning.
These aren't in GLTFLoader's list, but Khronos's samples use them. None are supported. Each is dropped with a warning when optional, and refused when required.
| Extension | What is lost |
|---|---|
KHR_materials_variants |
The alternative looks. It has its own warning: each primitive keeps its default material, and import drops the materials and images only the variants use. inspect takes occupancy from the default materials, so before import a texture only the variants use counts as unoccupied. |
KHR_animation_pointer |
Animations that target a property through a pointer, such as a material's or a light's. The channels stay in the output with no target, so the validator gives each a VALUE_NOT_IN_LIST warning. |
KHR_node_visibility |
Hidden nodes become visible. |
KHR_materials_diffuse_transmission, KHR_materials_retroreflection, KHR_materials_volume_scatter
|
The material effect. The rest of the material stays. |
KHR_xmp, KHR_xmp_json_ld
|
The metadata. |
KHR_materials_pbrSpecularGlossiness |
Its only sample requires it, so it is refused. |
--convert-images true is the only operation on these formats that has to decode pixels. Sizes come from headers, and the transparency scan doesn't apply to glTF input.
| Format | --convert-images true |
What full support needs |
|---|---|---|
| WebP | Works | Nothing |
| AVIF | Error: image is built without an AVIF decoder |
A decoder such as image's avif-native feature, which adds a native dav1d dependency |
| KTX2 | Error: image has no KTX2 support |
A Basis Universal transcoder, plus handling for the transcoded output |
KHR_texture_basisu and EXT_texture_avif stay incomplete until these are available.
Draco and meshopt are left out for now.
When compression is added, data should be decoded where it is read rather than when a file is imported, and import should copy compressed data unchanged. Decoding at import would use more disk and add reads later, when most reads are already limited by I/O. It would also commit the file to uncompressed output: recompressing can add rounding, because Draco and meshopt filters quantize values, and a new encoder may choose a different grid.
Three consequences for import need designing at that point:
- It still has to decode to run its checks.
- Without an encoder, it can't remove zero-area triangles from compressed indices, so it can only count them in a warning.
-
--centerhas to apply at the scene roots.
These notes are estimates from reading the code. No decoder crate has been compiled, and the effect of recompression on geometry hasn't been measured.
-
Meshopt: decode each view on first use into a mapped workspace file, behind the reader's buffer-view lookup. This keeps heap use bounded, and
inspectnever needs to decodePOSITION. Themeshopt0.7 crate (official C++ code through FFI) is the first choice, and its publicffimodule accepts a caller's destination.meshopt-pure1.3 is an unofficial pure Rust port and the alternative. -
Draco: compressed data must be decoded as a whole, and
draco-core2.3 stores the result inVec<u8>buffers. Heap use therefore grows with the largest primitive, which conflicts with the streaming constraint for a model stored as one large primitive. That crate is pure Rust and Apache-2.0, and claims parity with Draco 1.5.7, but is unofficial.
The end-to-end harness ran over all 150 Khronos sample models in Blender 5.2.1, with 634 checks in total: every variant of each model, and the plain glTF variant again with --center --up-axis z and as a re-import of that output. It ran on ff51884; the later commits only changed warnings and corrected mimeType.
| Result | Checks | Meaning |
|---|---|---|
| Same | 536 | Pixels and scene data match |
| Pixels match, scene counts differ | 54 | Zero-area faces, omitted points and lines, or images only material variants use |
| Refused | 25 | 16 Draco, 5 meshopt, 4 SpecGlossVsMetalRough
|
| Not comparable | 16 | Blender can't load the source: KTX2, KHR_node_visibility, and a Blender error on AnimationPointerUVs
|
| Different | 3 |
MeshoptCubeTest, from a Blender bug: it decodes meshopt's COLOR filter with the exponential one, which miscolours six cubes of the source only |
The largest difference among matching views was 0.013% of pixels, and no outline changed. Deliberately broken outputs of two models (a moved model, lost textures, flipped UVs, a lost primitive, a changed roughness, a wrong recorded origin) were all flagged.
What this doesn't show:
- A match says nothing about an effect Blender doesn't draw, and nothing has been loaded in three.js.
- Each model renders at a single frame, so skinning and morphs aren't compared in motion. Only the number of animations is compared.
- The extensions without a sample rest on the test suite or one generated file, not on a visual comparison.
CI passes on Linux, macOS and Windows.