你是否曾經在瀏覽網頁時,被那些會隨著滑鼠移動而旋轉的 3D 模型、或是充滿空間感的視覺效果深深吸引?這些令人驚艷的互動體驗,背後都少不了 Three.js 這套強大的 JavaScript 函式庫。對於想要在網頁中加入立體視覺元素的開發者來說,Three.js 提供了一個相對友善的入口,讓你不需要深入理解複雜的 WebGL 底層語法,就能創造出專業級的 3D 場景。
Three.js 是建立在 WebGL 之上的 JavaScript 函式庫,讓開發者能用更簡潔的語法創作 3D 網頁內容。本文將帶你認識場景、相機、渲染器三大核心元素,學會建立基礎幾何體與光源,並掌握動畫循環與互動控制。透過實際範例與錯誤對照表,你將能獨立完成第一個 3D 專案,為後續更複雜的視覺創作打下穩固基礎。
為什麼選擇 Three.js 而非直接使用 WebGL
WebGL 是瀏覽器內建的圖形 API,能夠直接操控 GPU 進行高效能運算。
但它的學習曲線非常陡峭。
你需要撰寫著色器程式(Shader),處理頂點座標、矩陣運算、光照計算等底層細節。對於大多數前端開發者來說,這些數學與圖學知識門檻相當高。
Three.js 的出現改變了這個局面。
它將 WebGL 的複雜操作封裝成更直覺的物件與方法,讓你能夠專注在創意發想與場景設計上。你可以用幾行程式碼就建立一個立方體,而不需要手動計算每個頂點的位置。
這種抽象化並不代表失去彈性。
Three.js 仍然保留了足夠的自訂空間,當你需要更精細的控制時,依然可以深入修改材質、光源演算法,甚至撰寫自己的著色器。
對於想要快速產出成果、又希望保有擴充性的開發者來說,Three.js 是目前最理想的選擇。
Three.js 的三大核心元素
每個 Three.js 專案都建立在三個基礎元件之上:場景(Scene)、相機(Camera)、渲染器(Renderer)。
理解這三者的關係,就像理解劇場的運作方式。
場景是舞台本身。
你在場景中放置所有的物件,包括 3D 模型、光源、霧效等等。它是一個容器,負責管理所有需要被渲染的元素。
相機是觀眾的視角。
它決定了從哪個位置、以什麼角度來觀看場景。Three.js 提供多種相機類型,最常用的是透視相機(PerspectiveCamera),它模擬人眼的視覺效果,近大遠小。
渲染器是實際執行繪製的引擎。
它接收場景與相機的資訊,計算出每個像素應該顯示什麼顏色,最後輸出到 HTML 的 canvas 元素上。
這三者缺一不可。
沒有場景,就沒有內容可以顯示。沒有相機,就不知道該從哪裡看。沒有渲染器,所有的計算都無法呈現在螢幕上。
建立你的第一個 3D 場景
讓我們用實際程式碼來感受 Three.js 的魅力。
以下是建立一個旋轉立方體的完整流程:
- 引入 Three.js 函式庫(可以透過 CDN 或 npm 安裝)
- 建立場景、相機、渲染器三大元件
- 創建幾何體與材質,組合成網格物件
- 將網格物件加入場景中
- 設定動畫循環,讓立方體持續旋轉
- 將渲染結果輸出到網頁上
這個流程看起來步驟不少,但每一步都很直覺。
首先建立場景物件:
const scene = new THREE.Scene();
接著設定相機的視野角度、畫面比例、可視範圍:
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
然後初始化渲染器,並將它的 canvas 元素加到網頁中:
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
現在舞台已經搭好,該放入主角了。
建立一個立方體需要兩個東西:幾何形狀與材質。
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
最後設定動畫循環,讓立方體不斷旋轉:
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
存檔後開啟瀏覽器,你會看到一個綠色的立方體在畫面中央緩緩旋轉。
這就是你的第一個 Three.js 作品。
幾何體與材質的選擇策略
Three.js 內建了豐富的幾何體類型,從基本的立方體、球體、圓柱,到複雜的環面、多面體都有。
選擇合適的幾何體能讓你的創作事半功倍。
以下是常用的幾何體類型與適用情境:
- BoxGeometry:建築物、箱子、方塊狀物件
- SphereGeometry:星球、泡泡、球形裝飾
- CylinderGeometry:柱子、罐子、管狀結構
- PlaneGeometry:地板、牆面、平面背景
- TorusGeometry:甜甜圈、環狀飾品
材質則決定了物體的外觀質感。
最基礎的 MeshBasicMaterial 不受光照影響,永遠保持同樣的顏色。適合用在不需要光影變化的場景,或是當作初期測試使用。
MeshStandardMaterial 是目前最推薦的材質類型。
它支援物理基礎渲染(PBR),能夠真實呈現金屬、塑膠、布料等不同材質的光澤與反射。透過調整粗糙度(roughness)與金屬度(metalness)參數,你可以模擬出各種真實世界的表面質感。
MeshPhongMaterial 則適合需要高光效果的物體,像是陶瓷、漆面等表面會產生明顯亮點的材質。
光源讓場景活起來
沒有光,再精緻的模型也只是一片漆黑。
Three.js 提供多種光源類型,各有不同的照明特性。
環境光(AmbientLight)是最基礎的光源。
它均勻照亮場景中的所有物體,不產生陰影或方向性。通常用來提升整體亮度,避免暗部過黑。
方向光(DirectionalLight)模擬太陽光的效果。
光線平行射出,不會隨距離衰減。適合用來創造戶外場景的主光源,能夠產生清晰的陰影。
點光源(PointLight)像是燈泡一樣向四面八方發光。
光線強度會隨距離遞減,適合模擬室內照明、火把、街燈等局部光源。
聚光燈(SpotLight)則像舞台燈光一樣,產生錐形的照射範圍。
你可以調整光錐角度、邊緣柔化程度、照射距離等參數,創造出戲劇化的光影效果。
實務上,場景通常會混合使用多種光源。
一個典型的配置是:一盞環境光提供基礎照明,一盞方向光作為主光源,再加上一兩盞點光源或聚光燈製造重點光效。
記住,光源數量會直接影響渲染效能。每增加一盞即時光源,GPU 的計算負擔就會增加。如果場景需要大量光源,考慮使用光照貼圖(Lightmap)或環境貼圖(Environment Map)來預先烘焙光照效果。
動畫與互動控制
靜態的 3D 模型只能展示造型,真正吸引人的是動態與互動。
Three.js 的動畫系統建立在 requestAnimationFrame 之上。
這個瀏覽器 API 會在每次重繪畫面前呼叫你指定的函式,確保動畫與螢幕更新率同步。大多數螢幕是 60Hz,所以你的動畫函式每秒會被呼叫 60 次。
在動畫循環中,你可以修改物體的位置、旋轉、縮放等屬性。
function animate() {
requestAnimationFrame(animate);
// 持續旋轉
mesh.rotation.y += 0.01;
// 上下浮動
mesh.position.y = Math.sin(Date.now() * 0.001) * 2;
renderer.render(scene, camera);
}
互動控制則需要監聽使用者的輸入事件。
最常用的是 OrbitControls,它讓使用者可以用滑鼠拖曳來旋轉視角、滾輪縮放、右鍵平移。
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 啟用慣性效果
controls.dampingFactor = 0.05;
記得在動畫循環中呼叫 controls.update(),讓控制器的慣性效果能夠正確運作。
更進階的互動可以透過 Raycaster 實現。
它能夠偵測滑鼠點擊或移動時,射線與哪些物體相交。這讓你可以實作點擊物體觸發事件、滑鼠懸停改變顏色等互動效果。
常見錯誤與解決方案
初學者在使用 Three.js 時,經常會遇到一些典型問題。
以下表格整理了最常見的錯誤與對應的解決方法:
| 問題現象 | 可能原因 | 解決方式 |
|---|---|---|
| 畫面一片黑 | 相機位置在物體內部或太遠 | 調整 camera.position.z 到適當距離 |
| 物體看不到 | 沒有加入場景或在相機視野外 | 確認已執行 scene.add() 且物體在可視範圍內 |
| 材質沒有光影 | 使用 MeshBasicMaterial 且場景無光源 | 改用 MeshStandardMaterial 並加入光源 |
| 動畫卡頓 | 渲染負擔過重或未使用 requestAnimationFrame | 減少多邊形數量或優化光源配置 |
| 控制器無反應 | 忘記在動畫循環中更新控制器 | 在 animate 函式中加入 controls.update() |
| 視窗調整後畫面變形 | 未監聽 resize 事件更新相機與渲染器 | 加入視窗大小改變的事件處理 |
視窗調整的處理特別重要。
當使用者改變瀏覽器大小時,你需要同步更新相機的長寬比與渲染器的尺寸:
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
另一個常見問題是效能優化。
如果你的場景包含大量物件,每個物件都是獨立的 Mesh,渲染器需要為每個物件執行一次繪製呼叫(Draw Call)。這會嚴重影響效能。
解決方法是使用 InstancedMesh。
它讓你用一次繪製呼叫就能渲染數千個相同幾何體的物件,只要它們使用相同的材質。這對於創作粒子效果、森林場景、城市建築等需要大量重複物件的情境特別有用。
載入外部 3D 模型
自己用程式碼建立幾何體有其限制。
當你需要更複雜的模型時,通常會使用 3D 建模軟體(如 Blender)製作,再匯入到 Three.js 中。
最推薦的格式是 glTF(GL Transmission Format)。
它是專為網頁傳輸設計的 3D 格式,支援幾何體、材質、動畫、骨架等完整資訊,且檔案大小經過優化。
Three.js 提供了 GLTFLoader 來載入 glTF 檔案:
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
loader.load('model.glb', (gltf) => {
scene.add(gltf.scene);
}, undefined, (error) => {
console.error('載入失敗', error);
});
載入是非同步的。
你需要在載入完成的回呼函式中才能存取模型。如果模型包含動畫,可以透過 AnimationMixer 來播放:
const mixer = new THREE.AnimationMixer(gltf.scene);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
// 在動畫循環中更新
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
mixer.update(clock.getDelta());
renderer.render(scene, camera);
}
注意模型的大小與座標系統。
不同的 3D 軟體可能使用不同的單位與軸向。載入後可能需要調整模型的 scale 或 rotation 才能正確顯示。
後處理效果讓視覺更專業
後處理(Post-processing)是在場景渲染完成後,對整個畫面進行額外的影像處理。
這些效果能大幅提升視覺品質。
常見的後處理效果包括:
- 泛光(Bloom):讓明亮區域產生光暈效果
- 景深(Depth of Field):模擬相機焦距,讓焦點外的物體模糊
- 色彩校正(Color Correction):調整對比、飽和度、色調
- 抗鋸齒(Anti-aliasing):平滑邊緣,消除鋸齒狀
- 暈影(Vignette):畫面邊緣變暗的效果
Three.js 透過 EffectComposer 來管理後處理流程。
你需要先建立 composer,再加入各種 pass(處理步驟):
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.5, // 強度
0.4, // 半徑
0.85 // 閾值
);
composer.addPass(bloomPass);
使用 composer 後,你的動畫循環要改成呼叫 composer.render() 而非 renderer.render()。
後處理會增加 GPU 負擔。
每個 pass 都需要額外的運算時間,特別是在行動裝置上可能會明顯影響幀率。建議根據裝置效能動態調整後處理的品質或是否啟用。
效能優化的實戰技巧
Three.js 專案的效能瓶頸通常出現在以下幾個地方。
多邊形數量是最直接的影響因素。
每個三角形都需要 GPU 處理。高精度模型可能包含數十萬個三角形,對效能造成沉重負擔。解決方法是使用 LOD(Level of Detail),根據物體與相機的距離自動切換不同精度的模型。
材質與光源的複雜度也很關鍵。
MeshStandardMaterial 比 MeshBasicMaterial 需要更多運算。每盞即時光源都會增加著色器的計算量。如果場景不需要動態光影,考慮使用預先烘焙的光照貼圖。
繪製呼叫次數在場景物件很多時會成為瓶頸。
合併靜態物件、使用 InstancedMesh、或是透過 BufferGeometryUtils.mergeBufferGeometries 將多個幾何體合併,都能有效減少繪製呼叫。
紋理大小影響記憶體用量與載入時間。
盡量使用 2 的次方尺寸(512、1024、2048),讓 GPU 能夠產生 mipmap 優化。不需要高解析度的紋理就不要用 4K,行動裝置上 1024px 通常就足夠。
使用瀏覽器的開發者工具監控效能。
Chrome 的 Performance 面板能顯示每一幀的渲染時間。Three.js 也提供了 Stats.js 套件,能在畫面上即時顯示 FPS 與記憶體用量。
import Stats from 'three/examples/jsm/libs/stats.module.js';
const stats = new Stats();
document.body.appendChild(stats.dom);
function animate() {
stats.begin();
// 你的渲染程式碼
stats.end();
}
從入門到創作的下一步
現在你已經掌握了 Three.js 的核心概念與基礎技術。
場景、相機、渲染器構成了 3D 世界的骨架。幾何體與材質賦予物體形狀與質感。光源讓場景充滿生命力。動畫與互動讓作品能夠與觀眾對話。
但這只是起點。
真正的創作需要你持續實驗與累積經驗。試著重現你喜歡的網頁效果,分析它們使用了哪些技術。參與 Three.js 社群,觀摩其他創作者的作品與原始碼。
從簡單的專案開始,逐步增加複雜度。
先做一個會跟隨滑鼠旋轉的地球,再加上雲層與大氣效果。建立一個產品展示頁面,讓使用者能 360 度檢視模型。創作一個互動式的資料視覺化,用 3D 圖表呈現多維度資訊。
每完成一個專案,你對 Three.js 的理解就會更深一層。你會開始思考如何優化效能、如何設計更流暢的互動、如何讓視覺效果更吸引人。這些經驗會內化成你的創作直覺,讓你能夠更自由地實現腦中的想像。
網頁 3D 技術仍在快速演進。WebGPU 即將成為下一代圖形 API,Three.js 也持續更新以支援新特性。保持學習的熱情,你會發現這個領域充滿無限可能。現在就打開編輯器,開始創作你的第一個 3D 作品吧。