threejs-loaders

threejs-loaders

熱門

Three.js 資產載入 - GLTF、紋理、圖片、模型、非同步模式。用於載入 3D 模型、紋理、HDR 環境或管理載入進度時使用。

2616星標
300分支
更新於 2026/7/9
SKILL.md
readonlyread-only
name
threejs-loaders
description

Three.js 資產載入 - GLTF、紋理、圖片、模型、非同步模式。用於載入 3D 模型、紋理、HDR 環境或管理載入進度時使用。

Three.js Loaders

快速開始

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

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

// 載入紋理
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load("texture.jpg");

LoadingManager

協調多個載入器並追蹤進度。

const manager = new THREE.LoadingManager();

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

manager.onLoad = () => {
  console.log("所有資產載入完成!");
  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}`);
};

// 將 manager 用於載入器
const textureLoader = new THREE.TextureLoader(manager);
const gltfLoader = new GLTFLoader(manager);

// 載入資產
textureLoader.load("texture1.jpg");
textureLoader.load("texture2.jpg");
gltfLoader.load("model.glb");
// 當所有資產載入完成時觸發 onLoad

紋理載入

TextureLoader

const loader = new THREE.TextureLoader();

// 回呼風格
loader.load(
  "texture.jpg",
  (texture) => {
    // onLoad
    material.map = texture;
    material.needsUpdate = true;
  },
  undefined, // onProgress - 圖片載入不支援
  (error) => {
    // onError
    console.error("載入紋理時發生錯誤", error);
  },
);

// 同步(回傳紋理,非同步載入)
const texture = loader.load("texture.jpg");
material.map = texture;

紋理設定

const texture = loader.load("texture.jpg", (tex) => {
  // 色彩空間(對色彩準確性很重要)
  tex.colorSpace = THREE.SRGBColorSpace; // 用於顏色/漫反射貼圖
  // tex.colorSpace = THREE.LinearSRGBColorSpace;  // 用於數據貼圖(法線、粗糙度)

  // 環繞模式
  tex.wrapS = THREE.RepeatWrapping;
  tex.wrapT = THREE.RepeatWrapping;
  // ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping

  // 重複/偏移
  tex.repeat.set(2, 2);
  tex.offset.set(0.5, 0.5);
  tex.rotation = Math.PI / 4;
  tex.center.set(0.5, 0.5);

  // 濾波
  tex.minFilter = THREE.LinearMipmapLinearFilter; // 預設
  tex.magFilter = THREE.LinearFilter; // 預設
  // NearestFilter - 像素化
  // LinearFilter - 平滑
  // LinearMipmapLinearFilter - 平滑含 mipmap

  // 各向異性濾波(傾斜角度更清晰)
  tex.anisotropy = renderer.capabilities.getMaxAnisotropy();

  // 翻轉 Y(標準紋理通常為 true)
  tex.flipY = true;

  tex.needsUpdate = true;
});

CubeTextureLoader

用於環境貼圖和天空盒。

const loader = new THREE.CubeTextureLoader();

// 載入六個面
const cubeTexture = loader.load([
  "px.jpg",
  "nx.jpg", // 正/負 X
  "py.jpg",
  "ny.jpg", // 正/負 Y
  "pz.jpg",
  "nz.jpg", // 正/負 Z
]);

// 用作背景
scene.background = cubeTexture;

// 用作環境貼圖
scene.environment = cubeTexture;
material.envMap = cubeTexture;

HDR/EXR 載入

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

為 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 載入

網頁中最常見的 3D 格式。

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

const loader = new GLTFLoader();

loader.load("model.glb", (gltf) => {
  // 載入的場景
  const model = gltf.scene;
  scene.add(model);

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

  // 相機(如果有的話)
  const cameras = gltf.cameras;

  // 資產資訊
  console.log(gltf.asset); // 版本、產生器等

  // 來自 Blender 等的使用者資料
  console.log(gltf.userData);
});

使用 Draco 壓縮的 GLTF

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);
});

使用 KTX2 紋理的 GLTF

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/three@0.160.0/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);
});

處理 GLTF 內容

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

  // 啟用陰影
  model.traverse((child) => {
    if (child.isMesh) {
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });

  // 尋找特定網格
  const head = model.getObjectByName("Head");

  // 調整材質
  model.traverse((child) => {
    if (child.isMesh && child.material) {
      child.material.envMapIntensity = 0.5;
    }
  });

  // 置中與縮放
  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);
});

其他模型格式

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 通常比例較大
  object.scale.setScalar(0.01);

  // 動畫
  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);
});

非同步/Promise 載入

Promise 化的載入器

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

// 使用方式
async function init() {
  try {
    const gltf = await loadModel("model.glb");
    scene.add(gltf.scene);
  } catch (error) {
    console.error("載入模型失敗:", error);
  }
}

載入多個資產

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;
}

// 輔助函式
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);
  });
}

快取

內建快取

// 啟用快取
THREE.Cache.enabled = true;

// 清除快取
THREE.Cache.clear();

// 手動快取管理
THREE.Cache.add("key", data);
THREE.Cache.get("key");
THREE.Cache.remove("key");

自訂資產管理器

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();
  }
}

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

從不同來源載入

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

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

// 使用載入器解析
const loader = new GLTFLoader();
loader.parse(buffer, "", (gltf) => {
  scene.add(gltf.scene);
});

自訂路徑/URL

// 設定基礎路徑
loader.setPath("assets/models/");
loader.load("model.glb"); // 從 assets/models/model.glb 載入

// 設定資源路徑(用於模型中引用的紋理)
loader.setResourcePath("assets/textures/");

// 自訂 URL 修飾器
manager.setURLModifier((url) => {
  return `https://cdn.example.com/${url}`;
});

錯誤處理

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

// 重試邏輯
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)));
    }
  }
}

// 超時
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("載入超時");
    }
    throw error;
  }
}

效能提示

  1. 使用壓縮格式:幾何用 DRACO,紋理用 KTX2/Basis
  2. 漸進式載入:載入時顯示佔位符
  3. 延遲載入:只載入需要的內容
  4. 使用 CDN:更快的資產傳遞
  5. 啟用快取THREE.Cache.enabled = true
// 漸進式載入含佔位符
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);
});

另請參閱

  • threejs-textures - 紋理設定
  • threejs-animation - 播放載入的動畫
  • threejs-materials - 從載入的模型取得材質