mcpField
T
threejs-loaders

threejs-loaders

github.com/cloudai-x開発3.8k13.5k 回インストール1 週間前に更新
リポジトリ

threejs-loaders について

Three.js 開発者向けのリソース読み込み解説スキルです。モデル、テクスチャ、環境ファイルの読み込みに加え、非同期処理と WebGL・WebGPU の使い方を扱います。

主な機能

  1. 01複数ローダーの進行状況を管理
  2. 02glTF、OBJ、FBX、STL、PLY、USD の読み込み
  3. 03テクスチャと HDR・EXR 環境の読み込み
  4. 04圧縮アセットと独自データソースへの対応
  5. 05クローン、キャッシュ、破棄の解説

活用シーン

  1. 013D モデルの読み込みと表示
  2. 02モデル、環境、テクスチャの並列読み込み
  3. 03圧縮 glTF 用デコーダーの設定
  4. 04既存のバイナリデータからモデルを解析

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/cloudai-x/threejs-skills --skill threejs-loaders

インストール後、Claude Code で/threejs-loadersを入力して読み込まれたか確認します。

詳細

Three.js ローダー

three.js r186 を対象としています。サンプルでは WebGLRenderer を使用します。WebGPU と TSL のセクションでは WebGPURenderer を使用します。

クイックスタート

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

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.environment = new THREE.PMREMGenerator(renderer).fromScene(new RoomEnvironment(), 0.04).texture; // glTF PBR materials render black without lights or an environment

const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(2, 1.5, 3);
camera.lookAt(0, 0, 0);

const gltf = await new GLTFLoader().loadAsync("model.glb");
scene.add(gltf.scene);

function animate() {
  renderer.render(scene, camera);
}

renderer.setAnimationLoop(animate);

LoadingManager

1 つのマネージャーで複数のローダーをまとめて管理し、統合された進捗状況を報告できます。loadAsync は引き続き失敗時に reject され、同時に manager.onError も呼び出されます。

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

const manager = new THREE.LoadingManager();

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

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

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

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

const gltfLoader = new GLTFLoader(manager);
const textureLoader = new THREE.TextureLoader(manager);

// onLoad fires when every tracked item has finished
await Promise.all([
  gltfLoader.loadAsync("model.glb"),
  textureLoader.loadAsync("texture.jpg"),
  textureLoader.loadAsync("normal.jpg"),
]);

manager.abort() は、FileLoader 経由で読み込むローダー(モデル、HDR、EXR、KTX2)の処理中のリクエストをキャンセルします。TextureLoader のように HTML 画像要素を介して読み込むローダーは、この操作を無視します。

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

const manager = new THREE.LoadingManager();
const timeoutId = setTimeout(() => manager.abort(), 30000);

try {
  const gltf = await new GLTFLoader(manager).loadAsync("model.glb");
  scene.add(gltf.scene);
} catch (error) {
  console.error(error);
} finally {
  clearTimeout(timeoutId);
}

テクスチャの読み込み

TextureLoader.load() はテクスチャをすぐに返し、画像のデコードが完了するとその内容を反映します。テクスチャをすぐに割り当てれば、最初のレンダリングで反映されます。テクスチャマップなしですでにレンダリングしたマテリアルにマップを追加する場合は、material.needsUpdate = true を設定する必要があります。画像の読み込みでは onProgress は呼び出されません。

new THREE.TextureLoader().load(
  "texture.jpg",
  (map) => {
    map.colorSpace = THREE.SRGBColorSpace; // a color map; scalar maps keep NoColorSpace
    mesh.material.map = map;
    mesh.material.needsUpdate = true; // the material rendered without a map before
  },
  undefined, // onProgress: not supported for image loading
  (error) => {
    console.error("Could not load texture", error);
  },
);
テクスチャの設定
const map = new THREE.TextureLoader().load("color.jpg");
map.colorSpace = THREE.SRGBColorSpace; // LDR color images; scalar maps keep NoColorSpace

map.wrapS = THREE.RepeatWrapping; // ClampToEdgeWrapping (default), RepeatWrapping, MirroredRepeatWrapping
map.wrapT = THREE.RepeatWrapping;
map.repeat.set(2, 2);
map.offset.set(0.5, 0.5);
map.rotation = Math.PI / 4;
map.center.set(0.5, 0.5);

map.minFilter = THREE.LinearMipmapLinearFilter; // default
map.magFilter = THREE.LinearFilter; // default; NearestFilter for a pixelated look
map.anisotropy = renderer.capabilities.getMaxAnisotropy(); // sharper textures at grazing angles

mesh.material.map = map;
// wrap, filters, anisotropy, colorSpace and flipY (true by default) apply at upload: set them before the first render or set map.needsUpdate = true; repeat, offset, rotation and center apply every frame

色空間:LDR カラー画像(アルベド、発光)は SRGBColorSpace にします。スカラーのマップ(法線、バンプ、ラフネス、メタルネス、AO、アルファ)はデフォルトの NoColorSpace のままにします。GLTFLoader は読み込むテクスチャに正しい色空間を設定し、以下の HDR ローダーは LinearSRGBColorSpace を自ら設定します。glTF モデルのマテリアル用に自分で読み込むテクスチャでは、GLTFLoader が読み込むテクスチャと一致させるため、map.flipY = false が必要です。

CubeTextureLoader

スカイボックスやキューブ環境には、+X、-X、+Y、-Y、+Z、-Z の順に 6 面を指定します。ローダーが sRGB を自動設定します。

const cubeTexture = await new THREE.CubeTextureLoader().loadAsync([
  "px.jpg",
  "nx.jpg",
  "py.jpg",
  "ny.jpg",
  "pz.jpg",
  "nz.jpg",
]);

scene.background = cubeTexture;
scene.environment = cubeTexture;

HDR 環境

HDRLoader(Radiance .hdr)と EXRLoader は、浮動小数点の正距円筒テクスチャを返します。どちらのレンダラーも正距円筒環境を自動的に事前フィルタリングするため、ファイルには PMREM 処理は不要です。mapping を設定して割り当ててください。

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

const env = await new HDRLoader().loadAsync("environment.hdr");
env.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = env;
scene.background = env; // without mapping, a background texture draws as a flat screen quad

const exr = await new EXRLoader().loadAsync("environment.exr");
exr.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = exr;

環境の明るさを下げるには scene.environmentIntensity を使用し、回転させるには scene.environmentRotation を使用します。material.envMapIntensity が調整するのはマテリアル独自の envMap のみです。scene.environment でライティングされたモデルでは、この値は null のままです。

UltraHDRLoader は、JPEG のファイルサイズで HDR のダイナミックレンジを実現するゲインマップ付き JPEG を、同じ方法で読み込みます。

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

const env = await new UltraHDRLoader().loadAsync("environment.jpg");
env.mapping = THREE.EquirectangularReflectionMapping;
scene.environment = env;

ファイルではなく、生成した環境には PMREMGenerator を使用してください。

import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";

const pmremGenerator = new THREE.PMREMGenerator(renderer);
const room = new RoomEnvironment();
scene.environment = pmremGenerator.fromScene(room, 0.04).texture;
room.dispose(); pmremGenerator.dispose(); // the room's geometries and materials, then the generator

glTF と GLB

ウェブ配信の主要なフォーマットです。読み込み結果には scene、animations、cameras、asset(バージョン、生成ツール)、userData(作成時のカスタムデータ)が含まれます。

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

const gltf = await new GLTFLoader().loadAsync("model.glb");
scene.add(gltf.scene);

if (gltf.animations.length > 0) {
  const mixer = new THREE.AnimationMixer(gltf.scene);
  mixer.clipAction(gltf.animations[0]).play();
  const timer = new THREE.Timer();
  timer.connect(document);

  function animate() {
    timer.update();
    mixer.update(timer.getDelta());
    renderer.render(scene, camera);
  }

  renderer.setAnimationLoop(animate);
}
Draco、meshopt、KTX2 圧縮

Draco デコーダーと Basis トランスコーダーは、デフォルトで three パッケージから読み込まれます。自分でホストする場合に限り setDecoderPath/setTranscoderPath を呼び出し、使用する three のリリースに対応したパスを指定してください。EXT_meshopt_compression または KHR_meshopt_compression を必要とするファイルは、setMeshoptDecoder が設定されていないと読み込みに失敗します。

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

const gltfLoader = new GLTFLoader()
  .setDRACOLoader(new DRACOLoader())
  .setMeshoptDecoder(MeshoptDecoder)
  .setKTX2Loader(new KTX2Loader().detectSupport(renderer));

const gltf = await gltfLoader.loadAsync("compressed-model.glb");
scene.add(gltf.scene);
読み込んだコンテンツの処理
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";

const gltf = await new GLTFLoader().loadAsync("model.glb");
const model = gltf.scene;

model.traverse((child) => {
  if (child.isMesh) {
    child.castShadow = true; // shadows also need renderer.shadowMap.enabled = true and a light with castShadow
    child.receiveShadow = true;
  }
});

model.getObjectByName("Head"); // look up named nodes

scene.environmentIntensity = 0.5; // dim scene.environment for this scene

// Center the model and scale it into a unit box
const box = new THREE.Box3().setFromObject(model);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
const scale = 1 / Math.max(size.x, size.y, size.z);
model.scale.setScalar(scale);
model.position.copy(center).multiplyScalar(-scale); // the offset is scaled too

scene.add(model);

その他のモデル形式

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

const materials = await new MTLLoader().loadAsync("model.mtl");
materials.preload();

const object = await new OBJLoader().setMaterials(materials).loadAsync("model.obj");
scene.add(object);
FBX
import { FBXLoader } from "three/addons/loaders/FBXLoader.js";

const object = await new FBXLoader().loadAsync("model.fbx");
object.scale.setScalar(0.01); // FBX files are often exported in centimeters

const mixer = new THREE.AnimationMixer(object); // call mixer.update(timer.getDelta()) every frame, as in the glTF example
if (object.animations.length > 0) mixer.clipAction(object.animations[0]).play();

scene.add(object);
STL
import { STLLoader } from "three/addons/loaders/STLLoader.js";

const geometry = await new STLLoader().loadAsync("model.stl");
scene.add(new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color: 0x888888 })));
scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 1));
scene.add(new THREE.DirectionalLight(0xffffff, 2)); // lit materials render black without lights; this one shines down from (0, 1, 0)
PLY

ローダーはファイルのデータ型をそのまま反映するため、double プロパティは Float64Array を基盤とする属性として読み込まれます。WebGLRenderer はこれをアップロードできないため、レンダリング前に Float32BufferAttribute に変換してください。

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

const geometry = await new PLYLoader().loadAsync("model.ply");

for (const name of Object.keys(geometry.attributes)) {
  const attribute = geometry.getAttribute(name);
  if (attribute.array instanceof Float64Array) {
    geometry.setAttribute(name, new THREE.Float32BufferAttribute(attribute.array, attribute.itemSize));
  }
}

if (!geometry.hasAttribute("normal")) geometry.computeVertexNormals(); // keep the file's own normals
const material = new THREE.MeshStandardMaterial({ vertexColors: geometry.hasAttribute("color") }); // vertexColors without a color attribute renders black
scene.add(new THREE.Mesh(geometry, material));
scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 1));
scene.add(new THREE.DirectionalLight(0xffffff, 2)); // lit materials render black without lights; this one shines down from (0, 1, 0)
USD
import { USDLoader } from "three/addons/loaders/USDLoader.js";

const model = await new USDLoader().loadAsync("model.usdz"); // USD, USDA, USDC and USDZ archives
scene.add(model);

非同期パターンとカスタムソース

互いに依存しないアセットは並行して読み込みます。

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

const [model, environment, colorMap] = await Promise.all([
  new GLTFLoader().loadAsync("model.glb"),
  new HDRLoader().loadAsync("environment.hdr"),
  new THREE.TextureLoader().loadAsync("color.jpg"),
]);

environment.mapping = THREE.EquirectangularReflectionMapping;
colorMap.colorSpace = THREE.SRGBColorSpace;

scene.add(model.scene);
scene.environment = environment;

すでに手元にあるデータ(ダウンロードしたファイル、ファイル入力、データベースのレコード)を解析します。

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

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

const gltf = await new GLTFLoader().parseAsync(buffer, "");
scene.add(gltf.scene);

Blob URL とデータ URL は、ローダーの URL として使用できます。

async function loadTextureFromBlob(blob) {
  const url = URL.createObjectURL(blob);
  const texture = await new THREE.TextureLoader().loadAsync(url);
  URL.revokeObjectURL(url);
  return texture;
}

マネージャーを介してパスのプレフィックスを追加し、URL を書き換えます。

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

const loader = new GLTFLoader();
loader.setPath("assets/models/"); // prefix for the URL you pass to load
loader.setResourcePath("assets/textures/"); // where the file's own references resolve
const gltf = await loader.loadAsync("model.glb"); // loads assets/models/model.glb

const manager = new THREE.LoadingManager();
manager.setURLModifier((url) => url.replace("assets/", "https://cdn.example.com/assets/"));
const cdnGltf = await new GLTFLoader(manager).loadAsync("assets/models/model.glb"); // fetched from https://cdn.example.com/assets/models/model.glb

共有キャッシュを有効にすると、繰り返し使う URL をメモリから取得できます。

THREE.Cache.enabled = true;
THREE.Cache.clear(); // empty it when you need to force reloads

ペイロードは小さく保ちます。ジオメトリには Draco または meshopt、テクスチャには KTX2 を使用し、threejs-asset-pipeline で生成してください。また、本物のモデルの読み込み中はプレースホルダーのメッシュを表示し、読み込みが完了したら置き換えます。

クローンと破棄

スキンモデルで Object3D.clone() を使うと、スケルトンが共有されます。そのため、すべてのコピーは元のボーンにバインドされたままとなり、元のモデルに追従し、それぞれのアニメーションは無視されます。インスタンスには SkeletonUtils.clone() を使用してください。

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

const gltf = await new GLTFLoader().loadAsync("character.glb");
scene.add(gltf.scene);

const copy = SkeletonUtils.clone(gltf.scene);
scene.add(copy);

object.dispose() は、そのオブジェクトのレンダラー側の状態(WebGPU バインディング、InstancedMesh のバッファー)のみを解放します。子要素をたどったり、ジオメトリ、マテリアル、テクスチャを破棄したりはしません。サブツリーを走査してください。

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

const gltf = await new GLTFLoader().loadAsync("model.glb");
const model = gltf.scene;
scene.add(model);

// ... later
scene.remove(model);
model.traverse((child) => {
  child.dispose(); // per-object renderer state: InstancedMesh buffers, WebGPU bindings
  if (child.geometry) { // meshes, lines and points
    child.geometry.dispose();
    const materials = Array.isArray(child.material) ? child.material : [child.material];
    for (const material of materials) {
      for (const value of Object.values(material)) {
        if (value?.isTexture) value.dispose();
      }
      material.dispose();
    }
  }
});

WebGPU と TSL

import * as THREE from "three/webgpu";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init(); // before any other use of the renderer
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111827); // the WebGPU canvas is transparent by default
scene.environment = new THREE.PMREMGenerator(renderer).fromScene(new RoomEnvironment(), 0.04).texture;
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(2, 1.5, 3);
camera.lookAt(0, 0, 0);
const gltf = await new GLTFLoader().loadAsync("model.glb"); // loaded materials become node materials automatically
scene.add(gltf.scene);
function animate() { renderer.render(scene, camera); } renderer.setAnimationLoop(animate);
  • ktx2Loader.detectSupport(renderer) と PMREMGenerator のメソッド(fromScene、fromEquirectangular、fromCubemap)は、await renderer.init() の前に呼び出すとエラーになります。最大異方性は renderer.getMaxAnisotropy() で取得します。WebGPURenderer では renderer.capabilities は存在しません。
  • Gaussian splat:SPLATLoader、SPZLoader、KSPLATLoader、GaussianSplatPLYLoader は、three/addons/objects/GaussianSplat.js の new GaussianSplat(data) に渡すデータを読み込みます。この処理には WebGPURenderer が必要です。

よくある間違い

  • import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js" → import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js":three.js がドキュメントで指定しているインポートマップでは、three/examples/jsm/ の指定子を解決できません。
  • import { RGBELoader } from "three/addons/loaders/RGBELoader.js" → import { HDRLoader } from "three/addons/loaders/HDRLoader.js":r180 で RGBELoader から名称が変更されました。古いクラスでは、インスタンスを作成するたびに警告が表示されます。
  • new Promise((resolve, reject) => loader.load(url, resolve, undefined, reject)) → loader.loadAsync(url):各ローダーにはすでに loadAsync が実装されており、エラー時には reject されます。
  • material.map = new THREE.TextureLoader().load("color.jpg") → const map = new THREE.TextureLoader().load("color.jpg"); map.colorSpace = THREE.SRGBColorSpace; material.map = map;:読み込んだカラー マップはデフォルトで NoColorSpace となり、白っぽく表示されます。
  • normalMap.colorSpace = THREE.LinearSRGBColorSpace → normalMap.colorSpace = THREE.NoColorSpace:スカラー マップ(normal、bump、roughness、metalness、ao、alpha)は NoColorSpace のままにします。LinearSRGBColorSpace はリニアなカラー データに使用します。
  • texture.encoding = THREE.sRGBEncoding → texture.colorSpace = THREE.SRGBColorSpace:encoding と sRGBEncoding は廃止されているため、古いコードではマップが NoColorSpace のままとなり、白っぽく表示されます。
  • child.material.envMapIntensity = 0.5 → scene.environmentIntensity = 0.5:envMapIntensity は material.envMap のみをスケーリングします。モデルのライティングに scene.environment を使っている場合、material.envMap は null のままです。
  • scene.environment = pmremGenerator.fromEquirectangular(hdrTexture).texture → hdrTexture.mapping = THREE.EquirectangularReflectionMapping; scene.environment = hdrTexture;:どちらのレンダラーも、エクイレクタングラー テクスチャに対する PMREM 処理を独自に実行するため、手動処理ではコードと破棄対象のレンダーターゲットが増えるだけです。
  • scene.background = hdrTexture → hdrTexture.mapping = THREE.EquirectangularReflectionMapping; scene.background = hdrTexture;:このマッピングがないと、背景テクスチャは平らなスクリーン クアッドとして描画されます。
  • ktx2Loader.setTranscoderPath(pathFromAnotherRelease) → const ktx2Loader = new KTX2Loader().detectSupport(renderer):Basis トランスコーダーと Draco デコーダーは、デフォルトで three パッケージから読み込まれます。別のリリースに固定されたパスでは、ローダーとのバージョンが一致しません。
  • ktx2Loader.detectSupportAsync(renderer) → await renderer.init(); ktx2Loader.detectSupport(renderer);:detectSupportAsync は r181 で非推奨になりました。レンダラーの初期化後に detectSupport を呼び出してください。
  • renderer.capabilities.getMaxAnisotropy() → renderer.getMaxAnisotropy():capabilities は WebGLRenderer にのみ存在します。WebGPURenderer ではレンダラーのメソッドを使います。
  • scene.add(new THREE.Mesh(await new PLYLoader().loadAsync("scan.ply"), material)) → const geometry = await new PLYLoader().loadAsync("scan.ply"); for (const [name, a] of Object.entries(geometry.attributes)) if (a.array instanceof Float64Array) geometry.setAttribute(name, new THREE.Float32BufferAttribute(a.array, a.itemSize)); scene.add(new THREE.Mesh(geometry, material));:PLYLoader は double プロパティを Float64 属性として保持しますが、WebGLRenderer はこれをアップロードできず、Unsupported buffer data format をスローします。
  • gltf.scene.clone() → SkeletonUtils.clone(gltf.scene):Object3D.clone() はスケルトンを共有するため、すべてのコピーが元のボーンにバインドされたままとなり、そのアニメーションに追従します。
  • gltf.scene.dispose() → gltf.scene.traverse((child) => { child.geometry?.dispose(); for (const material of [child.material ?? []].flat()) { for (const value of Object.values(material)) if (value?.isTexture) value.dispose(); material.dispose(); } child.dispose(); }):Object3D.dispose() はそのオブジェクトのレンダラー状態のみを解放し、ジオメトリ、マテリアル、テクスチャは解放しません。また、material.dispose() ではマテリアルのテクスチャは解放されません。
  • fetch(url, { signal: controller.signal }) → setTimeout(() => manager.abort(), 30000):ローダーのキャンセルには LoadingManager を使います。LoadingManager は処理中の FileLoader リクエストを中断します。
  • new USDZLoader() → new USDLoader():r179 で非推奨になりました。USDLoader は USD、USDA、USDC、USDZ を読み込みます。

関連項目

  • threejs-fundamentals - レンダラーのセットアップ、リサイズ処理、および読み込んだアセットを扱うアプリのひな形
  • threejs-textures と threejs-materials - テクスチャのプロパティと色空間、および読み込んだモデルに付属するマテリアルの調整
  • threejs-asset-pipeline - これらのローダーに渡す前の glTF ファイルの圧縮と最適化
  • threejs-animation - AnimationMixer、クリップ、およびスキンメッシュの再生

コメント