
深入探讨如何使用 Three.js BufferGeometry 和自定义 ShaderMaterial 构建一个性能优秀的粒子系统
## 前言
粒子系统是 3D 图形中最迷人的效果之一。从星空到魔法特效,粒子无处不在。本文将带你用 Three.js 从零构建一个包含 10000+ 粒子的实时系统。
## 为什么用 BufferGeometry
传统的 `Geometry` 已经被废弃,`BufferGeometry` 直接操作 GPU 缓冲区,性能提升显著。
```typescript
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(COUNT * 3);
for (let i = 0; i < COUNT; i++) {
positions[i * 3] = (Math.random() - 0.5) * 10;
positions[i * 3 + 1] = (Math.random() - 0.5) * 10;
positions[i * 3 + 2] = (Math.random() - 0.5) * 10;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
```
## 自定义 Shader
标准材质无法满足我们的需求,我们需要自定义顶点着色器和片段着色器:
```glsl
// 顶点着色器
attribute float aSize;
uniform float uTime;
void main() {
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
modelPosition.y += sin(uTime + position.x * 100.0) * 0.1;
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
gl_PointSize = aSize * (1.0 / -viewPosition.z);
}
```
## 鼠标排斥力
通过 Raycaster 获取鼠标位置,在更新循环中计算排斥力:
```typescript
function updateParticles(mousePos: THREE.Vector3) {
for (let i = 0; i < COUNT; i++) {
const dx = positions[i * 3] - mousePos.x;
const dy = positions[i * 3 + 1] - mousePos.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < REPULSION_RADIUS) {
const force = (REPULSION_RADIUS - dist) / REPULSION_RADIUS;
velocities[i * 3] += dx * force * 0.01;
velocities[i * 3 + 1] += dy * force * 0.01;
}
}
}
```
## 性能优化
- 使用 `instancedMesh` 代替多个 Mesh
- 只更新可见范围内的��子
- 使用 Web Worker 计算物理
完整代码已开源在 GitHub,欢迎 Star!
粒子系统是 3D 图形中最迷人的效果之一。从星空到魔法特效,粒子无处不在。本文将带你用 Three.js 从零构建一个包含 10000+ 粒子的实时系统。
## 为什么用 BufferGeometry
传统的 `Geometry` 已经被废弃,`BufferGeometry` 直接操作 GPU 缓冲区,性能提升显著。
```typescript
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(COUNT * 3);
for (let i = 0; i < COUNT; i++) {
positions[i * 3] = (Math.random() - 0.5) * 10;
positions[i * 3 + 1] = (Math.random() - 0.5) * 10;
positions[i * 3 + 2] = (Math.random() - 0.5) * 10;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
```
## 自定义 Shader
标准材质无法满足我们的需求,我们需要自定义顶点着色器和片段着色器:
```glsl
// 顶点着色器
attribute float aSize;
uniform float uTime;
void main() {
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
modelPosition.y += sin(uTime + position.x * 100.0) * 0.1;
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
gl_PointSize = aSize * (1.0 / -viewPosition.z);
}
```
## 鼠标排斥力
通过 Raycaster 获取鼠标位置,在更新循环中计算排斥力:
```typescript
function updateParticles(mousePos: THREE.Vector3) {
for (let i = 0; i < COUNT; i++) {
const dx = positions[i * 3] - mousePos.x;
const dy = positions[i * 3 + 1] - mousePos.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < REPULSION_RADIUS) {
const force = (REPULSION_RADIUS - dist) / REPULSION_RADIUS;
velocities[i * 3] += dx * force * 0.01;
velocities[i * 3 + 1] += dy * force * 0.01;
}
}
}
```
## 性能优化
- 使用 `instancedMesh` 代替多个 Mesh
- 只更新可见范围内的��子
- 使用 Web Worker 计算物理
完整代码已开源在 GitHub,欢迎 Star!