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)就能做出彩色星空;再进一步,可以换成自定义着色器,让粒子按规律流动。

← 返回首页