粒子系统应预分配vector空间并采用连续内存布局的结构体存储粒子数据,用float型生命周期实现平滑衰减,通过初始扰动增强真实感,避免逐粒子draw call而改用实例化渲染,且生命周期更新必须乘以delta_time确保帧率无关。

粒子系统核心结构怎么设计才不卡
直接用 std::vector 存粒子没问题,但每帧遍历+更新+绘制时,如果粒子数超 2000,push_back 频繁触发内存重分配会明显掉帧。必须预分配空间:particles.reserve(5000);更关键的是——所有粒子数据(位置、速度、生命周期)必须保持结构体连续布局,别拆成多个 std::vector 分别存 x、y、life,否则 CPU 缓存不友好,SIMD 也加不了。
推荐一个最小可行结构:
struct Particle {
glm::vec2 pos;
glm::vec2 vel;
float life;
float max_life;
};
注意:life 和 max_life 用 float 而非 int,方便做平滑衰减(比如 alpha = life / max_life),整数除法容易崩。
如何让粒子自然消散而不是突兀消失
常见错误是写 if (p.life —— 这在遍历时 erase 会失效迭代器,且频繁删除导致内存碎片。正确做法是“标记+批量回收”:
- 每帧更新时,把
life减去delta_time,并设个标志位(比如alive = (life > 0)) - 用
std::remove_if把死亡粒子挪到末尾,再调particles.erase()一次性裁掉 - 消散动画别只靠 alpha:加点缩放(
scale = life / max_life)和轻微旋转(rot += 0.1f * delta_time),视觉上更柔和
发射逻辑怎么避免堆叠成一团
新手常写 pos = emitter_pos,结果所有粒子从同一个点炸开,像颗钉子。真实感来自初始扰动:
- 速度方向:用
glm::vec2{cos(a), sin(a)}+ 小范围随机角偏移(±0.3 弧度足够) - 初速度大小:用
min_speed + (max_speed - min_speed) * rand_float(),别固定值 - 位置偏移:发射器是点?那加个
glm::diskRand(0.5f)(GLM 自带);是矩形?用glm::vec2{rand(-w/2, w/2), rand(-h/2, h/2)}
如果用 OpenGL 渲染,千万别在循环里对每个粒子调 glDrawArrays(GL_POINTS, ...) —— 5000 粒子就是 5000 次 draw call,GPU 直接窒息。改用 instanced rendering 或把所有粒子顶点塞进一个 VBO 一次绘制。
为什么用 GLM 而不是自己写向量类
不是为了炫技。glm::vec2 的内存布局是标准的两个 float 连续排列,和 OpenGL 的 glVertexAttribPointer 要求完全对齐;你自己写的 struct {float x,y;} 如果中间有 padding(比如加了 int id),VBO 读取就会错位。另外 glm::diskRand()、glm::ballRand() 这种函数省去你查数学公式推均匀采样,而且它们返回的就是 vec2/vec3,无缝接入运算流。
顺手提醒:粒子系统最易被忽略的其实是时间步长处理。别用 life -= 1.0f 这种帧率绑定写法,必须乘以 delta_time(单位秒)。否则 30fps 时粒子活 1 秒,60fps 时只活 0.5 秒——这问题在笔记本切电源模式后立刻暴露。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











