本文 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 支援光照或貼圖,必須提供 normaluv

// 法線:因為平面位於 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,配合 MeshBasicMaterialMeshLambertMaterialvertexColors 參數使用:

// 為每個頂點設定不同顏色
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
程式化地形 依照噪聲函式產生高程資料,直接寫入 positionnormaluv,即時更新可製作動態山脈或海浪。
自訂粒子系統 每個粒子可視為一個小三角形或四邊形,使用單一 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 形態。祝你玩得開心,創作無限!