Back to UI/UX Design

threejs-loaders

threejs3d graphicsasset loadinggltftextureswebgljavascriptloading manager
2.4k🕒 2026-01-19Source ↗

Install this skill

npx skills add cloudai-x/threejs-skills

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

The threejs-loaders skill provides standardized patterns for importing 3D models, textures, and environmental data into web scenes. It manages the asynchronous retrieval of assets through dedicated classes like GLTFLoader, TextureLoader, and RGBELoader. By implementing the LoadingManager, developers can centralize progress tracking across multiple parallel requests, ensuring synchronization before initialization. The skill covers essential configuration for texture mapping—including color spaces, filtering modes, and wrapping—as well as the advanced generation of prefiltered environment maps via PMREMGenerator for physically based rendering. This toolkit serves as the primary interface for integrating external binary and image files into a functional Three.js project, abstracting the complexity of data parsing and resource lifecycle management for efficient, high-performance rendering.

When to Use This Skill

  • Integrating 3D character models exported from Blender into a web app
  • Creating a unified preloader screen for a browser-based 3D scene
  • Setting up high-fidelity image-based lighting using HDRI files
  • Optimizing texture filtering and mipmapping for sharp visual output

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • How do I load a GLB file in Three.js?
  • Set up a loading progress bar for my 3D scene
  • Configure a high-resolution environment map using an HDR image
  • Apply specific texture wrapping and filtering to a Three.js material
  • Handle animations inside a loaded GLTF file

Pro Tips

  • 💡Always use `THREE.LoadingManager` for projects with multiple assets; it centralizes progress tracking and error handling, making your code cleaner and more robust.
  • 💡Optimize your 3D models (e.g., using `gltf-pipeline` for GLTF) and textures (e.g., compressed formats like KTX2) *before* loading to minimize file sizes and improve load times significantly.
  • 💡Implement a visual loading indicator or progress bar early in development to provide immediate feedback to users, especially for larger assets, preventing perceived delays.

What this skill does

  • Importing and parsing GLTF/GLB binary models with animation support
  • Managing collective loading progress for batches of disparate assets
  • Configuring texture properties like SRGB color space, anisotropy, and wrapping
  • Processing HDR and EXR environmental maps for global illumination
  • Pre-calculating radiance data for PBR materials using PMREMGenerator

When not to use it

  • Generating 3D geometry procedurally through mathematical algorithms
  • Handling proprietary or non-standard 3D formats not supported by Three.js loaders

Example workflow

  1. Initialize a LoadingManager to track total asset progress
  2. Instantiate specific loaders like GLTFLoader or RGBELoader
  3. Assign the manager to each loader instance
  4. Initiate asset requests and update UI based on manager callbacks
  5. Configure textures and apply scene environment variables once loaded

Prerequisites

  • Basic knowledge of Three.js Scene and Renderer setup
  • Node.js environment with three.js installed as a dependency

Pitfalls & limitations

  • !Forgetting to set texture colorSpace to SRGBColorSpace often results in washed-out visuals
  • !Loading textures and models without disposing of them properly can lead to GPU memory leaks
  • !Misconfiguring texture filtering settings can cause significant performance hits on mobile devices

FAQ

Why is my loaded texture appearing black or missing color?
Check your colorSpace property. Setting it to THREE.SRGBColorSpace is usually necessary for albedo maps to display colors accurately.
Can I track the loading progress of a single image?
No, standard image loading in Three.js does not support progress callbacks; the LoadingManager is required to aggregate progress across multiple assets.
Why should I use PMREMGenerator instead of just applying an HDR texture directly?
PMREMGenerator prefilters the environment map to provide accurate reflections and lighting for physically based materials, which a raw HDR image cannot do alone.

How it compares

Using this skill enforces consistent loading patterns and error handling compared to writing raw XHR or fetch requests, which lack built-in Three.js format parsing and manager integration.

Source & trust

2.4k stars🕒 Updated 2026-01-19
📄 Full skill instructions — original source: cloudai-x/threejs-skills
# Three.js Loaders

## Quick Start

import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";

// Load GLTF model
const loader = new GLTFLoader();
loader.load("model.glb", (gltf) => {
scene.add(gltf.scene);
});

// Load texture
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("texture.jpg");


## LoadingManager

Coordinate multiple loaders and track progress.

const manager = new THREE.LoadingManager();

// Callbacks
manager.onStart = (url, loaded, total) => {
console.log(Started loading: ${url});
};

manager.onLoad = () => {
console.log("All assets loaded!");
startGame();
};

manager.onProgress = (url, loaded, total) => {
const progress = (loaded / total) * 100;
console.log(Loading: ${progress.toFixed(1)}%);
updateProgressBar(progress);
};

manager.onError = (url) => {
console.error(Error loading: ${url});
};

// Use manager with loaders
const textureLoader = new THREE.TextureLoader(manager);
const gltfLoader = new GLTFLoader(manager);

// Load assets
textureLoader.load("texture1.jpg");
textureLoader.load("texture2.jpg");
gltfLoader.load("model.glb");
// onLoad fires when ALL are complete


## Texture Loading

### TextureLoader

const loader = new THREE.TextureLoader();

// Callback style
loader.load(
"texture.jpg",
(texture) => {
// onLoad
material.map = texture;
material.needsUpdate = true;
},
undefined, // onProgress - not supported for image loading
(error) => {
// onError
console.error("Error loading texture", error);
},
);

// Synchronous (returns texture, loads async)
const texture = loader.load("texture.jpg");
material.map = texture;


### Texture Configuration

const texture = loader.load("texture.jpg", (tex) => {
// Color space (important for color accuracy)
tex.colorSpace = THREE.SRGBColorSpace; // For color/albedo maps
// tex.colorSpace = THREE.LinearSRGBColorSpace; // For data maps (normal, roughness)

// Wrapping
tex.wrapS = THREE.RepeatWrapping;
tex.wrapT = THREE.RepeatWrapping;
// ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping

// Repeat/offset
tex.repeat.set(2, 2);
tex.offset.set(0.5, 0.5);
tex.rotation = Math.PI / 4;
tex.center.set(0.5, 0.5);

// Filtering
tex.minFilter = THREE.LinearMipmapLinearFilter; // Default
tex.magFilter = THREE.LinearFilter; // Default
// NearestFilter - pixelated
// LinearFilter - smooth
// LinearMipmapLinearFilter - smooth with mipmaps

// Anisotropic filtering (sharper at angles)
tex.anisotropy = renderer.capabilities.getMaxAnisotropy();

// Flip Y (usually true for standard textures)
tex.flipY = true;

tex.needsUpdate = true;
});


### CubeTextureLoader

For environment maps and skyboxes.

const loader = new THREE.CubeTextureLoader();

// Load 6 faces
const cubeTexture = loader.load([
"px.jpg",
"nx.jpg", // positive/negative X
"py.jpg",
"ny.jpg", // positive/negative Y
"pz.jpg",
"nz.jpg", // positive/negative Z
]);

// Use as background
scene.background = cubeTexture;

// Use as environment map
scene.environment = cubeTexture;
material.envMap = cubeTexture;


### HDR/EXR Loading

import { RGBELoader } from "three/addons/loaders/RGBELoader.js";
import { EXRLoader } from "three/addons/loaders/EXRLoader.js";

// HDR
const rgbeLoader = new RGBELoader();
rgbeLoader.load("environment.hdr", (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
scene.background = texture;
});

// EXR
const exrLoader = new EXRLoader();
exrLoader.load("environment.exr", (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = texture;
});


### PMREMGenerator

Generate prefiltered environment maps for PBR.

import { RGBELoader } from "three/addons/loaders/RGBELoader.js";

const pmremGenerator = new THREE.PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();

new RGBELoader().load("environment.hdr", (texture) => {
const envMap = pmremGenerator.fromEquirectangular(texture).texture;

scene.environment = envMap;
scene.background = envMap;

texture.dispose();
pmremGenerator.dispose();
});


## GLTF/GLB Loading

The most common 3D format for web.

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

const loader = new GLTFLoader();

loader.load("model.glb", (gltf) => {
// The loaded scene
const model = gltf.scene;
scene.add(model);

// Animations
const animations = gltf.animations;
if (animations.length > 0) {
const mixer = new THREE.AnimationMixer(model);
animations.forEach((clip) => {
mixer.clipAction(clip).play();
});
}

// Cameras (if any)
const cameras = gltf.cameras;

// Asset info
console.log(gltf.asset); // Version, generator, etc.

// User data from Blender/etc
console.log(gltf.userData);
});


### GLTF with Draco Compression

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

const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath(
"https://www.gstatic.com/draco/versioned/decoders/1.5.6/",
);
dracoLoader.preload();

const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(dracoLoader);

gltfLoader.load("compressed-model.glb", (gltf) => {
scene.add(gltf.scene);
});


### GLTF with KTX2 Textures

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

const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath(
"https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/libs/basis/",
);
ktx2Loader.detectSupport(renderer);

const gltfLoader = new GLTFLoader();
gltfLoader.setKTX2Loader(ktx2Loader);

gltfLoader.load("model-with-ktx2.glb", (gltf) => {
scene.add(gltf.scene);
});


### Process GLTF Content

loader.load("model.glb", (gltf) => {
const model = gltf.scene;

// Enable shadows
model.traverse((child) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});

// Find specific mesh
const head = model.getObjectByName("Head");

// Adjust materials
model.traverse((child) => {
if (child.isMesh && child.material) {
child.material.envMapIntensity = 0.5;
}
});

// Center and scale
const box = new THREE.Box3().setFromObject(model);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());

model.position.sub(center);
const maxDim = Math.max(size.x, size.y, size.z);
model.scale.setScalar(1 / maxDim);

scene.add(model);
});


## Other Model Formats

### OBJ + MTL

import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
import { MTLLoader } from "three/addons/loaders/MTLLoader.js";

const mtlLoader = new MTLLoader();
mtlLoader.load("model.mtl", (materials) => {
materials.preload();

const objLoader = new OBJLoader();
objLoader.setMaterials(materials);
objLoader.load("model.obj", (object) => {
scene.add(object);
});
});


### FBX

import { FBXLoader } from "three/addons/loaders/FBXLoader.js";

const loader = new FBXLoader();
loader.load("model.fbx", (object) => {
// FBX often has large scale
object.scale.setScalar(0.01);

// Animations
const mixer = new THREE.AnimationMixer(object);
object.animations.forEach((clip) => {
mixer.clipAction(clip).play();
});

scene.add(object);
});


### STL

import { STLLoader } from "three/addons/loaders/STLLoader.js";

const loader = new STLLoader();
loader.load("model.stl", (geometry) => {
const material = new THREE.MeshStandardMaterial({ color: 0x888888 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});


### PLY

import { PLYLoader } from "three/addons/loaders/PLYLoader.js";

const loader = new PLYLoader();
loader.load("model.ply", (geometry) => {
geometry.computeVertexNormals();
const material = new THREE.MeshStandardMaterial({ vertexColors: true });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});


## Async/Promise Loading

### Promisified Loader

function loadModel(url) {
return new Promise((resolve, reject) => {
loader.load(url, resolve, undefined, reject);
});
}

// Usage
async function init() {
try {
const gltf = await loadModel("model.glb");
scene.add(gltf.scene);
} catch (error) {
console.error("Failed to load model:", error);
}
}


### Load Multiple Assets

async function loadAssets() {
const [modelGltf, envTexture, colorTexture] = await Promise.all([
loadGLTF("model.glb"),
loadRGBE("environment.hdr"),
loadTexture("color.jpg"),
]);

scene.add(modelGltf.scene);
scene.environment = envTexture;
material.map = colorTexture;
}

// Helper functions
function loadGLTF(url) {
return new Promise((resolve, reject) => {
new GLTFLoader().load(url, resolve, undefined, reject);
});
}

function loadRGBE(url) {
return new Promise((resolve, reject) => {
new RGBELoader().load(
url,
(texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
resolve(texture);
},
undefined,
reject,
);
});
}

function loadTexture(url) {
return new Promise((resolve, reject) => {
new THREE.TextureLoader().load(url, resolve, undefined, reject);
});
}


## Caching

### Built-in Cache

// Enable cache
THREE.Cache.enabled = true;

// Clear cache
THREE.Cache.clear();

// Manual cache management
THREE.Cache.add("key", data);
THREE.Cache.get("key");
THREE.Cache.remove("key");


### Custom Asset Manager

class AssetManager {
constructor() {
this.textures = new Map();
this.models = new Map();
this.gltfLoader = new GLTFLoader();
this.textureLoader = new THREE.TextureLoader();
}

async loadTexture(key, url) {
if (this.textures.has(key)) {
return this.textures.get(key);
}

const texture = await new Promise((resolve, reject) => {
this.textureLoader.load(url, resolve, undefined, reject);
});

this.textures.set(key, texture);
return texture;
}

async loadModel(key, url) {
if (this.models.has(key)) {
return this.models.get(key).clone();
}

const gltf = await new Promise((resolve, reject) => {
this.gltfLoader.load(url, resolve, undefined, reject);
});

this.models.set(key, gltf.scene);
return gltf.scene.clone();
}

dispose() {
this.textures.forEach((t) => t.dispose());
this.textures.clear();
this.models.clear();
}
}

// Usage
const assets = new AssetManager();
const texture = await assets.loadTexture("brick", "brick.jpg");
const model = await assets.loadModel("tree", "tree.glb");


## Loading from Different Sources

### Data URL / Base64

const loader = new THREE.TextureLoader();
const texture = loader.load("data:image/png;base64,iVBORw0KGgo...");


### Blob URL

async function loadFromBlob(blob) {
const url = URL.createObjectURL(blob);
const texture = await loadTexture(url);
URL.revokeObjectURL(url);
return texture;
}


### ArrayBuffer

// From fetch
const response = await fetch("model.glb");
const buffer = await response.arrayBuffer();

// Parse with loader
const loader = new GLTFLoader();
loader.parse(buffer, "", (gltf) => {
scene.add(gltf.scene);
});


### Custom Path/URL

// Set base path
loader.setPath("assets/models/");
loader.load("model.glb"); // Loads from assets/models/model.glb

// Set resource path (for textures referenced in model)
loader.setResourcePath("assets/textures/");

// Custom URL modifier
manager.setURLModifier((url) => {
return https://cdn.example.com/${url};
});


## Error Handling

// Graceful fallback
async function loadWithFallback(primaryUrl, fallbackUrl) {
try {
return await loadModel(primaryUrl);
} catch (error) {
console.warn(Primary failed, trying fallback: ${error});
return await loadModel(fallbackUrl);
}
}

// Retry logic
async function loadWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await loadModel(url);
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
}
}
}

// Timeout
async function loadWithTimeout(url, timeout = 30000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);

try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return response;
} catch (error) {
if (error.name === "AbortError") {
throw new Error("Loading timed out");
}
throw error;
}
}


## Performance Tips

1. **Use compressed formats**: DRACO for geometry, KTX2/Basis for textures
2. **Load progressively**: Show placeholders while loading
3. **Lazy load**: Only load what's needed
4. **Use CDN**: Faster asset delivery
5. **Enable cache**: THREE.Cache.enabled = true

// Progressive loading with placeholder
const placeholder = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial({ wireframe: true }),
);
scene.add(placeholder);

loadModel("model.glb").then((gltf) => {
scene.remove(placeholder);
scene.add(gltf.scene);
});


## See Also

- threejs-textures - Texture configuration
- threejs-animation - Playing loaded animations
- threejs-materials - Material from loaded models

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/threejs-loaders/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/cloudai-x/threejs-skills/threejs-loaders/SKILL.md
  • Cursor: ~/.cursor/skills/cloudai-x/threejs-skills/threejs-loaders/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/cloudai-x/threejs-skills/threejs-loaders/SKILL.md

🚀 Install with CLI:
npx skills add cloudai-x/threejs-skills

Read the Master Guide: Mastering Agent Skills

Recommended Rules

View more rules

Recommended Workflows

View more workflows

Recommended MCP Servers

View more MCP servers

Take It Further

Maximize your productivity with these powerful resources

📋

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
📖

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid ui/ux design issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under UI/UX Design and is published by CloudAI-X, maintained in cloudai-x/threejs-skills.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.