jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Three.js from Zero to Senior · Part 3 — Transforms, Scene Graph & Cameras

Position, rotation, scale and the parent-child scene graph where children inherit transforms — plus Perspective vs Orthographic cameras and OrbitControls, with a live sun-earth-moon rig you can drive.

So far every object has sat alone at the origin. {Tới giờ mọi vật đều ngồi một mình ở gốc toạ độ.} Real scenes are hierarchies: a hand follows an arm, an arm follows a body, a moon follows a planet that follows the sun. {Scene thật là các phân cấp: bàn tay theo cánh tay, cánh tay theo thân, mặt trăng theo hành tinh, hành tinh theo mặt trời.} Master the scene graph and you’ve unlocked rigs, solar systems, and every “attach this to that” problem. {Làm chủ scene graph là bạn mở khoá rig, hệ mặt trời, và mọi bài toán “gắn cái này vào cái kia”.}

Drive the demo: turn up Sun rotation and watch the earth and its moon swing around together. {Điều khiển demo: tăng Sun rotation và xem trái đất cùng mặt trăng của nó quay theo.} Then scale the sun and notice the whole system scales. {Rồi scale mặt trời và để ý cả hệ scale theo.} That inheritance is the entire lesson. {Sự kế thừa đó chính là toàn bộ bài học.}

Open the full demo {Mở demo đầy đủ}: /tools/threejs-scene-graph-demo/.

Transform — position, rotation, scale {Transform — vị trí, xoay, tỉ lệ}

Every Object3D (meshes, groups, cameras, lights all inherit from it) carries three transform properties. {Mọi Object3D (mesh, group, camera, đèn đều kế thừa từ nó) mang ba thuộc tính biến đổi.}

mesh.position.set(x, y, z);          // move — units are arbitrary, you decide the scale
mesh.rotation.set(rx, ry, rz);       // rotate — RADIANS, not degrees
mesh.scale.set(sx, sy, sz);          // resize — 1 = original, 2 = double

Two gotchas right away. {Hai cái bẫy ngay đây.} Rotation is in radians, so a quarter turn is Math.PI / 2, not 90. {Xoay tính bằng radian, nên một phần tư vòng là Math.PI / 2, không phải 90.} And these are live objects — you can nudge a single axis without rebuilding the vector: {Và đây là các object sống — bạn có thể chỉnh một trục mà không cần dựng lại vector:}

mesh.position.x += 0.1;
mesh.rotation.y = THREE.MathUtils.degToRad(45); // convert when you think in degrees

The scene graph — children inherit their parent {Scene graph — con kế thừa cha}

When you call scene.add(mesh), you make mesh a child of the scene. {Khi bạn gọi scene.add(mesh), bạn biến mesh thành con của scene.} But any Object3D can be a parent. {Nhưng bất kỳ Object3D nào cũng có thể làm cha.} The golden rule: a child’s transform is applied on top of its parent’s. {Quy tắc vàng: transform của con được áp chồng lên transform của cha.} Move the parent, the child moves with it. Rotate the parent, the child orbits. {Di chuyển cha, con đi theo. Xoay cha, con quay vòng.}

A THREE.Group is an empty Object3D — the perfect invisible “pivot” or “rig joint”: {Một THREE.GroupObject3D rỗng — “điểm xoay” hay “khớp rig” vô hình hoàn hảo:}

const sun = new THREE.Group();
scene.add(sun);
sun.add(sunMesh);

// earthPivot is OFFSET from the sun, then added as its child.
const earthPivot = new THREE.Group();
earthPivot.position.set(3.5, 0, 0);   // 3.5 units out from the sun
sun.add(earthPivot);
earthPivot.add(earthMesh);

// moonPivot is a child of earthPivot — it inherits BOTH rotations.
const moonPivot = new THREE.Group();
moonPivot.position.set(1.3, 0, 0);
earthPivot.add(moonPivot);
moonPivot.add(moonMesh);

Now the magic in the render loop: {Giờ là phép màu trong render loop:}

sun.rotation.y += dt * 0.4;         // earth + moon both orbit the sun
earthPivot.rotation.y += dt * 1.2;  // moon orbits the earth (on top of sun's rotation)
moonPivot.rotation.y += dt * 3.0;   // moon spins around its own pivot

Rotating sun alone makes the earth and the moon swing around it, because both live inside sun’s coordinate space. {Chỉ xoay sun thôi cũng làm trái đất mặt trăng quay quanh nó, vì cả hai sống trong không gian toạ độ của sun.} This composition — local transforms multiplying up the tree into a world transform — is exactly how character skeletons and robotic arms work. {Sự kết hợp này — transform cục bộ nhân dần lên cây thành transform thế giới — chính là cách bộ xương nhân vật và cánh tay robot hoạt động.}

Local vs world space {Không gian cục bộ vs thế giới}

mesh.position is local — relative to its parent. {mesh.positioncục bộ — tương đối với cha của nó.} To get the real position in the scene (after every parent rotation and scale is applied), ask for the world position: {Để lấy vị trí thật trong scene (sau khi mọi xoay và scale của cha đã áp), hãy hỏi vị trí thế giới:}

const worldPos = new THREE.Vector3();
moonMesh.getWorldPosition(worldPos); // the moon's true location in the scene

The demo’s HUD prints exactly this — watch the moon’s world position trace a wobbling path as both pivots rotate. {HUD của demo in đúng cái này — xem vị trí thế giới của mặt trăng vẽ ra một đường loằng ngoằng khi cả hai pivot xoay.} Knowing the difference between local and world space is a senior-level reflex; mixing them up is the source of countless “why is my object in the wrong place” bugs. {Phân biệt được không gian cục bộ và thế giới là phản xạ cấp senior; lẫn lộn chúng là nguồn gốc vô số lỗi “sao vật của tôi sai chỗ”.}

Two cameras — Perspective vs Orthographic {Hai camera — Perspective vs Orthographic}

Toggle the camera button in the demo and watch the projection change. {Bấm nút camera trong demo và xem phép chiếu đổi.}

PerspectiveCamera mimics the human eye: distant things shrink, parallel lines converge to a vanishing point. {PerspectiveCamera mô phỏng mắt người: vật ở xa nhỏ lại, đường song song hội tụ về điểm tụ.} It’s the default for games, product views, anything that should feel real. {Nó là mặc định cho game, xem sản phẩm, mọi thứ cần cảm giác thật.}

new THREE.PerspectiveCamera(fov, aspect, near, far);

OrthographicCamera has no perspective: objects keep their size regardless of distance, parallel lines stay parallel. {OrthographicCamera không có phối cảnh: vật giữ nguyên kích thước bất kể khoảng cách, đường song song vẫn song song.} This is the look of CAD software, isometric strategy games, and 2D HUDs. {Đây là vẻ của phần mềm CAD, game chiến thuật isometric, và HUD 2D.}

// defined by a box, not an angle: left, right, top, bottom, near, far
new THREE.OrthographicCamera(-w, w, h, -h, 0.1, 100);

On resize, both need their bounds updated — and remember updateProjectionMatrix(): {Khi resize, cả hai cần cập nhật biên — và nhớ updateProjectionMatrix():}

// perspective
camera.aspect = w / h;
// orthographic
camera.left = -size * (w / h); camera.right = size * (w / h);
camera.top = size; camera.bottom = -size;
camera.updateProjectionMatrix();

OrbitControls, retargeted {OrbitControls, đổi mục tiêu}

When you swap the active camera, point the controls at the new one: {Khi bạn đổi camera đang dùng, trỏ controls sang camera mới:}

controls.object = camera;
controls.update();

OrbitControls rotate around controls.target (default origin). {OrbitControls xoay quanh controls.target (mặc định là gốc).} Set the target to your subject so orbiting feels like inspecting that object, not swinging around empty space. {Đặt target vào chủ thể để xoay giống như đang soi vật đó, không phải quay quanh khoảng trống.}

The master’s warnings {Lời cảnh báo của sư phụ}

  • Rotation in degrees? No — radians. Use THREE.MathUtils.degToRad(deg). {Xoay bằng độ? Không — radian. Dùng THREE.MathUtils.degToRad(deg).}
  • Child won’t orbit? It must be add()-ed to the rotating parent (group), and offset by position, not sitting at the pivot. {Con không chịu quay? Nó phải được add() vào cha (group) đang xoay, và lệch bằng position, đừng nằm ngay tâm pivot.}
  • Object in the wrong place? You’re reading local position but need getWorldPosition(). {Vật sai chỗ? Bạn đang đọc position cục bộ nhưng cần getWorldPosition().}
  • Ortho camera looks flat/empty? Its size box is wrong for the aspect ratio — recompute left/right from width/height. {Camera ortho trông phẳng/trống? Hộp size sai so với aspect — tính lại left/right từ width/height.}

Practice, or it didn’t happen {Luyện tập, không thì coi như chưa học}

  1. Add a second planet {Thêm hành tinh thứ hai}: another pivot child of sun at a different distance and speed. {một pivot con khác của sun ở khoảng cách và tốc độ khác.}
  2. Tilt the orbit {Nghiêng quỹ đạo}: rotate earthPivot slightly on x and watch the moon’s path tilt with it. {xoay earthPivot một chút theo x và xem đường mặt trăng nghiêng theo.}
  3. Detach the moon {Tách mặt trăng}: scene.add(moonMesh) instead of moonPivot.add(...) and watch it stop following — proof of inheritance. {scene.add(moonMesh) thay vì moonPivot.add(...) và xem nó ngừng đi theo — bằng chứng của kế thừa.}

What’s next {Phần tiếp theo}

You can now compose, transform, and view a multi-object scene. {Giờ bạn dựng, biến đổi, và ngắm được một scene nhiều vật.} It still looks a little flat, though — flat lighting is the giveaway of a beginner scene. {Tuy vậy nó vẫn hơi phẳng — ánh sáng phẳng là dấu hiệu của scene người mới.} In Part 4 we make it look 3D: directional, point, and spot lights, ambient and hemisphere fill, and real-time shadow maps — plus the performance cost of each and how seniors keep shadows cheap. {Ở Phần 4 ta làm nó trông 3D thật: đèn directional, point, spot, fill kiểu ambient và hemisphere, và shadow map real-time — kèm chi phí hiệu năng của từng loại và cách senior giữ bóng đổ rẻ.}