Three.js 粒子效果入门:用 Points 画一片星空
粒子效果是 Three.js 里最容易出彩的一类效果。核心只有三样东西:存坐标的几何体、决定怎么画点的材质、把两者组合起来的 Points 对象。这篇从零画一片会缓慢旋转的星空。
准备场景
import * as THREE from 'three';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
setPixelRatio 限制在 2 以内:手机的像素比经常是 3,全分辨率渲染会让 GPU 压力翻倍,肉眼却几乎看不出区别。
生成粒子坐标
const COUNT = 10_000;
const positions = new Float32Array(COUNT * 3);
for (let i = 0; i < COUNT; i++) {
// 在半径 10 的球体内随机分布
const r = 10 * Math.cbrt(Math.random());
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
positions[i * 3 + 2] = r * Math.cos(phi);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
两个细节:
- 用
Float32Array一次性存所有坐标,每 3 个数代表一个点的 x、y、z。比创建一万个对象省内存得多,也能直接上传到 GPU。 - 半径用
Math.cbrt(Math.random())(立方根)而不是Math.random()。球体的体积和半径的立方成正比,不开立方的话粒子会挤在球心附近。
材质和 Points
const material = new THREE.PointsMaterial({
size: 0.04,
color: 0xaad8ff,
transparent: true,
opacity: 0.85,
depthWrite: false,
blending: THREE.AdditiveBlending,
});
const stars = new THREE.Points(geometry, material);
scene.add(stars);
size默认会随距离衰减(sizeAttenuation: true),离相机越远的点越小,自然就有了纵深感;AdditiveBlending让重叠的点颜色叠加变亮,星星密集的地方会更亮;depthWrite: false可以避免半透明粒子之间互相遮挡、出现黑边。
动起来,并适配窗口
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
const t = clock.getElapsedTime();
stars.rotation.y = t * 0.03;
stars.rotation.x = Math.sin(t * 0.1) * 0.1;
renderer.render(scene, camera);
});
addEventListener('resize', () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
旋转角度用经过的时间来算,而不是每帧加一个固定值。这样在 60Hz 和 120Hz 的屏幕上转速一致。
小结
| 角色 | 类 | 作用 |
|---|---|---|
| 几何体 | BufferGeometry | 用 Float32Array 存所有点的坐标 |
| 材质 | PointsMaterial | 点的大小、颜色、透明度和混合方式 |
| 物体 | Points | 把几何体和材质组合起来放进场景 |
在这个基础上,给每个点加一个 color 属性(并打开 vertexColors: true)就能做出彩色星空;再进一步,可以换成自定义着色器,让粒子按规律流动。