本文 AI 產出,尚未審核

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) 轉換。

最佳實踐

  1. 盡可能使用索引(index):同一頂點被多個三角形共用時,使用 setIndex 可減少記憶體與 GPU 帶寬。
  2. 預先分配 TypedArray:在大量頂點的情況下,先建立固定長度的 Float32Array,避免在動畫中不斷 push 產生 GC。
  3. 使用 computeBoundingBox / computeBoundingSphere:若自行建立幾何體,呼叫這兩個方法可讓相機自動聚焦與剔除(frustum culling)更有效。
  4. 結合 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 形態。祝你玩得開心,創作無限!