本文 AI 產出,尚未審核

Three.js 粒子系統與特效 – Points 與粒子材質


簡介

在 3D 網頁互動中,粒子系統是營造星塵、火焰、雨滴等特效的關鍵工具。Three.js 以 THREE.Points 以及多樣的粒子材質(PointsMaterialShaderMaterial)提供了既簡潔又高效的實作方式。即使是剛接觸 WebGL 的新手,也能在短時間內完成令人驚豔的視覺效果。

本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,完整呈現 Points 與粒子材質的使用方法,幫助你快速在專案中加入動態粒子特效。


核心概念

1. 為什麼使用 Points 而不是 Mesh?

  • THREE.Points 只渲染每個頂點的座標,不需要三角形索引,GPU 處理量遠低於一般的 Mesh
  • 當粒子數量達到 數千到數萬 時,使用 Points 可保持 60 FPS 以上的流暢度。

2. 基本結構

Geometry (或 BufferGeometry)  → 位置 (position) attribute
          │
          ▼
   PointsMaterial (或自訂 ShaderMaterial)
          │
          ▼
        Points
  • Geometry:儲存每個粒子的座標、顏色、尺寸等屬性。
  • PointsMaterial:提供點的顏色、大小、透明度、貼圖等基本屬性。
  • Points:將 Geometry 與 Material 結合,交給渲染器繪製。

3. BufferGeometry 與 Attribute

自 Three.js r125 起,BufferGeometry 成為唯一推薦的幾何體型別。使用 Float32Array 建立 attribute,能讓資料一次性傳給 GPU,效能最佳化。

3.1 建立位置 attribute

const count = 5000;                     // 粒子數量
const positions = new Float32Array(count * 3); // x, y, z 三個分量

for (let i = 0; i < count; i++) {
  positions[i * 3 + 0] = (Math.random() - 0.5) * 200; // x
  positions[i * 3 + 1] = (Math.random() - 0.5) * 200; // y
  positions[i * 3 + 2] = (Math.random() - 0.5) * 200; // z
}

4. PointsMaterial 的重要屬性

屬性 說明 常用值
size 粒子在螢幕上的直徑(像素) 1~10
sizeAttenuation 是否根據相機距離衰減大小 true(預設)
map 粒子貼圖(常用透明 PNG) new THREE.TextureLoader().load('star.png')
transparent 啟用透明度混合 true
alphaTest 透明度測試門檻,降低過度渲染 0.5
vertexColors 是否使用每個頂點的顏色 true

程式碼範例

以下提供 5 個實用範例,從最簡單的靜態粒子到使用自訂 Shader 的動態特效。每段程式碼皆附上註解,方便直接貼上測試。

範例 1:最基本的 Points(單色)

// 1️⃣ 建立場景、相機與渲染器(略)
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 300);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

// 2️⃣ 建立 BufferGeometry 與 position attribute
const count = 2000;
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
  positions[i * 3 + 0] = (Math.random() - 0.5) * 400;
  positions[i * 3 + 1] = (Math.random() - 0.5) * 400;
  positions[i * 3 + 2] = (Math.random() - 0.5) * 400;
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

// 3️⃣ 使用單色 PointsMaterial
const material = new THREE.PointsMaterial({
  color: 0xffffff,
  size: 4,
  sizeAttenuation: true,
});

// 4️⃣ 建立 Points 並加入場景
const points = new THREE.Points(geometry, material);
scene.add(points);

// 5️⃣ 渲染迴圈
function animate() {
  requestAnimationFrame(animate);
  points.rotation.y += 0.001; // 輕微旋轉
  renderer.render(scene, camera);
}
animate();

重點:只要 sizeAttenuationtrue,粒子會隨相機遠近自動縮放,營造自然的深度感。

範例 2:使用貼圖與透明度

// 先載入透明 PNG(星星圖)
const tex = new THREE.TextureLoader().load('https://threejs.org/examples/textures/sprites/disc.png');

const material = new THREE.PointsMaterial({
  map: tex,
  size: 8,
  transparent: true,   // 必須開啟才能看到透明效果
  alphaTest: 0.5,      // 減少半透明像素的過度渲染
  depthWrite: false,  // 防止粒子相互遮擋產生深度衝突
});

// geometry 同上(省略)
const points = new THREE.Points(geometry, material);
scene.add(points);
  • depthWrite: false 能避免遠近粒子互相覆蓋的問題,常用於 星空火花 等特效。

範例 3:每顆粒子擁有不同顏色

// 產生顏色 attribute
const colors = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
  colors[i * 3 + 0] = Math.random(); // R
  colors[i * 3 + 1] = Math.random(); // G
  colors[i * 3 + 2] = Math.random(); // B
}
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

// 材質開啟 vertexColors
const material = new THREE.PointsMaterial({
  size: 5,
  vertexColors: true, // 讓每個點使用對應的顏色
  map: tex,
  transparent: true,
});

技巧:若同時使用 vertexColorscolor 參數,color 會作為全域基礎色,與每個頂點的顏色相乘。

範例 4:動態改變粒子大小(使用 sizeAttenuationonBeforeRender

const material = new THREE.PointsMaterial({
  size: 1,               // 初始大小
  sizeAttenuation: true,
  map: tex,
  transparent: true,
});

function animate() {
  requestAnimationFrame(animate);
  // 讓粒子隨時間脈衝變化
  const time = performance.now() * 0.001;
  material.size = 5 + Math.sin(time * 3) * 3; // 5~8 之間振盪
  renderer.render(scene, camera);
}
animate();
  • 直接改變 material.size 可即時影響所有粒子,適合製作 呼吸光環脈衝星 等簡易特效。

範例 5:自訂 ShaderMaterial 實作雨滴特效

// 雨滴的頂點與片元著色器
const vertexShader = `
  attribute float size;
  varying vec3 vColor;
  void main() {
    vColor = color;
    vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
    gl_PointSize = size * (300.0 / -mvPosition.z); // 手動衰減
    gl_Position = projectionMatrix * mvPosition;
  }
`;

const fragmentShader = `
  uniform sampler2D pointTexture;
  varying vec3 vColor;
  void main() {
    gl_FragColor = vec4(vColor, 1.0) * texture2D(pointTexture, gl_PointCoord);
    if (gl_FragColor.a < 0.1) discard; // 透明度剪裁
  }
`;

// 產生 size attribute(雨滴長度)
const sizes = new Float32Array(count);
for (let i = 0; i < count; i++) {
  sizes[i] = Math.random() * 10 + 5; // 5~15 像素
}
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));

// ShaderMaterial
const material = new THREE.ShaderMaterial({
  uniforms: {
    pointTexture: { value: tex },
  },
  vertexShader,
  fragmentShader,
  transparent: true,
  depthWrite: false,
  vertexColors: true,
});

// 建立 Points
const points = new THREE.Points(geometry, material);
scene.add(points);
  • 透過自訂 size attribute 與 Shader,可以讓每顆粒子擁有獨立的大小、顏色與動畫,適用於 雨滴、雪花、火焰 等需要細緻控制的特效。

常見陷阱與最佳實踐

陷阱 說明 解決方案
貼圖未設 transparent:true 透明 PNG 仍顯示黑色方塊 PointsMaterialShaderMaterial 中開啟 transparent
alphaTest 設太低 大量半透明像素導致 overdraw,FPS 下降 調整至 0.4~0.6,或改用 alphaTest + depthWrite:false
大量粒子使用 MeshBasicMaterial 無法正確渲染點,或出現 Z‑fight 必須使用 PointsMaterial 或自訂 ShaderMaterial
未使用 BufferGeometry Geometry 已被棄用,效能會大幅下降 永遠使用 THREE.BufferGeometry,並以 Float32Array 建立 attribute
粒子尺寸不隨距離衰減 當相機靠近時,粒子看起來像「方塊」 設定 sizeAttenuation:true,或自行在 shader 計算衰減
忘記回收資源 多次建立 TextureGeometry 會造成記憶體洩漏 在不需要時呼叫 .dispose(),並移除場景節點

最佳實踐

  1. 批次渲染:將相同貼圖、相同材質的粒子合併到同一個 Points,減少 draw call。
  2. 使用 InstancedBufferGeometry(進階)如果粒子需要不同的變形或動畫,可考慮 InstancedBufferGeometry 以提升效能。
  3. 限制粒子數量:在手機或低階裝置上,建議不超過 10,000 個粒子;若需要更多,請改用 GPU 粒子系統(如 GPUComputationRenderer)。
  4. 預先載入貼圖:使用 THREE.TextureLoaderonLoad 回呼,確保貼圖在渲染前已完成解碼。

實際應用場景

場景 使用技巧 成效
星空背景 PointsMaterial + alphaTest:0.5 + depthWrite:false + sizeAttenuation:true 無縫、深度感十足的星空,CPU 負載低
雨滴特效 自訂 ShaderMaterial,使用 size attribute 以及隨時間的 y 位移 逼真的雨幕,支援風向與重力變化
火焰/煙霧 PointsMaterial + map 為燈光貼圖,結合 Tween.js 變化 material.opacity 動態、漸變的火焰或煙霧,適合 UI 動畫
資料可視化(散點圖) vertexColors 依資料值上色,size 依權重調整 高效能的 3D 散點圖,支援即時互動
互動特效(點擊產生粒子) 於點擊事件中新建 Points,使用 setFromPoints 並加入場景,短暫顯示後 dispose 簡易的爆炸、星星閃爍等 UI 反饋

總結

  • THREE.Points 搭配 PointsMaterial自訂 ShaderMaterial,是實作高效能粒子系統的首選。
  • 透過 BufferGeometryattribute(position、color、size)與 貼圖,可以在瀏覽器中輕鬆呈現星空、雨滴、火焰等多樣特效。
  • 注意 透明度、深度寫入、alphaTest 等屬性設定,能避免常見的渲染問題並提升效能。
  • 為了在行動裝置上保持流暢,務必控制粒子數量、合併 draw call,並在不需要時正確釋放資源。

掌握上述概念與技巧後,你就能在 Three.js 專案中自由創作各式動態粒子特效,為使用者帶來更豐富、沉浸的 3D 體驗。祝開發順利!