本文 AI 產出,尚未審核
Three.js 粒子系統與特效 – Points 與粒子材質
簡介
在 3D 網頁互動中,粒子系統是營造星塵、火焰、雨滴等特效的關鍵工具。Three.js 以 THREE.Points 以及多樣的粒子材質(PointsMaterial、ShaderMaterial)提供了既簡潔又高效的實作方式。即使是剛接觸 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();
重點:只要
sizeAttenuation為true,粒子會隨相機遠近自動縮放,營造自然的深度感。
範例 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,
});
技巧:若同時使用
vertexColors與color參數,color會作為全域基礎色,與每個頂點的顏色相乘。
範例 4:動態改變粒子大小(使用 sizeAttenuation 與 onBeforeRender)
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);
- 透過自訂
sizeattribute 與 Shader,可以讓每顆粒子擁有獨立的大小、顏色與動畫,適用於 雨滴、雪花、火焰 等需要細緻控制的特效。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
貼圖未設 transparent:true |
透明 PNG 仍顯示黑色方塊 | 在 PointsMaterial 或 ShaderMaterial 中開啟 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 計算衰減 |
| 忘記回收資源 | 多次建立 Texture、Geometry 會造成記憶體洩漏 |
在不需要時呼叫 .dispose(),並移除場景節點 |
最佳實踐
- 批次渲染:將相同貼圖、相同材質的粒子合併到同一個
Points,減少 draw call。 - 使用
InstancedBufferGeometry(進階)如果粒子需要不同的變形或動畫,可考慮InstancedBufferGeometry以提升效能。 - 限制粒子數量:在手機或低階裝置上,建議不超過 10,000 個粒子;若需要更多,請改用 GPU 粒子系統(如
GPUComputationRenderer)。 - 預先載入貼圖:使用
THREE.TextureLoader的onLoad回呼,確保貼圖在渲染前已完成解碼。
實際應用場景
| 場景 | 使用技巧 | 成效 |
|---|---|---|
| 星空背景 | 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,是實作高效能粒子系統的首選。- 透過
BufferGeometry、attribute(position、color、size)與 貼圖,可以在瀏覽器中輕鬆呈現星空、雨滴、火焰等多樣特效。 - 注意 透明度、深度寫入、alphaTest 等屬性設定,能避免常見的渲染問題並提升效能。
- 為了在行動裝置上保持流暢,務必控制粒子數量、合併 draw call,並在不需要時正確釋放資源。
掌握上述概念與技巧後,你就能在 Three.js 專案中自由創作各式動態粒子特效,為使用者帶來更豐富、沉浸的 3D 體驗。祝開發順利!