Three.js 教學:幾何體與 Mesh — 使用 Line、Points 等不同物件
簡介
在 3D 網頁開發中,Three.js 提供了豐富的繪製基元,讓開發者可以快速構築各式各樣的視覺效果。除了最常見的 Mesh(由幾何體 + 材質組成)之外,Line、LineLoop、LineSegments、Points 也是不可或缺的工具,尤其在繪製線框、路徑、粒子系統等情境時,能大幅提升效能與表現力。
本單元將聚焦於 如何在 Three.js 中使用不同的物件類型,說明它們的特性、建立方式與實務應用。即使你是剛接觸 Three.js 的新手,或是已經能寫出簡單場景的中階開發者,閱讀完本篇後都能掌握:
- 何時該使用
Mesh、Line或Points - 各類型的建構流程與常見參數
- 在實際專案中避免的陷阱與最佳實踐
核心概念
1. 為什麼有多種「物件」?
Three.js 把 渲染管線 分成了不同的渲染模式:
| 物件類型 | 渲染方式 | 典型用途 |
|---|---|---|
Mesh |
三角形光柵化(Triangle rasterization) | 立體模型、角色、建築 |
Line、LineLoop、LineSegments |
線段光柵化(Line rasterization) | 線框、路徑、座標軸 |
Points |
點渲染(Point rasterization) | 粒子系統、星空、點雲資料 |
使用不同的渲染方式可以 降低 GPU 負擔,例如大量的粒子只需要點渲染,而不必浪費三角形的計算資源。
2. 基本建構流程
無論是哪一種物件,建立流程大致相同:
- 建立幾何體(Geometry / BufferGeometry)
定義頂點座標、索引(若需要)以及其他屬性(如顏色、法線、UV)。 - 建立對應的材質(Material)
依照渲染模式選擇MeshBasicMaterial、LineBasicMaterial、PointsMaterial等。 - 組合成物件(Object3D)
用new Mesh(...)、new Line(...)、new Points(...)包裝起來。 - 將物件加入場景(scene),最後在渲染迴圈中呼叫
renderer.render(scene, camera)。
下面的範例會一步一步示範每個步驟。
3. 程式碼範例
註:以下範例皆以 ES6 模組 為前提,假設已在 HTML 中載入
three.module.js。
3.1 範例一:基本的 Mesh(立方體)
import * as THREE from 'three';
// 1. 建立幾何體
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
// 2. 建立材質
const boxMaterial = new THREE.MeshStandardMaterial({
color: 0x156289,
metalness: 0.5,
roughness: 0.2,
});
// 3. 組合成 Mesh
const boxMesh = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(boxMesh);
重點:
MeshStandardMaterial會受到光源影響,適合需要真實感的模型。
3.2 範例二:使用 Line 繪製座標軸
// 1. 座標軸的頂點資料
const axesVertices = new Float32Array([
// X 軸 (紅)
0, 0, 0, 5, 0, 0,
// Y 軸 (綠)
0, 0, 0, 0, 5, 0,
// Z 軸 (藍)
0, 0, 0, 0, 0, 5,
]);
const axesGeometry = new THREE.BufferGeometry();
axesGeometry.setAttribute('position', new THREE.BufferAttribute(axesVertices, 3));
// 2. 為每條軸使用不同顏色
const axesColors = new Float32Array([
// X: red
1, 0, 0, 1, 0, 0,
// Y: green
0, 1, 0, 0, 1, 0,
// Z: blue
0, 0, 1, 0, 0, 1,
]);
axesGeometry.setAttribute('color', new THREE.BufferAttribute(axesColors, 3));
// 3. 使用 LineBasicMaterial 並啟用 vertexColors
const axesMaterial = new THREE.LineBasicMaterial({ vertexColors: true });
// 4. 建立 Line (一次繪製多條線段)
const axesHelper = new THREE.LineSegments(axesGeometry, axesMaterial);
scene.add(axesHelper);
技巧:
LineSegments會把每兩個頂點視為一條獨立的線段,適合畫多條不相連的線。若想形成閉合環路,使用LineLoop。
3.3 範例三:動態更新的線條(波形)
// 1. 建立空的 BufferGeometry,預留 200 個點
const pointsCount = 200;
const waveGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(pointsCount * 3); // x, y, z
waveGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
// 2. 使用 LineBasicMaterial
const waveMaterial = new THREE.LineBasicMaterial({ color: 0xffaa00 });
// 3. 建立 Line 物件
const waveLine = new THREE.Line(waveGeometry, waveMaterial);
scene.add(waveLine);
// 4. 在動畫迴圈中更新頂點位置
function animateWave(time) {
const pos = waveGeometry.attributes.position.array;
for (let i = 0; i < pointsCount; i++) {
const x = (i / (pointsCount - 1)) * 10 - 5; // -5 ~ +5
const y = Math.sin(x * 2 + time * 0.002) * 1.5;
const z = 0;
pos[i * 3] = x;
pos[i * 3 + 1] = y;
pos[i * 3 + 2] = z;
}
waveGeometry.attributes.position.needsUpdate = true; // 告訴 GPU 重新上傳
}
說明:
needsUpdate = true是 關鍵,否則變更不會反映在畫面上。
3.4 範例四:基本的 Points(粒子系統)
// 1. 隨機產生 1000 個粒子座標
const particleCount = 1000;
const particlePositions = new Float32Array(particleCount * 3);
for (let i = 0; i < particleCount; i++) {
particlePositions[i * 3] = (Math.random() - 0.5) * 20; // x
particlePositions[i * 3 + 1] = (Math.random() - 0.5) * 20; // y
particlePositions[i * 3 + 2] = (Math.random() - 0.5) * 20; // z
}
// 2. 建立 BufferGeometry
const particleGeometry = new THREE.BufferGeometry();
particleGeometry.setAttribute('position', new THREE.BufferAttribute(particlePositions, 3));
// 3. PointsMaterial(可設定大小、顏色、貼圖)
const particleMaterial = new THREE.PointsMaterial({
size: 0.2,
color: 0xffffff,
map: new THREE.TextureLoader().load('textures/sprite.png'), // 透明貼圖
transparent: true,
depthWrite: false, // 讓粒子不會互相遮擋
});
// 4. 建立 Points 物件
const particles = new THREE.Points(particleGeometry, particleMaterial);
scene.add(particles);
小技巧:
depthWrite: false常用於星空或煙霧等效果,避免遠近排序產生奇怪的遮蔽。
3.5 範例五:結合 Line 與 Points – 繪製路徑與節點
// 假設有一條路徑的座標陣列
const path = [
new THREE.Vector3(-5, 0, 0),
new THREE.Vector3(-3, 2, 0),
new THREE.Vector3(0, 3, 0),
new THREE.Vector3(3, 2, 0),
new THREE.Vector3(5, 0, 0),
];
// 1. 建立 Line
const lineGeom = new THREE.BufferGeometry().setFromPoints(path);
const lineMat = new THREE.LineBasicMaterial({ color: 0x00ff00 });
const pathLine = new THREE.Line(lineGeom, lineMat);
scene.add(pathLine);
// 2. 在每個節點放置一個小球(使用 Points 做為簡易標記)
const nodeGeom = new THREE.BufferGeometry().setFromPoints(path);
const nodeMat = new THREE.PointsMaterial({ size: 0.4, color: 0xff0000 });
const nodes = new THREE.Points(nodeGeom, nodeMat);
scene.add(nodes);
說明:
setFromPoints會自動把Vector3陣列轉成position屬性,省去手動建立Float32Array的步驟。
常見陷阱與最佳實踐
| 陷阱 | 可能的結果 | 解決方式 / 最佳實踐 |
|---|---|---|
忘記設定 material.vertexColors |
顏色不會顯示,全部變成白色或預設色 | 若要使用每個頂點的顏色,務必在 LineBasicMaterial 或 PointsMaterial 中開啟 vertexColors: true |
大量 Mesh 造成渲染瓶頸 |
FPS 大幅下降 | 使用 InstancedMesh 或將多個相同幾何體合併成單一 BufferGeometry |
| Line 的寬度在 WebGL1 中無效 | 線條看起來只有 1px,無法調整粗細 | 在 WebGL2(Three.js r152 之後)或使用 THREE.Line2(基於 LineSegments2)搭配 LineMaterial |
| Points 的大小不隨距離變化 | 遠近的粒子看起來大小相同,缺乏深度感 | 開啟 sizeAttenuation: true(預設),或自行在 shader 中調整 |
忘記呼叫 needsUpdate |
更新頂點後畫面不變 | 修改 BufferAttribute 後必須設定 .needsUpdate = true,或使用 setDrawRange 重新定義可見區間 |
| 未考慮座標系統差異 | 物件出現在相機背後或不可見 | 確認 camera 的 near、far 以及物件的 position,必要時使用 scene.add( object ) 前先 object.lookAt( camera.position ) |
最佳實踐小結:
- 盡量使用
BufferGeometry:相較於舊式Geometry,前者記憶體佔用更低、效能更佳。 - 批次渲染:同一材質的多個物件可合併成單一
InstancedMesh或LineSegments,減少 draw call。 - 適時切換渲染模式:大量點使用
Points,線框使用Line*,完整模型才使用Mesh。 - 資源管理:使用完的貼圖、幾何體與材質要記得
dispose(),避免記憶體泄漏。
實際應用場景
| 場景 | 建議使用的物件 | 為什麼 |
|---|---|---|
| 星空背景 | Points + PointsMaterial(透明貼圖) |
數千至數萬顆星星只需要點渲染,效能極佳 |
| 建築線框視圖 | LineSegments 或 LineLoop + LineBasicMaterial |
只需要顯示邊緣,省去面材質的計算 |
| 道路或軌跡動畫 | Line + 動態更新 position |
可即時改變頂點形成動畫路徑 |
| 粒子特效(煙火、雨滴) | Points + 自訂 ShaderMaterial |
需要控制每顆粒子的大小、透明度與顏色變化 |
| 3D 資料視覺化(點雲) | Points + BufferGeometry(儲存大量座標) |
大量測量資料(如 LiDAR)直接以點呈現 |
| 遊戲 UI(指示線、光標) | Line + LineBasicMaterial(可加上 dashed 效果) |
簡單且可快速更新的 UI 元件 |
總結
- Three.js 為 3D 網頁開發提供了 Mesh、Line、Points 三大渲染類型,讓開發者能根據需求選擇最合適的效能方案。
- 建立流程始終是 幾何體 → 材質 → 物件 → 加入場景,只要掌握
BufferGeometry的使用,就能輕鬆處理大量頂點資料。 - 常見的坑包括 忘記設定頂點顏色、Line 寬度限制、未更新 BufferAttribute,只要遵循 最佳實踐(批次渲染、資源釋放、適時切換渲染模式),即可寫出高效、可維護的程式碼。
- 透過本篇的 5 個實作範例,你已經能在專案中快速實現 立方體、座標軸、波形線條、粒子系統、路徑+節點 等常見需求,並將它們應用於星空、建築線框、資料視覺化等實務場景。
掌握了 Line、Points 的使用後,你的 Three.js 項目將不再受限於傳統的 Mesh,能以更彈性的方式呈現各式 3D 效果。祝你在 3D 網頁開發的路上玩得開心、寫得順利! 🚀