three.js glTF 로딩
.gltf / .glb 모델(압축된 지오메트리(DRACO, Meshopt) 및 텍스처(KTX2) 포함)을 로드하고 애니메이션 클립을 재생합니다. 패턴은 r187을 대상으로 합니다. 기존 프로젝트는 사용자가 업그레이드를 요청하지 않는 한 고정된 릴리스를 유지합니다. glTF는 기본 런타임 포맷입니다. OBJ는 씬 그래프나 애니메이션이 없고, FBX는 브라우저에서 파싱하기에 무겁습니다.
사용 시기
- 모델이나 캐릭터 가져오기, 노드 계층 검사, 클립 재생 또는 블렌딩, 애니메이션 캐릭터의 여러 복사본 생성, 또는 나타나지 않는 모델 수정.
- 파일이
.gltf/.glb이거나, 코드가three/addons/loaders/...에서GLTFLoader,DRACOLoader또는KTX2Loader를 가져오는 경우. - 렌더러, 카메라 및 루프(
threejs-scene-setup), 모델의 조명 및 재질(threejs-materials-lighting), 또는 DCC 도구의 모델링 및 내보내기 설정에는 해당되지 않습니다.
워크플로
- 로더 코드를 작성하기 전에 파일 검사 (
gltf-transform을 사용할 수 있을 때):npx @gltf-transform/cli inspect model.glb는 메시, 확장(예:KHR_draco_mesh_compression,EXT_meshopt_compression,KHR_texture_basisu), 클립 및 텍스처 크기를 나열합니다. 나열된 각 압축 확장은 디코더를 연결해야 합니다. GLTFLoader로 로드 (비동기 코드에서는loadAsync). 결과에는gltf.scene(루트Object3D)과gltf.animations(AnimationClip[])가 있습니다.gltf.scene을 추가하고 프레이밍합니다. glTF 단위는 미터입니다. 이름과 경계를 기록하여 코드가 제어할 부분을 찾습니다.- 애니메이션 루트마다 하나의
AnimationMixer,mixer.clipAction(clip)으로 액션을 생성하고, 매 프레임마다mixer.update(seconds)를 호출합니다. - 1단계에서 찾은 확장에 대한 디코더를 연결합니다. r185부터
DRACOLoader와KTX2Loader는 경로 없이 번들 디코더 파일을 찾습니다. - Verify로 확인합니다.
패턴
1. 모델 로드 및 프레이밍
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const gltf = await new GLTFLoader().loadAsync('assets/robot.glb');
const root = gltf.scene;
scene.add(root);
const box = new THREE.Box3().setFromObject(root);
console.log('size (m):', box.getSize(new THREE.Vector3()).toArray());
console.log('clips:', gltf.animations.map((c) => c.name));
2. 클립 재생
const timer = new THREE.Timer(); // THREE.Clock은 r183부터 폐기됨
const mixer = new THREE.AnimationMixer(gltf.scene);
const run = THREE.AnimationClip.findByName(gltf.animations, 'Run') ?? gltf.animations[0];
mixer.clipAction(run).play();
renderer.setAnimationLoop(() => {
timer.update();
mixer.update(timer.getDelta()); // 초 단위, 밀리초 아님
renderer.render(scene, camera);
});
3. 클립 간 크로스페이드
const actions = Object.fromEntries(gltf.animations.map((c) => [c.name, mixer.clipAction(c)]));
let current = actions.Idle;
current.play();
function transitionTo(name, duration = 0.3) {
const next = actions[name];
if (!next || next === current) return;
next.reset().play(); // 되감기 및 이전 페이드 제거
current.crossFadeTo(next, duration, false);
current = next;
}
4. 압축된 지오메트리 및 텍스처
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader, DRACO_GLTF_CONFIG } 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 loader = new GLTFLoader()
.setDRACOLoader(new DRACOLoader().setDecoderPath(DRACO_GLTF_CONFIG)) // glTF 전용 WASM 빌드 (r185+)
.setKTX2Loader(new KTX2Loader().detectSupport(renderer)) // 렌더러 필요
.setMeshoptDecoder(MeshoptDecoder);
// 디코더와 워커가 한 번만 생성되도록 모든 모델에 대해 이 로더 하나를 공유합니다.
r185 이전 릴리스나 호스트가 번들 파일을 제공하지 않는 경우, 설치된 three에서 examples/jsm/libs/draco/ 및 libs/basis/를 복사하여 setDecoderPath / setTranscoderPath에 해당 폴더를 전달합니다.
5. 이름이 지정된 부품 찾기
const wheels = [];
gltf.scene.traverse((node) => {
if (node.name.startsWith('Wheel')) wheels.push(node);
});
// 루프에서: for (const w of wheels) w.rotation.x += dt * 4;
6. 코드가 필요한 부품을 유지하면서 모델 축소
# Meshopt 압축, 최대 1024px WebP 텍스처, 이름이 지정된 노드와 계층 유지.
npx @gltf-transform/cli optimize in.glb out.glb \
--compress meshopt --texture-compress webp --texture-size 1024 \
--flatten false --join-named false
npx @gltf-transform/cli validate out.glb
optimize는 기본적으로 이름이 지정된 노드를 포함하여 계층을 평탄화하고 메시를 결합합니다. 이렇게 하면 드로우 콜이 줄어들지만, Wheel_FL을 찾거나 도어 노드를 애니메이션하는 코드는 아무것도 찾지 못하게 됩니다. 코드가 접근하는 모든 모델에 대해 위의 두 플래그를 유지하세요. 출력에는 setMeshoptDecoder(패턴 4)가 필요합니다.
확인
- 파일 확인: 각 새 파일이나 변경된 파일에 대해
npx @gltf-transform/cli validate model.glb를 실행하고, 로더 코드를 디버깅하기 전에 보고된 오류를 수정합니다. - 빌드:
npm run build(또는npx tsc --noEmit)가 통과합니다. - 런타임 프로브:
scene과 mixer를window에 노출시킵니다(Object.assign(window, { scene, mixer })). 이 표현식을 Playwright의page.evaluate에 문자열로 전달하여 Node가 아닌 페이지에서 실행되도록 하거나(threejs-scene-setup의 스모크 스크립트가 작동함), 사용자에게 브라우저 콘솔에await을 앞에 붙여 붙여넣고 출력을 보고하도록 요청합니다:
(async () => {
const before = window.mixer?.time ?? null;
await new Promise((r) => setTimeout(r, 500));
const meshes = [];
window.scene.traverse((o) => { if (o.isMesh) meshes.push(o.name || o.type); });
return { meshes: meshes.length, mixerAdvanced: before !== null && window.mixer.time > before };
})();
0이 아닌 메시 개수와 mixerAdvanced: true를 기대하고, 스크린샷으로 스케일과 방향을 확인합니다.
주의사항
- 모델이 로드되지만 아무것도 표시되지 않음 → PBR 재질에 빛이나 환경이 없음(
threejs-materials-lighting참조) 또는 스케일이 100배 차이(센티미터 단위 내보내기). 패턴 1의 경계 상자를 로그로 확인합니다. - 콜백 외부에서
gltf가 정의되지 않음 →load는 비동기입니다.loadAsync를 사용하거나 콜백 내부에서 공유 참조를 할당합니다. - 애니메이션이 움직이지 않음 →
mixer.update가 매 프레임 호출되지 않거나, 밀리초 단위로 전달되거나,play()가 호출되지 않았습니다. draco_*또는basis_*파일 404 또는 디코딩 오류 → 디코더 파일이 제공되지 않거나 다른 three.js 릴리스에서 왔습니다. 설치된three에서 복사본을 제공하고 로더가 이를 가리키도록 합니다.setKTX2Loader must be called before loading KTX2 textures또는setMeshoptDecoder must be called before loading compressed files→ 파일이KHR_texture_basisu또는 meshopt 압축을 사용합니다. 디코더를 연결합니다(패턴 4)..hasFeature() called before the backend is initialized→KTX2Loader.detectSupport가await renderer.init()완료 전에WebGPURenderer에서 실행되었습니다.- 동일한 모델을 두 번 추가하면 이동함 →
Object3D는 하나의 부모를 가집니다. 복제하고, 스킨 메시의 경우SkeletonUtils.clone을 사용합니다(참고자료 참조). - 클론이 함께 애니메이션되거나 하나만 움직임 → 일반
.clone()은 스켈레톤을 공유합니다.SkeletonUtils.clone과 클론당 별도의 mixer를 사용합니다. gltf-transform optimize후 이름이 지정된 부품이 사라짐 → 기본 평탄화 및 결합이 병합했습니다.--flatten false --join-named false로 다시 실행합니다.- 베이크된 오프셋이 런타임 제어와 충돌 → 내보내기 도구가 자식 노드에 스케일이나 회전을 베이크할 수 있습니다. 먼저 이름과 로컬 변환을 덤프합니다. 깨끗한 피벗을 위해 새
Object3D아래에 부품을 다시 부모로 지정하거나, 변환을 적용하여 다시 내보내도록 요청합니다.
참고자료
- 여러 로더에 걸친
LoadingManager진행률 표시줄,SkeletonUtils.clone을 사용한 여러 애니메이션 적 생성,AnimationAction루핑 및 블렌딩 세부 정보, 디코더 호스팅 참고 사항, 아티스트에게 전달할 내보내기 체크리스트에 대해서는references/loaders-and-animation.md를 읽어보세요.
관련 스킬
threejs-scene-setup: 렌더러, 카메라 및 루프.threejs-materials-lighting: PBR 모델이 올바르게 보이도록 조명 및 환경.create-game-assets: 3D 에셋 자체의 브리핑, 소싱 및 검사.fps-shooter: 이러한 스킬을 기반으로 하는 3D 장르.
버전 정보
최종 확인: 2026-11-11, three r187 (npm [email protected], 2026-10-30 게시) 및 @gltf-transform/cli 4.5.1 기준.
- r185:
DRACOLoader와KTX2Loader가 기본적으로 번들 디코더 폴더를 사용하도록 변경됨;DRACO_GLTF_CONFIG추가됨;DRACOLoader.setDecoderConfig()폐기됨(r194에서 제거 예정). r183:EXT_meshopt_compression외에KHR_meshopt_compression지원. r181:KTX2Loader.detectSupportAsync()폐기됨. - gltf-transform 4.5.1
optimize기본값:--compress meshopt,--flatten true,--join true,--join-named true,--simplify true,--texture-compress auto.--texture-compress ktx2는PATH에 KTX-Software 4.4.0 이상의ktx명령이 필요합니다. - 출처: r187 릴리스 노트, r187 DRACOLoader.js, r187 GLTFLoader.js, glTF Transform CLI,
npx @gltf-transform/cli optimize --help.