← Back to Blog guide

KHR_materials_variants in GLB: runtime skin swaps without draw calls

By BitSoul Team6/25/2026Updated 8/7/20264 min read44 views
KHR_materials_variants in GLB: runtime skin swaps without draw calls

Swapping a character's outfit, a weapon's finish, or a vehicle's livery at runtime is a staple of almost every modern game — and the naive solution (load a separate GLB per variant) blows your draw call budget before the level even loads. KHR_materials_variants is the GLTF extension that solves this problem natively: multiple material sets baked into a single GLB, switchable in one API call with zero extra geometry.

What KHR_materials_variants actually is

`KHR_materials_variants` is a ratified Khronos GLTF 2.0 extension that lets you define a named list of variants at the asset root, then map each mesh primitive to a different material per variant. The file ships with one baseline material set (the default GLTF material), plus N alternate sets stored in the extension block. At load time only the default renders; switching variants is a metadata operation — no geometry reload, no additional GPU buffers.

The extension lives in two places in the GLB JSON. At the root it declares the variant names; on each mesh primitive it maps variant index to material index:

```json
// Root-level (extensionsUsed + asset root extensions):
"KHR_materials_variants": { "variants": [{"name":"default"},{"name":"worn"},{"name":"gold"}] }

// Per-primitive extensions — each mapping is { variants: [idx], material: idx }:
"KHR_materials_variants": {
"mappings": [
{"variants": [0], "material": 0},
{"variants": [1], "material": 2},
{"variants": [2], "material": 4}
]
}
```

Each primitive can map to a completely different material (different albedo, roughness, metallic, emissive). Variants share geometry: the same vertex buffer, same UVs, same rig — only the material bindings differ.

What KHR_materials_variants actually is — illustrated

Authoring variants in Blender

Blender's built-in GLTF exporter (4.2+) supports `KHR_materials_variants` via the Material Variants panel in the GLTF export sidebar. Workflow:

  1. Create one material slot per variant (e.g. `Armor_Default`, `Armor_Worn`, `Armor_Gold`).
  2. In the GLTF export panel, enable Material Variants and assign each slot to a named variant.
  3. Export as GLB. Confirm the file contains `KHR_materials_variants` in `extensionsUsed`.

Keep variant count reasonable: 3–8 variants per asset is typical. Each variant adds one extra material entry to the GLB but no geometry — file size grows linearly with texture count, not mesh complexity.

Activating variants in Godot 4

Godot 4's GLTF importer recognises `KHR_materials_variants` as of 4.1. After import, each variant is accessible via `GLTFState`. Populate `gltf_state` at load time via `GLTFDocument.append_from_file()`, then switch variants by index:

```gdscript
func apply_variant(variant_name: String) -> void:
# gltf_state populated at load time via GLTFDocument.append_from_file()
var variants := gltf_state.get_additional_data("KHR_materials_variants_names") as Array
var idx := variants.find(variant_name)
if idx == -1:
push_warning("Variant not found: " + variant_name)
return
for mesh in get_children_recursive(model, MeshInstance3D):
var im := mesh.mesh as ImporterMesh
if im:
im.set_surface_material(idx, gltf_state.materials[idx])
```

For production use, the cleaner pattern is to bake materials into `MeshInstance3D` override slots at import time using an `EditorImportPlugin` — that way variant switching is a direct `set_surface_override_material()` call with no GLTF state retained at runtime.

Activating variants in Godot 4 — illustrated

Using variants in Three.js and web-based engines

Three.js ships a `KHRMaterialsVariantsExtension` plugin in `three/addons`. Register it on the loader, then call `selectVariant()` after the GLB loads — the call traverses the scene graph and applies cached material references per primitive synchronously, in one frame, when textures are already GPU-resident:

```javascript
// Import GLTFLoader and KHRMaterialsVariantsExtension from 'three/addons'
const loader = new GLTFLoader();
loader.register(parser => new KHRMaterialsVariantsExtension(parser));

loader.load('armor.glb', (gltf) => {
scene.add(gltf.scene);
// Switch to any named variant — zero extra draw calls, no geometry reload
gltf.functions.selectVariant(gltf.scene, 'gold');
});
```

`selectVariant` returns a Promise, but all material swaps happen within a single frame when textures are already loaded. List available variants via `gltf.userData.gltfExtensions['KHR_materials_variants'].variants.map(v => v.name)`.

Performance profile and when NOT to use it

| Approach | Draw calls | VRAM | Swap cost | Use when |
|---|---|---|---|---|
| Separate GLBs | N × base | N × base | Load from disk | Variants differ in geometry |
| KHR_materials_variants | 1× base | 1× geometry + N× textures | ~0 CPU, 0 GPU | Same mesh, different materials |
| Runtime material swap (no extension) | 1× base | same | Small | 1–2 variants, no reuse |

`KHR_materials_variants` wins when you have 3 or more surface variants on the same mesh, the asset is instanced across the scene, or you need deterministic VRAM cost — load once, switch freely. It's overkill for single-use props with one alternate skin; use a plain material swap there.

Texture packing for variants

All variant materials share the same UV layout — critical for packing efficiency. Use a shared AO/roughness/metallic ORM texture where channels don't change between variants, and only swap the albedo. This halves per-variant VRAM overhead:

With this approach, a 3-variant asset at 2K textures uses 3 albedos + 1 ORM = 4 textures instead of 9.

Where to find assets ready for variant authoring

The BitSoul 3D marketplace ships 747 GLB assets — props, characters, vehicles, and environment kits — in clean, single-UV layouts ideal for adding material variants post-download. Grab a weapon or vehicle asset, add 3 Blender materials, export with `KHR_materials_variants` enabled, and you have a production-ready multi-skin asset in under an hour.

A free BitSoul account covers prototyping this variant pipeline; commercial use of the assets in a shipped game is included with paid memberships. Browse the full library at bitsoulhosting.com/marketplace.

---

*This post is part of the Ultimate Guide to Free 3D Game Assets — BitSoul's complete reference for formats, texturing, rigging, optimization, and engine integration.*

Tags: 3d-models game-assets optimization tutorials workflow unreal blender

Skip the modelling — download it instead

A free BitSoul account gets you 2 game-ready models every month plus 25 AI Engine credits to generate one of your own, no card required. Clean topology, PBR textures, and GLB downloads that drop straight into Unreal, Unity, Godot or Blender — plus OBJ and 3D-printable STL export.

Create a free account → Browse 846 models