Three.js from Zero to Senior · Part 6 — Loading Models & the Animation System
Bring in real assets and make them move: loading glTF with Draco, the AnimationMixer / AnimationClip / AnimationAction trio, crossfading between clips, and frame-rate-independent playback — with a live mixer you can drive.
Tới giờ mọi vật thể đều sinh ra trong code. Dự án thật nạp asset tạo trong Blender, Maya, hay mua trên chợ — nhân vật, đạo cụ, cả môi trường — và nhiều cái đến kèm animation nướng sẵn. Phần này nói về đưa các asset đó vào và phát animation của chúng.
Demo nối đúng hệ animation bạn sẽ dùng cho nhân vật glTF — nhưng với clip dựng tay để chạy không cần tải gì. Crossfade giữa các clip và chỉnh time scale:
glTF — “JPEG của 3D”
glTF (.gltf / .glb) là định dạng phân phối chuẩn cho web: gọn, parse nhanh, và mang mesh, material PBR, camera, và animation trong một file. Ưu tiên .glb nhị phân cho production.
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load('/models/robot.glb', (gltf) => {
scene.add(gltf.scene); // the model's root (a Group)
// gltf.animations → an array of AnimationClips, if any
});
gltf.scene là một Group scene-graph bình thường — mọi thứ ở Phần 3 đều áp dụng. gltf.animations là mảng clip ta sắp phát.
Draco — đừng ship mesh khổng lồ
Model chi tiết thì nặng. Draco nén geometry mạnh; bạn nối một DRACOLoader vào GLTFLoader và nó tự giải mã khi nạp.
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
const draco = new DRACOLoader();
draco.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
loader.setDRACOLoader(draco);
Senior luôn kiểm cân nặng asset: một .glb 40 MB chưa nén có thể còn vài MB với Draco + nén texture (KTX2/Basis).
Bộ ba animation
Animation của Three.js có ba lớp, và tên gọi làm ai cũng rối lúc đầu. Mô hình tư duy:
AnimationClip— bản thu. Một gói track keyframe có tên (“Walk”, “Idle”, “Jump”). Dữ liệu tái dùng, tự nó không phát gì.AnimationMixer— máy phát, gắn với một model. Nó đẩy thời gian và trộn clip. Một mixer cho mỗi vật thể có animation.AnimationAction— điều khiển play/pause/loop cho một clip trên mixer đó. Tạo quamixer.clipAction(clip).
const mixer = new THREE.AnimationMixer(gltf.scene);
const action = mixer.clipAction(gltf.animations[0]);
action.play(); // starts; you still need to advance the mixer each frame
Đẩy mixer — dòng ai cũng quên
action.play() tự nó không làm gì animate. Mixer cần delta time mỗi frame — và đây chính là lý do ta dùng Timer từ Phần 1.
const timer = new THREE.Timer();
timer.connect(document);
function animate(timestamp) {
timer.update(timestamp);
const delta = timer.getDelta(); // seconds since last frame
mixer.update(delta); // ← without this, nothing moves
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
Truyền delta (không phải bước cố định) là thứ khiến phát lại độc lập với frame rate: một clip kéo dài đúng thời gian thật ở 30 fps hay 144 fps.
Crossfade — trộn, đừng giật
Đổi cứng từ “Idle” sang “Walk” trông như robot. crossFadeTo giảm trọng số một action xuống trong khi tăng cái khác lên qua một khoảng thời gian — nút “crossfade” của demo làm đúng vậy.
function switchTo(next, duration = 0.4) {
next.reset().play();
current.crossFadeTo(next, duration, false);
current = next;
}
Các cần khác bạn sẽ cần:
action.setLoop(THREE.LoopRepeat); // or LoopOnce
action.timeScale = 0.5; // half speed; negative = reverse
action.setEffectiveWeight(0.5); // blend multiple clips at once
mixer.timeScale = 1; // global speed for the whole mixer
Dựng một clip bằng tay
Khi không có clip tạo sẵn — chuyển động UI, hiệu ứng thủ tục, chính demo này — bạn có thể ráp một AnimationClip từ KeyframeTrack. Tên track là tênĐốiTượng.thuộcTính:
// times (s), then values flattened: [x,y,z, x,y,z, …] for a VectorKeyframeTrack
const bounce = new THREE.VectorKeyframeTrack(
'.position', // '.' = the object the mixer is bound to
[0, 0.5, 1], // keyframe times
[0, 0, 0, 0, 2, 0, 0, 0, 0], // positions at each time
);
const clip = new THREE.AnimationClip('bounce', 1, [bounce]);
mixer.clipAction(clip).play();
Có track theo kiểu cho mỗi dạng dữ liệu: NumberKeyframeTrack, VectorKeyframeTrack, QuaternionKeyframeTrack (luôn dùng cái này cho xoay, không phải Euler), và ColorKeyframeTrack.
Lời cảnh báo của sư phụ
- Model nạp xong mà đứng im? Bạn quên
mixer.update(delta)trong loop, hoặc chưa gọiaction.play(). - Animation nhanh lên trên màn hình tần số cao? Bạn truyền bước cố định thay vì delta từ
Timer. loader.loadkhông trả gì? Nó bất đồng bộ — model nằm trong callback, không phải sau lời gọi.- Animation xoay giật/lật? Dùng
QuaternionKeyframeTrack, đừng animate góc Euler trực tiếp. - Nhiều nhân vật độc lập, một mixer? Thường nên dùng một mixer cho mỗi root được điều khiển độc lập;
optionalRootvàAnimationObjectGrouplà ngoại lệ có chủ đích.
Luyện tập, không thì coi như chưa học
- Cảm giác crossfade: trong demo, đổi clip với crossfade dài và ngắn, cảm nhận khác biệt.
- Time scale: kéo time scale về 0.25 và -1 — slow-mo và tua ngược.
- Tạo một clip: dựng một
NumberKeyframeTracktrên.rotation[y]và phát một vòng quay 360°.
Phần tiếp theo
Giờ bạn nạp được asset thật có animation và điều khiển phát lại như một đạo diễn. Điều đó khép lại vòng “dựng một scene”. Nửa sau của series hướng tới làm nó nhanh và đẹp: Phần 7 đào vào post-processing và EffectComposer — bloom, độ sâu trường ảnh, và các pass shader tuỳ biến tạo nét điện ảnh cuối cùng cho bản render.