粒子与点精灵
粒子是 环形缓冲里的实例,不是一万个场景节点。发射器、配额、种子留在 CPU;位置/速度/寿命是投影,可以进 GPU。火花、雪、击中特效走这条路;要可点选、可撤销的「物体」不要伪装成粒子。
1. 场景怎么切
CPU:发射器列表、maxCount、CPU 随机种子
↓ spawn / kill 只改存活区间
GPU:pos/vel/life buffer + 一次 instanced 四边形
生产默认 WebGL2 实例化四边形。gl.POINTS + gl_PointSize 只适合不旋转、尺寸小于 ALIASED_POINT_SIZE_RANGE 的点;移动端上限经常几十像素,旋转和 atlas UV 也对不齐。点精灵要转、要图集帧,用四边形。
export type Emitter = {
id: number;
x: number;
y: number;
rate: number;
maxCount: number;
};
export type ParticleSoA = {
x: Float32Array;
y: Float32Array;
vx: Float32Array;
vy: Float32Array;
life: Float32Array;
head: number;
alive: number;
};
export function createPool(maxCount: number): ParticleSoA {
return {
x: new Float32Array(maxCount),
y: new Float32Array(maxCount),
vx: new Float32Array(maxCount),
vy: new Float32Array(maxCount),
life: new Float32Array(maxCount),
head: 0,
alive: 0,
};
}
export function spawn(pool: ParticleSoA, x: number, y: number, vx: number, vy: number, life: number): void {
const i = pool.head;
pool.x[i] = x;
pool.y[i] = y;
pool.vx[i] = vx;
pool.vy[i] = vy;
pool.life[i] = life;
pool.head = (pool.head + 1) % pool.x.length;
pool.alive = Math.min(pool.alive + 1, pool.x.length);
}
世界单位仍是场景图的单位;相机矩阵只在 VS 里乘。积分用秒(rAF dt 夹紧),不要 life-- 每帧。删除发射器:CPU 列表去掉,对应粒子 life = 0,不要从 GPU buffer 反推「还有哪些炮口」。
适用:装饰性粒子、短寿命特效、数量上万且不需要逐个 hit-test。
不适用:可拾取的单位、要进撤销栈的实体、每粒子不同 mesh。
2. CPU 积分还是 GPU 积分
| 层 | 归属 |
|---|---|
| 发射器、上限、碰撞世界(墙 AABB) | CPU |
| < 2 万且公式简单 | CPU 积分,每帧打包 instance |
| 全体同一公式、结果留在 GPU 再画 | 探测 WebGPU 后 compute;WebGL2 用 transform feedback |
| 最终颜色 | 片元采样图集,不要每粒子换纹理 |
WebGPU compute 不是默认栈。requestAdapter() 为 null 时走 CPU 积分 + drawArraysInstanced,见 Compute。热路径不要 readPixels 把位置读回 JS 再决策。
export function packAlive(pool: ParticleSoA): Float32Array {
const out = new Float32Array(pool.alive * 2);
let w = 0;
for (let i = 0; i < pool.x.length && w < pool.alive; i += 1) {
if ((pool.life[i] ?? 0) <= 0) continue;
out[w * 2] = pool.x[i] ?? 0;
out[w * 2 + 1] = pool.y[i] ?? 0;
w += 1;
}
return out.subarray(0, w * 2);
}
压缩空洞贵时,用环形「只追加、到期覆盖」,instanceCount = alive,接受短暂幽灵直到覆写。半透明粒子按视距或固定桶排序,不要为每粒子一次 draw。
3. 失败形态
| 症状 | 原因 |
|---|---|
| 大粒子被切成方块 | POINTS 超过 ALIASED_POINT_SIZE_RANGE |
| 旋转精灵糊成圆 | 用了点精灵而不是四边形 |
| 一帧后全 NaN | 移动端 mediump 积分没夹速度 |
| 删发射器粒子还在 | GPU buffer 当了文档 |
| 积分比画还慢 | 每帧 readPixels 位置 |
权威资料
核对日期:2026-08-26