Three.js – 幾何體與 Mesh
主題:BufferGeometry 與自訂頂點
簡介
在 Three.js 中,幾何體(Geometry) 是所有 3D 物件的基礎,而 BufferGeometry 則是目前官方推薦的資料結構。相較於舊版的 Geometry,它直接以 TypedArray(如 Float32Array)儲存頂點資訊,效能更佳、記憶體占用更小,且更貼近 WebGL 的底層 API。
掌握 自訂頂點 的技巧,意味著你可以不受限於 Three.js 內建的球體、立方體等形狀,隨心所欲地繪製任意多邊形、程式化產生地形、或是實作特殊的特效(例如波浪、粒子系統)。本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,一步步帶你建立自己的 BufferGeometry。
核心概念
1. BufferGeometry 的基本結構
BufferGeometry 由 屬性(attribute) 與 索引(index) 兩大部分組成。
| 屬性 | 說明 | 常見型別 |
|---|---|---|
position |
每個頂點的座標 (x, y, z) | Float32Array |
normal |
法線向量,用於光照計算 | Float32Array |
uv |
紋理座標 (u, v) | Float32Array |
color |
頂點顏色 (r, g, b) | Float32Array |
index |
定義三角形組成的頂點索引 | Uint16Array / Uint32Array |
重點:屬性資料必須以 平行陣列(flat array)的形式提供,且長度必須是 itemSize(每個頂點的分量數) 的倍數。
2. 建立最簡單的自訂幾何體
以下範例建立一個 等腰三角形(兩個三角形組成的平面):
// 1. 建立 BufferGeometry 物件
const geometry = new THREE.BufferGeometry();
// 2. 定義頂點座標 (4 個頂點, 每個 3 個分量)
const vertices = new Float32Array([
// x, y, z
0, 1, 0, // V0
-1, -1, 0, // V1
1, -1, 0, // V2
0, -1, 0 // V3 (額外頂點,用於第二個三角形)
]);
// 3. 建立 BufferAttribute 並加入 geometry
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
// 4. 若要使用索引,定義三角形組成方式
const indices = new Uint16Array([
0, 1, 2, // 第一個三角形
2, 1, 3 // 第二個三角形
]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
// 5. 建立 Mesh 並加入場景
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
註解:
setAttribute的第二個參數3代表 itemSize(每個頂點 3 個座標)。- 使用
setIndex可以讓同一頂點被多個三角形重複使用,減少資料量。
3. 加入法線與 UV
若要讓 Mesh 支援光照或貼圖,必須提供 normal 與 uv:
// 法線:因為平面位於 XY 平面,法線皆指向 +Z
const normals = new Float32Array([
0, 0, 1, // V0
0, 0, 1, // V1
0, 0, 1, // V2
0, 0, 1 // V3
]);
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
// UV 座標:映射整張貼圖到平面
const uvs = new Float32Array([
0.5, 1, // V0
0, 0, // V1
1, 0, // V2
0.5, 0 // V3
]);
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
4. 動態更新頂點資料
在動畫或互動應用中,常需要 即時改變頂點位置。只要修改 array 後呼叫 needsUpdate 即可:
function animate() {
requestAnimationFrame(animate);
// 讓頂點在 Y 軸上下震動
const pos = geometry.attributes.position.array;
const time = Date.now() * 0.001;
for (let i = 1; i < pos.length; i += 3) {
// 只改變 Y 分量
pos[i] = Math.sin(time + i) * 0.2;
}
geometry.attributes.position.needsUpdate = true; // 必須設定
renderer.render(scene, camera);
}
animate();
5. 自訂屬性:頂點顏色
Three.js 允許你自行定義額外屬性,最常見的是 vertex colors,配合 MeshBasicMaterial 或 MeshLambertMaterial 的 vertexColors 參數使用:
// 為每個頂點設定不同顏色
const colors = new Float32Array([
1, 0, 0, // 紅 (V0)
0, 1, 0, // 綠 (V1)
0, 0, 1, // 藍 (V2)
1, 1, 0 // 黃 (V3)
]);
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.MeshBasicMaterial({
vertexColors: true, // 啟用頂點顏色
side: THREE.DoubleSide
});
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
忘記設定 needsUpdate |
動態改變 BufferAttribute 後,若未標記 needsUpdate = true,渲染結果不會更新。 |
每次改變陣列後必須呼叫 attribute.needsUpdate = true。 |
| 索引型別不合 | 索引長度超過 Uint16Array 能表示的範圍(65535)時,會出現渲染錯誤。 |
使用 Uint32Array 並在建立 renderer 時設定 renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.getContext().getExtension('OES_element_index_uint');。 |
| 資料長度不對齊 | array.length 必須是 itemSize 的倍數,否則會拋出錯誤。 |
在填入資料前先檢查 array.length % itemSize === 0。 |
| 法線缺失 | 未提供 normal 時,光照模型會使用自動產生的法線,可能導致不正確的光照。 |
使用 geometry.computeVertexNormals() 或手動提供法線。 |
過度使用 Geometry |
舊版的 Geometry 仍在文件中出現,但已被棄用,效能較差。 |
盡量改用 BufferGeometry,並在需要時使用 new THREE.Geometry().fromBufferGeometry(bufferGeometry) 轉換。 |
最佳實踐
- 盡可能使用索引(index):同一頂點被多個三角形共用時,使用
setIndex可減少記憶體與 GPU 帶寬。 - 預先分配 TypedArray:在大量頂點的情況下,先建立固定長度的
Float32Array,避免在動畫中不斷push產生 GC。 - 使用
computeBoundingBox/computeBoundingSphere:若自行建立幾何體,呼叫這兩個方法可讓相機自動聚焦與剔除(frustum culling)更有效。 - 結合
ShaderMaterial時,將自訂屬性傳入attributes:保持資料結構一致,避免在 GPU 與 CPU 之間頻繁轉換。
實際應用場景
| 場景 | 為什麼需要自訂 BufferGeometry |
|---|---|
| 程式化地形 | 依照噪聲函式產生高程資料,直接寫入 position、normal、uv,即時更新可製作動態山脈或海浪。 |
| 自訂粒子系統 | 每個粒子可視為一個小三角形或四邊形,使用單一 BufferGeometry 並在 shader 中以 gl_PointSize 控制大小,提升效能。 |
| 建築模型的 LOD | 低階 LOD 只保留關鍵頂點,使用簡化後的 BufferGeometry;切換 LOD 時僅替換 geometry,無需重新建立 Mesh。 |
| 特殊特效(例如光束、雷射) | 透過自訂屬性(如 float progress)在 shader 中控制顏色與透明度,實現漸變或脈衝效果。 |
| 即時編輯工具 | 在 3D 建模或 VR/AR 內的即時編輯器裡,使用 geometry.attributes.position 直接修改頂點,讓使用者即時看到變化。 |
總結
BufferGeometry 是 Three.js 中最核心、效能最佳的幾何體資料結構。掌握 自訂頂點、索引、屬性(法線、UV、顏色)以及 動態更新 的技巧,可讓你突破預設模型的限制,打造任意形狀與特效。記得留意常見的型別、長度對齊與 needsUpdate 設定,並遵循「盡量使用索引、預先分配 TypedArray」的最佳實踐,將讓你的 3D 應用在效能與可維護性上都有顯著提升。
在未來的專案中,無論是程式化地形、即時粒子系統或是自訂 shader 效果,只要熟練本篇介紹的概念與範例,你就能在 Three.js 的世界裡自如地「畫」出任何你想要的 3D 形態。祝你玩得開心,創作無限!