Skip to content

GLTF Import & Export

This document provides a deep dive into the GLTFLoader and GLTFExporter classes, which enable bidirectional conversion between the glTF 2.0 format and three.js scene representations. GLTFLoader parses .gltf (JSON) and .glb (binary) files into three.js objects, while GLTFExporter converts three.js scenes back into glTF format.

For information about other model loaders, see Additional Format Loaders. For scene serialization via JSON, see Scene Serialization.

Overview

The glTF import/export system implements comprehensive support for the Khronos glTF 2.0 specification a royalty-free format optimized for efficient transmission and loading of 3D content. Both GLTFLoader and GLTFExporter leverage an extensible plugin architecture to support the core specification plus 17+ official glTF extensions.

Key Capabilities:

  • Parse glTF JSON (.gltf) and binary (.glb) formats via GLTFParser
  • Export three.js scenes to glTF/GLB with GLTFWriter preserving scene hierarchy
  • Plugin-based extension system using register() callbacks
  • Compressed mesh support via DRACOLoader and MeshoptDecoder
  • Compressed texture support via KTX2Loader (Basis Universal)
  • Full PBR material workflow mapping to MeshStandardMaterial/MeshPhysicalMaterial
  • Animation support: skeletal (Skeleton), morph targets, property keyframes (AnimationClip)
  • Complete scene graph: Object3D nodes, Camera, Light objects, hierarchies

Supported glTF Extensions:

ExtensionLoaderExporterPurpose
KHR_draco_mesh_compressionGeometry compression
KHR_materials_clearcoatClear coat layer
KHR_materials_dispersionChromatic dispersion
KHR_materials_emissive_strengthHDR emissive
KHR_materials_iorIndex of refraction
KHR_materials_iridescenceThin-film interference
KHR_materials_sheenFabric appearance
KHR_materials_specularSpecular workflow
KHR_materials_transmissionGlass/transparency
KHR_materials_unlitUnlit materials
KHR_materials_volumeVolumetric materials
KHR_materials_anisotropyAnisotropic reflections
KHR_lights_punctualPoint/Spot/Directional lights
KHR_mesh_quantizationQuantized vertex attributes
KHR_texture_basisuBasis Universal textures
KHR_texture_transformTexture transforms
EXT_materials_bumpBump mapping
EXT_texture_webpWebP textures
EXT_texture_avifAVIF textures
EXT_meshopt_compressionMeshopt buffer compression
EXT_mesh_gpu_instancingGPU instancing

Performance Considerations

Loader Optimizations

Exporter Optimizations

  • Resource deduplication: GLTFWriter.cache prevents duplicate processing of shared geometries, materials, and textures examples/jsm/exporters/GLTFExporter.js612-619
  • Buffer merging: All binary data merged into single buffer to reduce HTTP requests
  • Texture compression: Supports exporting compressed textures when available
  • Geometry sharing: Multiple meshes referencing same geometry only export geometry once

Best Practices

Loading Best Practices

1. Use Compression for Production Assets

Always configure compression loaders for production. This can reduce file sizes by 90%+ examples/misc_exporter_gltf.html486-492:

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/examples/jsm/libs/draco/');
const ktx2Loader = new KTX2Loader()
  .setTranscoderPath('jsm/libs/basis/')
  .detectSupport(renderer);

loader.setDRACOLoader(dracoLoader);
loader.setKTX2Loader(ktx2Loader);
loader.setMeshoptDecoder(MeshoptDecoder);

2. Clean Up Resources

Dispose of loader resources when switching scenes or models examples/jsm/loaders/GLTFLoader.js80-82:

// ImageBitmap resources require explicit cleanup
gltf.scene.traverse((node) => {
  if (node.isMesh) {
    node.geometry.dispose();
    if (node.material.map?.image instanceof ImageBitmap) {
      node.material.map.image.close();
    }
    node.material.dispose();
  }
});

3. Use LoadingManager for Progress Tracking

Track loading progress across multiple assets:

const manager = new THREE.LoadingManager();
manager.onProgress = (url, loaded, total) => {
  console.log(`Loading: ${(loaded/total * 100)}%`);
};
const loader = new GLTFLoader(manager);

4. Handle Errors Gracefully

Always provide error handlers examples/jsm/loaders/GLTFLoader.js253-327:

loader.load('model.gltf', onLoad, onProgress, (error) => {
  console.error('Loading failed:', error);
  // Fallback to placeholder model
  scene.add(createPlaceholder());
});

5. Prefer Async/Await for Cleaner Code

Use loadAsync() for modern promise-based workflows:

try {
  const gltf = await loader.loadAsync('model.gltf');
  scene.add(gltf.scene);
} catch (error) {
  console.error('Loading error:', error);
}

Export Best Practices

1. Use Binary Format for Production

Binary .glb files are more efficient than .gltf with separate resources examples/misc_exporter_gltf.html34-69:

const options = { binary: true };
const glb = await exporter.parseAsync(scene, options);

2. Enable TRS for Animated Content

When exporting animations, use trs: true to preserve animation compatibility examples/jsm/exporters/GLTFExporter.js656-660:

const options = {
  binary: true,
  trs: true,  // Required for animations
  animations: mixer.clipAction.getClip()
};

3. Optimize Texture Sizes

Limit texture dimensions to reasonable values examples/misc_exporter_gltf.html34-69:

const options = {
  binary: true,
  maxTextureSize: 2048  // Prevent exporting 4K+ textures
};

4. Set Up Texture Decompression

Required when exporting scenes with compressed textures examples/jsm/exporters/GLTFExporter.js1048-1058:

import * as WebGLTextureUtils from 'three/addons/utils/WebGLTextureUtils.js';
exporter.setTextureUtils(WebGLTextureUtils);

5. Use MeshStandardMaterial for Best Results

MeshStandardMaterial and MeshPhysicalMaterial map cleanly to glTF PBR workflow examples/jsm/exporters/GLTFExporter.js1571-1586:

// Good - exports cleanly
const material = new THREE.MeshStandardMaterial({
  color: 0xff0000,
  metalness: 0.5,
  roughness: 0.7
});

// Avoid - ShaderMaterial not supported
const shaderMat = new THREE.ShaderMaterial({...}); // Will be skipped

6. Ensure UV Channel Consistency

Metalness and roughness maps must use the same UV channel examples/jsm/exporters/GLTFExporter.js1035-1038:

// Both maps must use same UV channel
material.metalnessMap.channel = 0;
material.roughnessMap.channel = 0; // Must match

7. Export Only What's Needed

Use onlyVisible to skip hidden debug objects examples/misc_exporter_gltf.html34-69:

const options = {
  onlyVisible: true,  // Skip object.visible === false
  binary: true
};

Material Workflow Best Practices

1. Separate Metalness and Roughness Maps

The exporter automatically merges them, but keep sources separate for flexibility examples/jsm/exporters/GLTFExporter.js939-1045:

material.metalnessMap = metalnessTexture;  // Blue channel
material.roughnessMap = roughnessTexture;  // Green channel
// Exporter merges into single texture

2. Use Linear Color Space for Data Textures

Non-color data (normal, metalness, roughness) should use NoColorSpace:

normalMap.colorSpace = THREE.NoColorSpace;
metalnessMap.colorSpace = THREE.NoColorSpace;
roughnessMap.colorSpace = THREE.NoColorSpace;

3. Validate Normal Normalization

Ensure normals are unit length to avoid export warnings examples/jsm/exporters/GLTFExporter.js1808-1815:

geometry.computeVertexNormals();
// Exporter will normalize if needed, but pre-normalized is better

Limitations and Constraints


System Architecture

GLTFLoader and GLTFExporter Architecture

SVG
100%

GLTFLoader

Core API

The GLTFLoader class provides the main interface for loading glTF assets:

MethodParametersReturn TypePurpose
load()url, onLoad, onProgress, onErrorvoidAsynchronously load from URL
parse()data, path, onLoad, onErrorvoidParse raw glTF data
parseAsync()data, pathPromiseAsync version of parse()
setDRACOLoader()dracoLoaderGLTFLoaderConfigure Draco decoder
setKTX2Loader()ktx2LoaderGLTFLoaderConfigure KTX2 decoder
setMeshoptDecoder()meshoptDecoderGLTFLoaderConfigure Meshopt decoder
register()callbackGLTFLoaderRegister extension plugin
unregister()callbackGLTFLoaderUnregister extension plugin

Load Result Object (LoadObject):

{
  scene: Group,           // Main scene hierarchy
  scenes: Array<Group>,   // All scenes in file
  cameras: Array<Camera>, // All cameras
  animations: Array<AnimationClip>, // All animations
  asset: Object,         // glTF asset metadata
  parser: GLTFParser,    // Parser instance
  userData: Object       // Custom data
}

Loading Pipeline

GLTFLoader Loading Pipeline

SVG
100%

Key Methods in GLTFParser:

MethodPurposeReturns
parse(onLoad, onError)Main parsing entry pointvoid
getDependency(type, index)Load resource by type/indexPromise<any>
loadBuffer(bufferIndex)Load binary bufferPromise<ArrayBuffer>
loadBufferView(bufferViewIndex)Load buffer view with optional decompressionPromise<ArrayBuffer>
loadAccessor(accessorIndex)Create BufferAttribute from accessorPromise<BufferAttribute>
loadTexture(textureIndex)Create Texture from texture definitionPromise<Texture>
loadImage(imageIndex)Load image from URI or bufferView`Promise<Image
assignTexture(materialParams, mapName, mapDef)Assign texture to material paramsPromise<Texture>
loadMaterial(materialIndex)Create Material from material definitionPromise<Material>
loadGeometry(primitiveIndex)Create BufferGeometry from primitivePromise<BufferGeometry>
loadMesh(meshIndex)Create Mesh or Group from mesh definition`Promise<Group
loadCamera(cameraIndex)Create Camera from camera definitionPromise<Camera>
loadNode(nodeIndex)Create Object3D from node definitionPromise<Object3D>
loadScene(sceneIndex)Build complete scene from scene definitionPromise<Group>

Extension System

GLTFLoader uses a plugin architecture where each glTF extension is implemented as a separate class. Extensions are registered during loader construction via register() and invoked by GLTFParser at specific points in the parsing pipeline.

Extension Registration Flow:

SVG
100%

GLTFParser Extension Methods:

GLTFParser provides these hook points for extensions:

MethodWhen CalledReturn TypePurpose
getMaterialType(materialIndex)Before creating materialMaterial classOverride default material type (e.g., return MeshPhysicalMaterial)
extendMaterialParams(materialIndex, materialParams)During material creationPromiseAdd extension properties to materialParams
createNodeMesh(nodeIndex)During node parsingPromise<Mesh>Create custom mesh (e.g., InstancedMesh)
createNodeAttachment(nodeIndex)During node parsingPromise<Object3D>Attach additional objects (e.g., Light)
loadTexture(textureIndex)During texture loadingPromise<Texture>Custom texture loading (e.g., KTX2)
loadBufferView(index)During buffer loadingPromise<ArrayBuffer>Custom buffer decompression (e.g., Draco, Meshopt)
getDependency(type, index)Dependency resolutionPromise<any>Provide custom dependencies
_markDefs()Before parsing startsvoidMark referenced definitions for dependency tracking

Built-in Extension Classes:

ExtensionClass NameKey Methods
KHR_draco_mesh_compressionGLTFDracoMeshCompressionExtensiondecodePrimitive()
KHR_lights_punctualGLTFLightsExtension_loadLight(), createNodeAttachment(), _markDefs()
KHR_materials_clearcoatGLTFMaterialsClearcoatExtensiongetMaterialType(), extendMaterialParams()
KHR_materials_transmissionGLTFMaterialsTransmissionExtensiongetMaterialType(), extendMaterialParams()
KHR_materials_unlitGLTFMaterialsUnlitExtensiongetMaterialType(), extendParams()
KHR_texture_basisuGLTFTextureBasisUExtensionloadTexture()
EXT_meshopt_compressionGLTFMeshoptCompressionloadBufferView()
EXT_mesh_gpu_instancingGLTFMeshGpuInstancingcreateNodeMesh()

Extension Constructor Pattern:

Extensions receive a GLTFParser instance and declare their name examples/jsm/loaders/GLTFLoader.js643-653:

class GLTFLightsExtension {
  constructor(parser) {
    this.parser = parser;
    this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;
    this.cache = { refs: {}, uses: {} };
  }
  // ... extension methods
}

Extension Example: KHR_lights_punctual

GLTFLightsExtension demonstrates the extension pattern for adding custom scene objects:

GLTFLightsExtension Method Flow

SVG
100%

Implementation Details:

  1. Dependency Tracking: _markDefs() scans json.nodes[] for light references examples/jsm/loaders/GLTFLoader.js655-673:

    _markDefs() {
      const nodeDefs = this.parser.json.nodes || [];
      for (let nodeIndex = 0; nodeIndex < nodeDefs.length; nodeIndex++) {
        const nodeDef = nodeDefs[nodeIndex];
        if (nodeDef.extensions?.[this.name]?.light !== undefined) {
          this.parser._addNodeRef(this.cache, nodeDef.extensions[this.name].light);
        }
      }
    }
  2. Light Creation: _loadLight() creates appropriate Light subclass examples/jsm/loaders/GLTFLoader.js676-741:

    _loadLight(lightIndex) {
      const lightDef = extensions.lights[lightIndex];
      switch (lightDef.type) {
        case 'directional':
          lightNode = new DirectionalLight(color);
          break;
        case 'point':
          lightNode = new PointLight(color);
          lightNode.distance = range;
          break;
        case 'spot':
          lightNode = new SpotLight(color);
          lightNode.angle = lightDef.spot.outerConeAngle;
          lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / outerConeAngle;
          break;
      }
    }
  3. Node Attachment: createNodeAttachment() returns promise for node to attach examples/jsm/loaders/GLTFLoader.js753-770:

    createNodeAttachment(nodeIndex) {
      const lightIndex = nodeDef.extensions[this.name].light;
      return this._loadLight(lightIndex).then((light) => {
        return this.parser._getNodeRef(this.cache, lightIndex, light);
      });
    }

Material Extension Pattern

Material extensions follow a common pattern for extending PBR properties:

Material Extension Execution Flow

SVG
100%

GLTFMaterialsClearcoatExtension Example:

This extension adds clear coat properties to MeshPhysicalMaterial examples/jsm/loaders/GLTFLoader.js877-954:

class GLTFMaterialsClearcoatExtension {
  constructor(parser) {
    this.parser = parser;
    this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT;
  }
  
  getMaterialType(materialIndex) {
    const materialDef = this.parser.json.materials[materialIndex];
    if (!materialDef.extensions?.[this.name]) return null;
    return MeshPhysicalMaterial;
  }
  
  extendMaterialParams(materialIndex, materialParams) {
    const materialDef = this.parser.json.materials[materialIndex];
    const extension = materialDef.extensions?.[this.name];
    if (!extension) return Promise.resolve();
    
    const pending = [];
    
    // Scalar properties
    if (extension.clearcoatFactor !== undefined) {
      materialParams.clearcoat = extension.clearcoatFactor;
    }
    if (extension.clearcoatRoughnessFactor !== undefined) {
      materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor;
    }
    
    // Texture loading
    if (extension.clearcoatTexture !== undefined) {
      pending.push(this.parser.assignTexture(
        materialParams, 'clearcoatMap', extension.clearcoatTexture
      ));
    }
    if (extension.clearcoatRoughnessTexture !== undefined) {
      pending.push(this.parser.assignTexture(
        materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture
      ));
    }
    if (extension.clearcoatNormalTexture !== undefined) {
      pending.push(this.parser.assignTexture(
        materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture
      ));
    }
    
    return Promise.all(pending);
  }
}

Common Material Extension Properties:

ExtensionMaterial TypeProperties SetTextures Loaded
KHR_materials_clearcoatMeshPhysicalMaterialclearcoat, clearcoatRoughnessclearcoatMap, clearcoatRoughnessMap, clearcoatNormalMap
KHR_materials_transmissionMeshPhysicalMaterialtransmissiontransmissionMap
KHR_materials_volumeMeshPhysicalMaterialthickness, attenuationDistance, attenuationColorthicknessMap
KHR_materials_iorMeshPhysicalMaterialior
KHR_materials_sheenMeshPhysicalMaterialsheenColor, sheenRoughness, sheensheenColorMap, sheenRoughnessMap
KHR_materials_unlitMeshBasicMaterialcolor, opacitymap

Compressed Asset Support

GLTFLoader supports multiple compression formats through external decoder libraries:

Draco Mesh Compression:

SVG
100%

Usage examples/misc_exporter_gltf.html486-492:

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/examples/jsm/libs/draco/');
loader.setDRACOLoader(dracoLoader);

KTX2 Texture Compression:

const ktx2Loader = new KTX2Loader()
  .setTranscoderPath('jsm/libs/basis/')
  .detectSupport(renderer);
loader.setKTX2Loader(ktx2Loader);

Meshopt Buffer Compression:

import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
loader.setMeshoptDecoder(MeshoptDecoder);

GLTFExporter

Core API

The GLTFExporter class converts three.js scenes to glTF format:

MethodParametersReturn TypePurpose
parse()input, onDone, onError, optionsvoidExport to glTF
parseAsync()input, optionsPromiseAsync version
register()callbackGLTFExporterRegister extension plugin
unregister()callbackGLTFExporterUnregister extension plugin
setTextureUtils()utilsGLTFExporterConfigure texture decompression

Export Options:

{
  binary: false,              // Output GLB vs GLTF
  trs: false,                 // Use translation/rotation/scale vs matrices
  onlyVisible: true,          // Skip hidden objects
  maxTextureSize: Infinity,   // Resize textures
  animations: [],             // AnimationClips to export
  includeCustomExtensions: false  // Export userData.gltfExtensions
}

Supported Input Types:

  • Scene - Single scene
  • Object3D - Any scene graph node
  • Array<Scene|Object3D> - Multiple scenes/objects

Output Format:

  • Binary mode (binary: true): Returns ArrayBuffer (.glb file)
  • JSON mode (binary: false): Returns glTF JSON object with embedded base64 buffer

Export Pipeline

GLTFExporter Export Pipeline

SVG
100%

Key GLTFWriter Methods in Export Pipeline:

MethodCalled ByPurposeOutput
writeAsync(input, onDone, options)GLTFExporter.parse()Main export orchestrationCalls onDone() with result
processInputAsync(input)writeAsync()Traverse input scenes/objectsPopulates json structure
processNode(object)processInputAsync()Convert Object3D to glTF nodejson.nodes[] entry
processMeshAsync(mesh)processNode()Convert Mesh to glTF meshjson.meshes[] entry
processMaterialAsync(material)processMeshAsync()Convert Material to glTF materialjson.materials[] entry
processTextureAsync(texture)processMaterialAsync()Convert Texture to glTF texturejson.textures[] entry
processImage(image, format, flipY)processTextureAsync()Encode image to PNG/JPEGjson.images[] entry
processSampler(map)processTextureAsync()Convert texture parametersjson.samplers[] entry
processAccessor(attribute, geometry)processMeshAsync()Convert BufferAttribute to accessorjson.accessors[] entry
processBufferView(attribute, componentType)processAccessor()Create bufferView from attribute datajson.bufferViews[] entry
processBuffer(buffer)processBufferView()Add binary data to merge listAppends to buffers[]

GLTFWriter Internal Structure

GLTFWriter orchestrates the export process, maintaining state for all glTF resources:

GLTFWriter State Structure:

class GLTFWriter {
  constructor() {
    this.plugins = [];           // Extension plugin instances
    this.options = {};           // Export options from parseAsync()
    this.pending = [];           // Async operations (Promise[])
    this.buffers = [];           // Binary data chunks (ArrayBuffer[])
    this.byteOffset = 0;         // Current buffer write position
    this.nodeMap = new Map();    // Object3D -> node index mapping
    this.skins = [];             // Skin definitions
    this.extensionsUsed = {};    // Extensions used in export
    this.extensionsRequired = {}; // Required extensions
    this.uids = new Map();       // BufferAttribute -> UID mapping
    this.uid = 0;                // UID counter
    
    this.cache = {               // Resource deduplication
      meshes: new Map(),         // mesh geometry+material key -> index
      attributes: new Map(),     // BufferAttribute UID -> accessor index
      attributesNormalized: new Map(), // normalized attribute cache
      materials: new Map(),      // Material -> material index
      textures: new Map(),       // Texture -> texture index
      images: new Map()          // Image -> { mimeType:flipY -> image index }
    };
    
    this.json = {                // Output glTF JSON structure
      asset: {
        version: '2.0',
        generator: 'THREE.GLTFExporter r' + REVISION
      }
    };
    
    this.textureUtils = null;    // WebGLTextureUtils or WebGPUTextureUtils
  }
}

Core Processing Methods:

MethodInputOutputPurpose
processBuffer(buffer)ArrayBuffer0 (buffer index)Adds buffer to buffers[] for merging
processBufferView(attribute, componentType, start, count, target)BufferAttribute, component type, range, target{id, byteLength}Creates entry in json.bufferViews[]
processBufferViewImage(blob)BlobPromise<number>Creates bufferView for image data
processAccessor(attribute, geometry, start, count)BufferAttribute, BufferGeometry, rangenumber (accessor index)Creates entry in json.accessors[] with min/max
processImage(image, format, flipY, mimeType)Image, format, flip flag, mime typenumber (image index)Encodes image to PNG/JPEG, adds to json.images[]
processSampler(map)Texturenumber (sampler index)Converts filter/wrap to glTF constants
processTextureAsync(map)TexturePromise<number>Processes texture, calls processImage() and processSampler()
processMaterialAsync(material)MaterialPromise<number>Converts to PBR material definition
processMeshAsync(mesh)MeshPromise<number>Converts geometry and material to glTF mesh
getUID(attribute, isRelativeCopy)BufferAttribute, flagnumberReturns unique ID for attribute deduplication
serializeUserData(object, objectDef)object, definitionvoidAdds userData to objectDef.extras
applyTextureTransform(mapDef, texture)texture definition, TexturevoidAdds KHR_texture_transform if needed

Buffer Data Layout

GLTFExporter uses the glTF data alignment requirements where bufferViews must be aligned to 4-byte boundaries:

GLTFWriter Buffer and Accessor Layout

SVG
100%

Padding Function: examples/jsm/exporters/GLTFExporter.js491-530

function getPaddedBufferSize(bufferSize) {
  return Math.ceil(bufferSize / 4) * 4;
}

function getPaddedArrayBuffer(arrayBuffer, paddingByte = 0) {
  const paddedLength = getPaddedBufferSize(arrayBuffer.byteLength);
  if (paddedLength !== arrayBuffer.byteLength) {
    const array = new Uint8Array(paddedLength);
    array.set(new Uint8Array(arrayBuffer));
    // Fill padding bytes
    for (let i = arrayBuffer.byteLength; i < paddedLength; i++) {
      array[i] = paddingByte;
    }
    return array.buffer;
  }
  return arrayBuffer;
}

Material Export

Material export converts three.js materials to glTF's PBR metallic-roughness workflow:

Property Mapping:

Three.js PropertyglTF PropertyNotes
color, opacitybaseColorFactorRGBA array [r, g, b, opacity]
metalnessmetallicFactor0.0 to 1.0
roughnessroughnessFactor0.0 to 1.0
mapbaseColorTextureWith texCoord channel
metalnessMap, roughnessMapmetallicRoughnessTextureMerged into single texture (B=metalness, G=roughness)
normalMapnormalTextureWith scale factor
emissive, emissiveMapemissiveFactor, emissiveTextureHDR emissive via extension
aoMapocclusionTextureWith strength
transparentalphaMode: "BLEND"vs OPAQUE or MASK
alphaTestalphaMode: "MASK", alphaCutoffThreshold value
side: DoubleSidedoubleSided: trueBoolean flag

Metallic-Roughness Texture Merging:

When a material has separate metalnessMap and roughnessMap, the exporter merges them into a single texture as required by glTF examples/jsm/exporters/GLTFExporter.js939-1045:

// Green channel = roughness, Blue channel = metalness
async buildMetalRoughTextureAsync(metalnessMap, roughnessMap) {
  // Create canvas with max dimensions
  const width = Math.max(metalness?.width || 0, roughness?.width || 0);
  const height = Math.max(metalness?.height || 0, roughness?.height || 0);
  const canvas = getCanvas();
  canvas.width = width;
  canvas.height = height;
  
  // Draw and extract channel data
  // Blue channel = metalness, Green channel = roughness
  for (let i = 2; i < data.length; i += 4) {
    composite.data[i] = metalnessValue; // Blue
  }
  for (let i = 1; i < data.length; i += 4) {
    composite.data[i] = roughnessValue; // Green
  }
  
  return mergedTexture;
}

Texture Processing

Texture export handles image encoding, decompression of compressed textures, and mipmap flattening:

SVG
100%

Texture Decompression:

When exporting compressed textures (e.g., from CompressedTexture), the exporter requires texture utilities to decompress them examples/jsm/exporters/GLTFExporter.js1048-1058:

// Must be called before exporting compressed textures
import * as WebGLTextureUtils from 'three/addons/utils/WebGLTextureUtils.js';
exporter.setTextureUtils(WebGLTextureUtils);

// Or for WebGPU:
import * as WebGPUTextureUtils from 'three/addons/utils/WebGPUTextureUtils.js';
exporter.setTextureUtils(WebGPUTextureUtils);

Image Encoding:

Images are encoded to PNG or JPEG using canvas examples/jsm/exporters/GLTFExporter.js1377-1489:

processImage(image, format, flipY, mimeType = 'image/png') {
  const canvas = getCanvas();
  canvas.width = Math.min(image.width, options.maxTextureSize);
  canvas.height = Math.min(image.height, options.maxTextureSize);
  
  const ctx = canvas.getContext('2d');
  if (flipY) {
    ctx.translate(0, canvas.height);
    ctx.scale(1, -1);
  }
  ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
  
  // Binary mode: store as bufferView
  // JSON mode: encode as data URI
  if (options.binary) {
    const blob = await getToBlobPromise(canvas, mimeType);
    imageDef.bufferView = await processBufferViewImage(blob);
  } else {
    imageDef.uri = ImageUtils.getDataURL(canvas, mimeType);
  }
}

Extension Export

Extension plugins for the exporter follow a pattern similar to the loader:

Exporter Extension Methods:

{
  name: string,  // Extension identifier
  
  // Called for each material
  writeMaterialAsync(material, materialDef),
  
  // Called for each texture  
  writeTexture(texture, textureDef),
  
  // Called for each node
  writeNode(object, nodeDef),
  
  // Called for each mesh
  writeMesh(mesh, meshDef)
}

Built-in Export Extensions:

ExtensionClassPurpose
KHR_lights_punctualGLTFLightExtensionExport DirectionalLight, PointLight, SpotLight
KHR_materials_unlitGLTFMaterialsUnlitExtensionExport MeshBasicMaterial as unlit
KHR_materials_transmissionGLTFMaterialsTransmissionExtensionExport transmission property
KHR_materials_volumeGLTFMaterialsVolumeExtensionExport volume properties
KHR_materials_iorGLTFMaterialsIorExtensionExport IOR property
KHR_materials_specularGLTFMaterialsSpecularExtensionExport specular workflow
KHR_materials_clearcoatGLTFMaterialsClearcoatExtensionExport clearcoat properties
KHR_materials_dispersionGLTFMaterialsDispersionExtensionExport dispersion
KHR_materials_iridescenceGLTFMaterialsIridescenceExtensionExport iridescence
KHR_materials_sheenGLTFMaterialsSheenExtensionExport sheen
KHR_materials_anisotropyGLTFMaterialsAnisotropyExtensionExport anisotropy
KHR_materials_emissive_strengthGLTFMaterialsEmissiveStrengthExtensionExport HDR emissive
EXT_materials_bumpGLTFMaterialsBumpExtensionExport bump maps
EXT_mesh_gpu_instancingGLTFMeshGpuInstancingExport InstancedMesh

Plugin Invocation:

Extensions are invoked via _invokeAllAsync() at specific points examples/jsm/exporters/GLTFExporter.js1723-1727:

await this._invokeAllAsync(async function(ext) {
  ext.writeMaterialAsync && await ext.writeMaterialAsync(material, materialDef);
});

Usage Examples

Basic Loading

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const loader = new GLTFLoader();

// Callback style
loader.load('model.gltf', (gltf) => {
  scene.add(gltf.scene);
  
  // Access animations
  if (gltf.animations.length) {
    const mixer = new THREE.AnimationMixer(gltf.scene);
    gltf.animations.forEach(clip => mixer.clipAction(clip).play());
  }
}, undefined, (error) => {
  console.error('Loading error:', error);
});

// Async style
const gltf = await loader.loadAsync('model.gltf');
scene.add(gltf.scene);

Loading with Compression

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';

// Configure Draco decoder
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/jsm/libs/draco/');

// Configure KTX2 decoder
const ktx2Loader = new KTX2Loader()
  .setTranscoderPath('/jsm/libs/basis/')
  .detectSupport(renderer);

const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
loader.setKTX2Loader(ktx2Loader);
loader.setMeshoptDecoder(MeshoptDecoder);

const gltf = await loader.loadAsync('compressed-model.glb');
scene.add(gltf.scene);

Basic Export

import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';

const exporter = new GLTFExporter();

// Export to JSON (.gltf)
exporter.parse(scene, (gltf) => {
  const output = JSON.stringify(gltf, null, 2);
  downloadJSON(output, 'scene.gltf');
}, (error) => {
  console.error('Export error:', error);
}, {
  binary: false,
  trs: false,
  onlyVisible: true
});

// Export to binary (.glb)
exporter.parse(scene, (glb) => {
  downloadBinary(glb, 'scene.glb');
}, undefined, {
  binary: true
});

Export with Options

import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
import * as WebGLTextureUtils from 'three/addons/utils/WebGLTextureUtils.js';

const exporter = new GLTFExporter();

// Required for compressed texture export
exporter.setTextureUtils(WebGLTextureUtils);

const options = {
  binary: true,              // Output GLB format
  trs: true,                 // Use TRS instead of matrix (required for animation)
  onlyVisible: true,         // Skip hidden objects
  maxTextureSize: 2048,      // Resize textures
  animations: [clip1, clip2] // Include specific animations
};

const glb = await exporter.parseAsync(scene, options);
downloadBinary(glb, 'scene.glb');

Custom Extension Registration

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

// Custom loader extension
class MyCustomExtension {
  constructor(parser) {
    this.parser = parser;
    this.name = 'MY_custom_extension';
  }
  
  extendMaterialParams(materialIndex, materialParams) {
    const materialDef = this.parser.json.materials[materialIndex];
    const extension = materialDef.extensions?.[this.name];
    if (!extension) return Promise.resolve();
    
    // Apply custom properties
    materialParams.userData.customData = extension.customData;
    return Promise.resolve();
  }
}

const loader = new GLTFLoader();
loader.register((parser) => new MyCustomExtension(parser));

const gltf = await loader.loadAsync('model.gltf');

GLB Binary Format

The .glb binary format packages JSON and binary data into a single file:

SVG
100%

Constants: examples/jsm/exporters/GLTFExporter.js370-376

const GLB_HEADER_BYTES = 12;
const GLB_HEADER_MAGIC = 0x46546C67;  // 'glTF'
const GLB_VERSION = 2;

const GLB_CHUNK_PREFIX_BYTES = 8;
const GLB_CHUNK_TYPE_JSON = 0x4E4F534A;  // 'JSON'
const GLB_CHUNK_TYPE_BIN = 0x004E4942;   // 'BIN\0'

GLB Construction: examples/jsm/exporters/GLTFExporter.js688-734

// Binary chunk
const binaryChunk = getPaddedArrayBuffer(bufferData, 0);
const binaryChunkPrefix = new DataView(new ArrayBuffer(8));
binaryChunkPrefix.setUint32(0, binaryChunk.byteLength, true);
binaryChunkPrefix.setUint32(4, GLB_CHUNK_TYPE_BIN, true);

// JSON chunk (padded with spaces)
const jsonChunk = getPaddedArrayBuffer(stringToArrayBuffer(JSON.stringify(json)), 0x20);
const jsonChunkPrefix = new DataView(new ArrayBuffer(8));
jsonChunkPrefix.setUint32(0, jsonChunk.byteLength, true);
jsonChunkPrefix.setUint32(4, GLB_CHUNK_TYPE_JSON, true);

// Header
const header = new ArrayBuffer(12);
const headerView = new DataView(header);
headerView.setUint32(0, GLB_HEADER_MAGIC, true);
headerView.setUint32(4, GLB_VERSION, true);
const totalByteLength = 12 + 8 + jsonChunk.byteLength + 8 + binaryChunk.byteLength;
headerView.setUint32(8, totalByteLength, true);

// Concatenate all chunks
const glbBlob = new Blob([header, jsonChunkPrefix, jsonChunk, binaryChunkPrefix, binaryChunk]);

Performance Considerations

Loader Optimizations

Exporter Optimizations

  • Resource deduplication: GLTFWriter.cache prevents duplicate processing of shared geometries, materials, and textures examples/jsm/exporters/GLTFExporter.js612-619
  • Buffer merging: All binary data merged into single buffer to reduce HTTP requests
  • Texture compression: Supports exporting compressed textures when available
  • Geometry sharing: Multiple meshes referencing same geometry only export geometry once

Limitations and Constraints

Loader Limitations

Exporter Limitations