本文 AI 產出,尚未審核

Three.js 教學:幾何體與 Mesh — 使用 Line、Points 等不同物件

簡介

在 3D 網頁開發中,Three.js 提供了豐富的繪製基元,讓開發者可以快速構築各式各樣的視覺效果。除了最常見的 Mesh(由幾何體 + 材質組成)之外,LineLineLoopLineSegmentsPoints 也是不可或缺的工具,尤其在繪製線框、路徑、粒子系統等情境時,能大幅提升效能與表現力。

本單元將聚焦於 如何在 Three.js 中使用不同的物件類型,說明它們的特性、建立方式與實務應用。即使你是剛接觸 Three.js 的新手,或是已經能寫出簡單場景的中階開發者,閱讀完本篇後都能掌握:

  • 何時該使用 MeshLinePoints
  • 各類型的建構流程與常見參數
  • 在實際專案中避免的陷阱與最佳實踐

核心概念

1. 為什麼有多種「物件」?

Three.js 把 渲染管線 分成了不同的渲染模式:

物件類型 渲染方式 典型用途
Mesh 三角形光柵化(Triangle rasterization) 立體模型、角色、建築
LineLineLoopLineSegments 線段光柵化(Line rasterization) 線框、路徑、座標軸
Points 點渲染(Point rasterization) 粒子系統、星空、點雲資料

使用不同的渲染方式可以 降低 GPU 負擔,例如大量的粒子只需要點渲染,而不必浪費三角形的計算資源。


2. 基本建構流程

無論是哪一種物件,建立流程大致相同:

  1. 建立幾何體(Geometry / BufferGeometry)
    定義頂點座標、索引(若需要)以及其他屬性(如顏色、法線、UV)。
  2. 建立對應的材質(Material)
    依照渲染模式選擇 MeshBasicMaterialLineBasicMaterialPointsMaterial 等。
  3. 組合成物件(Object3D)
    new Mesh(...)new Line(...)new Points(...) 包裝起來。
  4. 將物件加入場景(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 顏色不會顯示,全部變成白色或預設色 若要使用每個頂點的顏色,務必在 LineBasicMaterialPointsMaterial 中開啟 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 重新定義可見區間
未考慮座標系統差異 物件出現在相機背後或不可見 確認 cameranearfar 以及物件的 position,必要時使用 scene.add( object ) 前先 object.lookAt( camera.position )

最佳實踐小結

  1. 盡量使用 BufferGeometry:相較於舊式 Geometry,前者記憶體佔用更低、效能更佳。
  2. 批次渲染:同一材質的多個物件可合併成單一 InstancedMeshLineSegments,減少 draw call。
  3. 適時切換渲染模式:大量點使用 Points,線框使用 Line*,完整模型才使用 Mesh
  4. 資源管理:使用完的貼圖、幾何體與材質要記得 dispose(),避免記憶體泄漏。

實際應用場景

場景 建議使用的物件 為什麼
星空背景 Points + PointsMaterial(透明貼圖) 數千至數萬顆星星只需要點渲染,效能極佳
建築線框視圖 LineSegmentsLineLoop + 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 網頁開發的路上玩得開心、寫得順利! 🚀