Three.js 基礎概念 ── 渲染管線(Renderer → Scene → Camera → Objects)
簡介
在 3D 網頁開發中,Three.js 是最常被採用的高階抽象函式庫。它把 WebGL 那層低階、繁雜的 API 包裝成易於上手的物件導向模型,讓開發者可以把注意力放在 「要呈現什麼」 而不是 「要怎麼把像素送到螢幕」。
任何一個 Three.js 應用的核心,都圍繞 渲染管線(Rendering Pipeline)運作。簡單來說,渲染管線的四大要素是:
- Renderer – 把資料送到 GPU,產生最終的影像。
- Scene – 3D 世界的容器,所有可見的物件都放在這裡。
- Camera – 定義觀察者的位置、方向與投影方式。
- Objects – 幾何體、材質、光源等組成的實體。
掌握這四者之間的關係與設定順序,是寫出正確且效能良好的 Three.js 程式的第一步。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你完整了解渲染管線的運作方式。
核心概念
1. Renderer:橋接 JavaScript 與 GPU
Three.js 內建兩種主要的渲染器:
| 渲染器 | 說明 | 何時使用 |
|---|---|---|
WebGLRenderer |
直接呼叫 WebGL,支援陰影、後處理等功能 | 大多數 3D 應用 |
CanvasRenderer |
使用 2D Canvas,功能較少 | 老舊裝置或教學示範(已逐步淘汰) |
基本設定:
// 建立 WebGLRenderer 並設定畫布大小
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
// 把 canvas 加入 DOM
document.body.appendChild(renderer.domElement);
antialias: true能減少鋸齒,但會稍微吃效能。setPixelRatio可配合 Retina 螢幕:renderer.setPixelRatio(window.devicePixelRatio);
2. Scene:3D 世界的容器
Scene 本質上是一個 「節點樹」(scene graph),所有物件都以階層方式掛載於其下。這讓我們可以一次性對整個子樹做變換(例如旋轉、平移),非常適合組合模型或 UI 元件。
const scene = new THREE.Scene();
// 加入環境光(不會產生陰影,只提供基礎亮度)
const ambient = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambient);
Scene本身不會自動清除畫面,渲染器會在每次render()前自行執行 clear,除非你手動設定renderer.autoClear = false。
3. Camera:觀察者的視角
Three.js 主要提供兩種相機:
| 相機類型 | 特色 | 常見用途 |
|---|---|---|
PerspectiveCamera |
透視投影,遠近物件大小會隨距離變化 | 真實感 3D 場景 |
OrthographicCamera |
正交投影,遠近大小相同 | 2.5D、平面 UI、等距視圖 |
// 透視相機:fov、aspect、near、far
const camera = new THREE.PerspectiveCamera(
75, // 視野角度 (Degree)
window.innerWidth / window.innerHeight, // 長寬比
0.1, // 最近可見距離
1000 // 最遠可見距離
);
camera.position.set(0, 2, 5); // 把相機抬高 2 單位、往後移 5 單位
- 相機的
near與far必須根據場景深度調整,過大會造成 Z‑buffer 精度問題(穿幀)。
4. Objects:幾何體、材質與光源
物件是由 Geometry(形狀) + Material(外觀) 組成的 Mesh。光源則是獨立的 Object,會影響材質的著色結果。
範例 A:建立一個簡單的立方體
// 幾何體:1x1x1 立方體
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 基礎材質:使用 MeshStandardMaterial 以支援光照
const material = new THREE.MeshStandardMaterial({ color: 0x156289, metalness: 0.5, roughness: 0.2 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
範例 B:加入方向光(產生陰影)
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(5, 10, 7);
dirLight.castShadow = true; // 開啟陰影
scene.add(dirLight);
// 讓立方體投射與接受陰影
renderer.shadowMap.enabled = true;
cube.castShadow = true;
cube.receiveShadow = true;
範例 C:載入外部模型(GLTF)
const loader = new THREE.GLTFLoader();
loader.load('models/scene.gltf', gltf => {
const model = gltf.scene;
model.traverse(node => {
if (node.isMesh) {
node.castShadow = true;
node.receiveShadow = true;
}
});
scene.add(model);
}, undefined, err => console.error(err));
範例 D:使用後處理(Composer + Bloom)
// 建立 RenderPass
const renderPass = new THREE.RenderPass(scene, camera);
// Bloom 效果
const bloomPass = new THREE.UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // 強度
0.4, // 半徑
0.85 // 閾值
);
const composer = new THREE.EffectComposer(renderer);
composer.addPass(renderPass);
composer.addPass(bloomPass);
// 在動畫迴圈中改用 composer.render()
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
composer.render();
}
animate();
小技巧:在開發階段先使用
renderer.render(scene, camera);確認基本渲染,待所有物件正確顯示後再加入EffectComposer,可避免除錯時被後處理「掩蓋」問題。
常見陷阱與最佳實踐
| 陷阱 | 可能的症狀 | 解決方法或最佳實踐 |
|---|---|---|
| 相機遠近裁剪範圍不合適 | 物件消失、Z‑fighting(深度抖動) | near 設為盡可能大的值,far 設為必要的最遠距離。 |
| 忘記啟用陰影或設定陰影地圖大小 | 陰影不顯示或模糊 | renderer.shadowMap.enabled = true; dirLight.shadow.mapSize.width = 2048; |
| 材質未支援光照 | 物件看起來像是「平面貼圖」 | 使用 MeshStandardMaterial、MeshPhongMaterial 或 MeshLambertMaterial。 |
| 場景內物件過多未使用層級管理 | FPS 大幅下降 | 利用 Object3D 分組、FrustumCulling、LOD(Level of Detail)等技術。 |
| 視口尺寸變更未同步更新 | 畫面被拉伸或比例錯亂 | 在 window.resize 事件中呼叫 renderer.setSize() 與 camera.aspect = ...; camera.updateProjectionMatrix(); |
最佳實踐:
- 先建立最小可執行範例(Renderer + Scene + Camera),確認渲染正常後,再逐步加入物件與光源。
- 使用
requestAnimationFrame取代setInterval,讓瀏覽器自行同步更新頻率。 - 分離渲染與更新邏輯:將動畫、物理、UI 等更新寫在
tick函式,最後統一呼叫renderer.render()。 - 針對行動裝置調整解析度:
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));以免過高的解析度拖慢效能。 - 使用開發者工具(Three.js Inspector):可即時檢視場景圖、相機參數與材質設定,快速定位問題。
實際應用場景
| 領域 | 典型案例 | 渲染管線的應用重點 |
|---|---|---|
| 產品展示 | 3D 電子商務(家具、汽車) | 高品質材質 + 環境光遮蔽(HDRI) 使用 OrbitControls 讓使用者自由旋轉。 |
| 資料視覺化 | 三維圖表、地圖投影 | OrthographicCamera 搭配 InstancedMesh 渲染大量點雲。 |
| 互動遊戲 | WebGL 小型遊戲 | EffectComposer 加上後處理(Bloom、FXAA)提升視覺效果。 |
| 虛擬實境 (VR) / 擴增實境 (AR) | WebXR 體驗 | 需要同時建立兩個相機(左/右眼)並使用 WebXRManager,渲染流程仍以 Renderer → Scene → Camera 為核心。 |
| 教育與培訓 | 交互式教學模擬 | 利用 AnimationMixer 控制模型動畫,渲染管線保持不變,只是每幀更新物件狀態。 |
重點:不論是哪一種應用,渲染管線的四大要素 都是不可或缺的基礎,只有在此基礎上再加入特效或優化,才能保證程式碼的可維護性與效能。
總結
Three.js 的渲染管線可以簡化為 Renderer → Scene → Camera → Objects 四個步驟。
- Renderer 負責把 JavaScript 資料送到 GPU,決定畫布大小與後處理。
- Scene 是所有 3D 元素的容器,使用階層結構管理變換與可見性。
- Camera 定義觀察者的視角與投影方式,
near/far必須適當調整以避免深度問題。 - Objects 包括幾何體、材質、光源與模型,是最終呈現在螢幕上的實體。
透過本文的概念說明與實作範例,你應該已能自行建立一個完整的 Three.js 基礎渲染流程,並了解常見的陷阱與最佳實踐。未來只要在此基礎上擴充光照、後處理、動畫或 XR 功能,就能快速開發出功能強大且效能穩定的 3D 網頁應用。祝你玩得開心,創作出令人驚艷的 Web 3D 體驗!