2、第一个粒子系统:创建Points对象、使用PointsMaterial、设置粒子大小和颜色
好,咱们直接动手。这一章的目标很简单——在屏幕上撒一把粒子,让它们亮起来。
你可能会想,粒子系统是不是很复杂?其实不然。Three.js 把最核心的部分封装成了两个东西:Points 和 PointsMaterial。说白了,Points 就是一堆点的集合,PointsMaterial 就是控制这些点长什么样的材质。
2.1 创建 Points 对象
先看代码,再解释。
// 1. 创建几何体,存放粒子位置
const geometry = new THREE.BufferGeometry();
const count = 1000; // 粒子数量
// 2. 生成随机位置
const positions = new Float32Array(count * 3);
for (let i = 0; i < count * 3; i += 3) {
positions[i] = (Math.random() - 0.5) * 10; // x
positions[i + 1] = (Math.random() - 0.5) * 10; // y
positions[i + 2] = (Math.random() - 0.5) * 10; // z
}
// 3. 把位置数据塞进几何体
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
// 4. 创建材质
const material = new THREE.PointsMaterial({
color: 0x88ccff,
size: 0.1
});
// 5. 合成 Points 对象
const points = new THREE.Points(geometry, material);
scene.add(points);
嗯,这里有个关键点——BufferGeometry。我刚开始学的时候,总觉得这东西很绕。其实你就把它想象成一个「数据容器」,专门用来存顶点信息的。
为什么用 Float32Array?因为 GPU 只认这种紧凑的二进制数据。你用普通数组传进去,Three.js 还得帮你转一遍,多此一举。
2.2 使用 PointsMaterial
PointsMaterial 和普通的 MeshMaterial 不一样。它没有光照计算,没有阴影,说白了就是「纯色点」。但正因为它简单,所以性能极好。
常用的属性就这几个:
| 属性 | 类型 | 说明 |
|---|---|---|
| color | Color | 粒子颜色,支持十六进制或 Color 对象 |
| size | Number | 粒子大小,单位是像素(默认) |
| sizeAttenuation | Boolean | 是否根据距离衰减大小,默认 true |
| opacity | Number | 透明度,0~1 |
| transparent | Boolean | 启用透明度,必须设为 true 才能用 opacity |
你想想看,如果 sizeAttenuation 设为 false,远处的粒子和近处的一样大,看起来就像贴在屏幕上一样,很假。所以我一般保持默认 true。
2.3 设置粒子大小和颜色
大小和颜色是最直观的两个参数。但这里有个坑——size 的单位是什么?
默认情况下,size 的单位是「像素」。也就是说 size: 0.1 并不是 0.1 个世界单位,而是 0.1 个像素。这会导致一个问题:在不同分辨率的屏幕上,粒子看起来大小不一样。
我曾经在 4K 屏上调试好的粒子大小,换到 1080p 的笔记本上,粒子变得巨大无比……嗯,后来我学乖了,要么用 sizeAttenuation 配合相机距离来调,要么干脆用纹理贴图来控制大小。
颜色方面,你可以给所有粒子统一颜色:
const material = new THREE.PointsMaterial({
color: 0xff6600, // 橙色
size: 0.2
});
也可以给每个粒子单独的颜色。这需要额外传一个 color 属性:
const colors = new Float32Array(count * 3);
for (let i = 0; i < count * 3; i += 3) {
colors[i] = Math.random(); // r
colors[i + 1] = Math.random(); // g
colors[i + 2] = Math.random(); // b
}
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({
size: 0.15,
vertexColors: true // 启用顶点颜色
});
2.4 避坑指南
- 粒子不显示? 检查一下相机位置。粒子默认在原点附近,如果相机离得太远或者看向别处,当然看不到。
- 粒子太大或太小? 调 size 的同时,别忘了 sizeAttenuation。如果关闭了衰减,远处的粒子会显得特别大。
- 颜色不对? 确认 color 用的是十六进制格式(0xrrggbb),而不是 CSS 字符串('#ff6600')。
我记得有一次,一个学员跑来问我:「老师,我的粒子全是黑色的!」我一看代码,他把 color 写成了 '#ff0000'。Three.js 的 Color 构造函数虽然能解析字符串,但 PointsMaterial 的 color 属性期望的是数值类型。这种小细节,不注意就会卡半天。
2.5 完整示例
最后,给你一个可以直接跑起来的完整代码。复制到你的项目里,看看效果:
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 粒子系统
const geometry = new THREE.BufferGeometry();
const count = 2000;
const positions = new Float32Array(count * 3);
for (let i = 0; i < count * 3; i += 3) {
positions[i] = (Math.random() - 0.5) * 20;
positions[i + 1] = (Math.random() - 0.5) * 20;
positions[i + 2] = (Math.random() - 0.5) * 20;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const material = new THREE.PointsMaterial({
color: 0x44aaff,
size: 0.15,
sizeAttenuation: true
});
const points = new THREE.Points(geometry, material);
scene.add(points);
// 控制器
new OrbitControls(camera, renderer.domElement);
function animate() {
requestAnimationFrame(animate);
points.rotation.y += 0.001;
renderer.render(scene, camera);
}
animate();
运行起来,你会看到一片蓝色的星云在缓缓旋转。这就是你的第一个粒子系统。
下一章,我们会给粒子加上纹理,让它们不再是单调的圆点,而是星星、火花、或者任何你想要的形状。