1. Points + PointsMaterial 基础粒子
Three.js 中的粒子系统通过 Points 对象实现——它把 BufferGeometry 的每个顶点渲染为一个面向屏幕的方形精灵(billboard):
// 基础粒子系统
const count = 5000;
const positions = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const i3 = i * 3;
positions[i3] = (Math.random() - 0.5) * 20; // x
positions[i3 + 1] = (Math.random() - 0.5) * 20; // y
positions[i3 + 2] = (Math.random() - 0.5) * 20; // z
}
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const mat = new THREE.PointsMaterial({
size: 0.05, // 粒子大小(世界单位)
color: 0xffffff,
transparent: true,
opacity: 0.8,
sizeAttenuation: true, // 远处粒子更小(透视效果)
depthWrite: false // 防止粒子遮挡后面粒子
});
const particles = new THREE.Points(geo, mat);
scene.add(particles);
2. 自定义粒子纹理
// 用圆形纹理替换方形粒子(更自然的外观)
const loader = new THREE.TextureLoader();
const particleTex = loader.load('/textures/particle.png');
const mat = new THREE.PointsMaterial({
size: 0.1,
map: particleTex,
transparent: true,
alphaMap: particleTex, // 或用 alphaMap 单独控制透明
alphaTest: 0.001, // 透明度低于此值的像素不渲染(代替 depthWrite)
vertexColors: true // 启用顶点颜色(每个粒子不同颜色)
});
// 为每个粒子指定颜色
const colors = new Float32Array(count * 3);
const color = new THREE.Color();
for (let i = 0; i < count; i++) {
color.setHSL(Math.random(), 1, 0.7);
colors[i * 3] = color.r;
colors[i * 3 + 1] = color.g;
colors[i * 3 + 2] = color.b;
}
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
3. 实战:十万粒子星空
// 高性能星空:10 万颗星
const STAR_COUNT = 100000;
const positions = new Float32Array(STAR_COUNT * 3);
const colors = new Float32Array(STAR_COUNT * 3);
const sizes = new Float32Array(STAR_COUNT);
const color = new THREE.Color();
for (let i = 0; i < STAR_COUNT; i++) {
const i3 = i * 3;
// 球形分布(半径 50~500 范围内)
const r = 50 + Math.random() * 450;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
positions[i3] = r * Math.sin(phi) * Math.cos(theta);
positions[i3 + 1] = r * Math.sin(phi) * Math.sin(theta);
positions[i3 + 2] = r * Math.cos(phi);
// 随机星色(白/蓝白/黄白)
const t = Math.random();
if (t < 0.7) {
color.setRGB(1, 1, 1); // 纯白
} else if (t < 0.9) {
color.setRGB(0.8, 0.9, 1); // 蓝白
} else {
color.setRGB(1, 0.95, 0.8); // 黄白
}
colors[i3] = color.r; colors[i3+1] = color.g; colors[i3+2] = color.b;
sizes[i] = Math.random() * 1.5 + 0.5; // 随机大小
}
const starGeo = new THREE.BufferGeometry();
starGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
starGeo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const starMat = new THREE.PointsMaterial({
size: 0.15,
vertexColors: true,
transparent: true,
opacity: 0.9,
depthWrite: false,
blending: THREE.AdditiveBlending // 加法混合:星光叠加更亮
});
const stars = new THREE.Points(starGeo, starMat);
scene.add(stars);
// 星空缓慢旋转
function animate() {
requestAnimationFrame(animate);
stars.rotation.y += 0.0001;
renderer.render(scene, camera);
}
4. PostProcessing 后处理
后处理(Post Processing)是在 Three.js 渲染完一帧后,对画面进行额外的图像处理效果,如辉光、景深、色调等:
// 安装后处理库(three/addons 中已内置基础效果)
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
import { FilmPass } from 'three/addons/postprocessing/FilmPass.js';
import { BokehPass } from 'three/addons/postprocessing/BokehPass.js';
// ── 创建 EffectComposer(后处理管线)──
const composer = new EffectComposer(renderer);
// 第一个 Pass 必须是 RenderPass(正常渲染场景)
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
// Bloom 辉光 — 亮部泛光效果
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.2, // strength 强度
0.4, // radius 扩散半径
0.85 // threshold 亮度阈值(超过此值才发光)
);
composer.addPass(bloomPass);
// Film 胶片颗粒 — 模拟老电影感
const filmPass = new FilmPass(
0.35, // noise intensity
0.025, // scanline intensity
648, // scanline count
false // grayscale
);
composer.addPass(filmPass);
// 景深 — BokehPass
const bokehPass = new BokehPass(scene, camera, {
focus: 5.0, // 焦距(世界单位)
aperture: 0.025, // 光圈(越大景深越浅)
maxblur: 0.01
});
composer.addPass(bokehPass);
// ⚠️ 使用 composer.render() 替代 renderer.render()
function animate() {
requestAnimationFrame(animate);
composer.render(); // 代替 renderer.render(scene, camera)
}
Bloom + 透明物体问题:UnrealBloomPass 会让所有物体都泛光。通常只希望特定物体发光时,可以用"选择性 Bloom"技术:将场景渲染两次,第二次只渲染发光物体,然后叠加。或使用 postprocessing npm 包(比内置 addons 更强大)。
5. 实战:魔法粒子效果
// 漂浮魔法粒子——每帧更新粒子位置
const MAGIC_COUNT = 2000;
const magicPos = new Float32Array(MAGIC_COUNT * 3);
const velocities = [];
for (let i = 0; i < MAGIC_COUNT; i++) {
const i3 = i * 3;
magicPos[i3] = (Math.random() - 0.5) * 4;
magicPos[i3 + 1] = Math.random() * 4;
magicPos[i3 + 2] = (Math.random() - 0.5) * 4;
velocities.push({
x: (Math.random() - 0.5) * 0.01,
y: Math.random() * 0.02,
z: (Math.random() - 0.5) * 0.01
});
}
const magicGeo = new THREE.BufferGeometry();
magicGeo.setAttribute('position', new THREE.BufferAttribute(magicPos, 3));
const magicMat = new THREE.PointsMaterial({
size: 0.08, color: 0x88aaff,
transparent: true, opacity: 0.9,
depthWrite: false,
blending: THREE.AdditiveBlending
});
const magic = new THREE.Points(magicGeo, magicMat);
scene.add(magic);
function animate() {
requestAnimationFrame(animate);
const pos = magicGeo.attributes.position;
for (let i = 0; i < MAGIC_COUNT; i++) {
pos.setX(i, pos.getX(i) + velocities[i].x);
pos.setY(i, pos.getY(i) + velocities[i].y);
pos.setZ(i, pos.getZ(i) + velocities[i].z);
// 超出范围重置
if (pos.getY(i) > 4) pos.setY(i, 0);
}
pos.needsUpdate = true; // 通知 GPU 数据已更改
renderer.render(scene, camera);
}
本章小结:粒子系统 = BufferGeometry(位置数组)+ PointsMaterial + Points。性能关键:depthWrite: false 避免排序问题;AdditiveBlending 让光粒叠加更亮。后处理一定要用 composer.render() 替代直接渲染,Bloom 能让粒子效果瞬间升华。