threejs-fundamentals

threejs-fundamentals

熱門

Three.js 場景建置、攝影機、渲染器、Object3D 層級結構與座標系統。適用於建立 3D 場景、建立攝影機、設定渲染器、管理物件層級結構或處理 Transform 變換操作。

2595星標
294分支
更新於 2026/7/9
SKILL.md
唯讀
名稱
threejs-fundamentals
描述

Three.js 場景建置、攝影機、渲染器、Object3D 層級結構與座標系統。適用於建立 3D 場景、建立攝影機、設定渲染器、管理物件層級結構或處理 Transform 變換操作。

Three.js 基礎概念

快速上手

import * as THREE from "three";

// 建立場景、攝影機與渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000,
);
const renderer = new THREE.WebGLRenderer({ antialias: true });

renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);

// 新增網格物件(Mesh)
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// 新增光源
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(5, 5, 5);
scene.add(dirLight);

camera.position.z = 5;

// 動畫循環
function animate() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);
}
animate();

// 處理視窗縮放
window.addEventListener("resize", () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

核心類別

Scene

包含所有 3D 物件、光源與攝影機的容器。

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000); // 純色背景
scene.background = texture; // 天空盒貼圖
scene.background = cubeTexture; // 立方體貼圖 (Cubemap)
scene.environment = envMap; // PBR 用的環境貼圖
scene.fog = new THREE.Fog(0xffffff, 1, 100); // 線性霧化 (Linear fog)
scene.fog = new THREE.FogExp2(0xffffff, 0.02); // 指數霧化 (Exponential fog)

Cameras

PerspectiveCamera — 最常用的類型,模擬人類眼睛視角。

// PerspectiveCamera(fov, aspect, near, far)
const camera = new THREE.PerspectiveCamera(
  75, // 視角(FOV,單位:度)
  window.innerWidth / window.innerHeight, // 長寬比
  0.1, // 近裁切面
  1000, // 遠裁切面
);

camera.position.set(0, 5, 10);
camera.lookAt(0, 0, 0);
camera.updateProjectionMatrix(); // 修改 fov、aspect、near 或 far 後必須呼叫此方法

OrthographicCamera — 無透視形變,適合 2D 或等角透視(Isometric)視角。

// OrthographicCamera(left, right, top, bottom, near, far)
const aspect = window.innerWidth / window.innerHeight;
const frustumSize = 10;
const camera = new THREE.OrthographicCamera(
  (frustumSize * aspect) / -2,
  (frustumSize * aspect) / 2,
  frustumSize / 2,
  frustumSize / -2,
  0.1,
  1000,
);

ArrayCamera — 包含多個子攝影機的多視口渲染。

const cameras = [];
for (let i = 0; i < 4; i++) {
  const subcamera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
  subcamera.viewport = new THREE.Vector4(
    Math.floor(i % 2) * 0.5,
    Math.floor(i / 2) * 0.5,
    0.5,
    0.5,
  );
  cameras.push(subcamera);
}
const arrayCamera = new THREE.ArrayCamera(cameras);

CubeCamera — 渲染環境貼圖以實現反射效果。

const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256);
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
scene.add(cubeCamera);

// 用於反射效果
material.envMap = cubeRenderTarget.texture;

// 每一幀更新(效能開銷較高!)
cubeCamera.position.copy(reflectiveMesh.position);
cubeCamera.update(renderer, scene);

WebGLRenderer

const renderer = new THREE.WebGLRenderer({
  canvas: document.querySelector("#canvas"), // 可選填已存在的 Canvas 元素
  antialias: true, // 開啟抗鋸齒
  alpha: true, // 透明背景
  powerPreference: "high-performance", // GPU 效能偏好提示
  preserveDrawingBuffer: true, // 保留繪圖緩衝區(可用於截圖)
});

renderer.setSize(width, height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

// 色調對映 (Tone mapping)
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;

// 色彩空間 (Three.js r152+)
renderer.outputColorSpace = THREE.SRGBColorSpace;

// 陰影設定
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

// 清除背景色
renderer.setClearColor(0x000000, 1);

// 執行渲染
renderer.render(scene, camera);

Object3D

所有 3D 物件的基類(Base class)。Mesh、Group、Light 和 Camera 皆繼承自 Object3D。

const obj = new THREE.Object3D();

// Transform 變換操作
obj.position.set(x, y, z);
obj.rotation.set(x, y, z); // 歐拉角(Euler angles,單位:弧度)
obj.quaternion.set(x, y, z, w); // 四元數旋轉
obj.scale.set(x, y, z);

// 區域 (Local) 與世界 (World) Transform
obj.getWorldPosition(targetVector);
obj.getWorldQuaternion(targetQuaternion);
obj.getWorldDirection(targetVector);

// 物件層級結構 (Hierarchy)
obj.add(child);
obj.remove(child);
obj.parent;
obj.children;

// 可見性
obj.visible = false;

// 圖層(用於選擇性渲染或光線投射 Raycasting)
obj.layers.set(1);
obj.layers.enable(2);
obj.layers.disable(0);

// 遍歷物件層級結構
obj.traverse((child) => {
  if (child.isMesh) child.material.color.set(0xff0000);
});

// 矩陣更新
obj.matrixAutoUpdate = true; // 預設值:自動更新矩陣
obj.updateMatrix(); // 手動更新矩陣
obj.updateMatrixWorld(true); // 遞迴更新世界矩陣

Group

用來組織與管理多個物件的空白容器。

const group = new THREE.Group();
group.add(mesh1);
group.add(mesh2);
scene.add(group);

// 對整個 Group 進行 Transform 變換
group.position.x = 5;
group.rotation.y = Math.PI / 4;

Mesh

結合幾何體(Geometry)與材質(Material)。

const mesh = new THREE.Mesh(geometry, material);

// 多重材質(每個幾何體分組對應一種材質)
const mesh = new THREE.Mesh(geometry, [material1, material2]);

// 常用屬性
mesh.geometry;
mesh.material;
mesh.castShadow = true;
mesh.receiveShadow = true;

// 視錐體裁切 (Frustum culling)
mesh.frustumCulled = true; // 預設值:若超出攝影機視野則跳過渲染

// 渲染順序
mesh.renderOrder = 10; // 數值越大越後渲染

座標系統

Three.js 使用右手座標系統

  • +X 指向右方
  • +Y 指向上方
  • +Z 指向觀察者(朝螢幕外)
// 座標軸輔助物件 (AxesHelper)
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper); // 紅色=X軸, 綠色=Y軸, 藍色=Z軸

數學工具類別

Vector3

const v = new THREE.Vector3(x, y, z);
v.set(x, y, z);
v.copy(otherVector);
v.clone();

// 向量運算(直接修改原物件)
v.add(v2);
v.sub(v2);
v.multiply(v2);
v.multiplyScalar(2);
v.divideScalar(2);
v.normalize();
v.negate();
v.clamp(min, max);
v.lerp(target, alpha);

// 數學計算(回傳新數值)
v.length();
v.lengthSq(); // 速度比 length() 更快
v.distanceTo(v2);
v.dot(v2);
v.cross(v2); // 會修改 v 本身
v.angleTo(v2);

// Transform 轉換
v.applyMatrix4(matrix);
v.applyQuaternion(q);
v.project(camera); // 世界座標轉 NDC
v.unproject(camera); // NDC 轉世界座標

Matrix4

const m = new THREE.Matrix4();
m.identity();
m.copy(other);
m.clone();

// 建構 Transform 矩陣
m.makeTranslation(x, y, z);
m.makeRotationX(theta);
m.makeRotationY(theta);
m.makeRotationZ(theta);
m.makeRotationFromQuaternion(q);
m.makeScale(x, y, z);

// 組合 / 解構矩陣
m.compose(position, quaternion, scale);
m.decompose(position, quaternion, scale);

// 矩陣運算
m.multiply(m2); // m = m * m2
m.premultiply(m2); // m = m2 * m
m.invert();
m.transpose();

// 攝影機矩陣
m.makePerspective(left, right, top, bottom, near, far);
m.makeOrthographic(left, right, top, bottom, near, far);
m.lookAt(eye, target, up);

Quaternion

const q = new THREE.Quaternion();
q.setFromEuler(euler);
q.setFromAxisAngle(axis, angle);
q.setFromRotationMatrix(matrix);

q.multiply(q2);
q.slerp(target, t); // 球面線性插值 (Slerp)
q.normalize();
q.invert();

Euler

const euler = new THREE.Euler(x, y, z, "XYZ"); // 旋轉順序非常重要!
euler.setFromQuaternion(q);
euler.setFromRotationMatrix(m);

// 旋轉順序選項:'XYZ', 'YXZ', 'ZXY', 'XZY', 'YZX', 'ZYX'

Color

const color = new THREE.Color(0xff0000);
const color = new THREE.Color("red");
const color = new THREE.Color("rgb(255, 0, 0)");
const color = new THREE.Color("#ff0000");

color.setHex(0x00ff00);
color.setRGB(r, g, b); // 數值範圍 0-1
color.setHSL(h, s, l); // 數值範圍 0-1

color.lerp(otherColor, alpha);
color.multiply(otherColor);
color.multiplyScalar(2);

MathUtils

THREE.MathUtils.clamp(value, min, max);
THREE.MathUtils.lerp(start, end, alpha);
THREE.MathUtils.mapLinear(value, inMin, inMax, outMin, outMax);
THREE.MathUtils.degToRad(degrees);
THREE.MathUtils.radToDeg(radians);
THREE.MathUtils.randFloat(min, max);
THREE.MathUtils.randInt(min, max);
THREE.MathUtils.smoothstep(x, min, max);
THREE.MathUtils.smootherstep(x, min, max);

常見開發模式

正確的資源釋放 (Cleanup)

function dispose() {
  // 釋放幾何體
  mesh.geometry.dispose();

  // 釋放材質
  if (Array.isArray(mesh.material)) {
    mesh.material.forEach((m) => m.dispose());
  } else {
    mesh.material.dispose();
  }

  // 釋放貼圖
  texture.dispose();

  // 從場景中移除物件
  scene.remove(mesh);

  // 釋放渲染器
  renderer.dispose();
}

時間時鐘控制動畫 (Clock)

const clock = new THREE.Clock();

function animate() {
  const delta = clock.getDelta(); // 自上一幀以來經過的時間(秒)
  const elapsed = clock.getElapsedTime(); // 累積總時間(秒)

  mesh.rotation.y += delta * 0.5; // 無論影格率 (FPS) 為何皆保持一致速度

  requestAnimationFrame(animate);
  renderer.render(scene, camera);
}

響應式 Canvas (Responsive Canvas)

function onWindowResize() {
  const width = window.innerWidth;
  const height = window.innerHeight;

  camera.aspect = width / height;
  camera.updateProjectionMatrix();

  renderer.setSize(width, height);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
}
window.addEventListener("resize", onWindowResize);

載入管理器 (Loading Manager)

const manager = new THREE.LoadingManager();

manager.onStart = (url, loaded, total) => console.log("Started loading");
manager.onLoad = () => console.log("All loaded");
manager.onProgress = (url, loaded, total) => console.log(`${loaded}/${total}`);
manager.onError = (url) => console.error(`Error loading ${url}`);

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

效能最佳化技巧

  1. 減少 Draw Call 次數:合併幾何體(Merge geometries)、使用實例化渲染(Instancing)或紋理圖集(Atlas textures)。
  2. 視錐體裁切 (Frustum Culling):預設為開啟狀態,請確保包圍盒(Bounding box)計算正確。
  3. 多細節層次 (LOD):使用 THREE.LOD 依據距離切換不同精細度的網格物件。
  4. 物件池 (Object Pooling):重複利用既存物件,避免頻繁建立與銷毀物件。
  5. 避免在迴圈中呼叫 getWorldPosition:建議快取(Cache)計算結果。
// 合併靜態幾何體
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";
const merged = mergeGeometries([geo1, geo2, geo3]);

// 多細節層次 (LOD)
const lod = new THREE.LOD();
lod.addLevel(highDetailMesh, 0);
lod.addLevel(medDetailMesh, 50);
lod.addLevel(lowDetailMesh, 100);
scene.add(lod);

延伸參考

  • threejs-geometry - 幾何體建置與操作
  • threejs-materials - 材質類型與屬性
  • threejs-lighting - 光源類型與陰影設定