术语“粒子已死”是一个术语,仅描述粒子的语义死。从 GPU 的角度来看,所有的粒子都是实时的,所有的粒子都会在每一帧中计算出来。 (或者至少,粒子 0 到 N 将被处理,但即使是死粒子)。
一旦 CPU“检测到”一个粒子已经死亡(例如,它的年龄是 5 秒左右),就需要记住该粒子的索引,以便新粒子可以重用该粒子索引。这可以通过许多不同的方式完成,两种明显的方式是堆栈或堆。
仅当粒子的最大年龄不同时,才需要存储这些死粒子索引的特殊数据结构。如果它们没有区别,您可以只实现一个环形缓冲区。但大多数情况下,您会将此粒子引擎用于各种粒子,而这些粒子可能具有可变的生存时间值。然后你需要那些数据结构。
该算法使用片段着色器进行速度计算。它从一个纹理(包含 x/y/z 坐标而不是 r/g/b 颜色信息)读取数据并写入不同的纹理(也包含 x/y/z 坐标而不是 r/g/b 颜色信息),在源纹理和目标纹理之间使用 1:1 映射,并将整个源纹理渲染到目标纹理。这与稍后将在步骤6. Render Particles 中渲染的实际粒子无关。
或者换句话说:“屏幕大小的四边形”在这里实际上是一个错误的术语,它应该读作“纹理大小的四边形”,因为此时屏幕上根本没有绘制任何内容。目标纹理(即保存新位置信息的纹理)是屏幕。
/再次编辑:
好的,也许改写文档:
你有一个struct:
struct color {
float r, g, b;
};
还有几个#defines:
#define vector color
#define x r
#define y g
#define z b
你的粒子有几个数组:
#define NP 1024 * 1024
struct vector particle_pos[2][NP];
struct vector particle_vel[2][NP];
uint32_t particle_birth_tick[NP];
// Double buffering - gonne have to remember, where
// we read from and where we write to:
struct vector * particle_pos_r = particle_pos[0];
struct vector * particle_pos_w = particle_pos[1];
struct vector * particle_vel_r = particle_vel[0];
struct vector * particle_vel_w = particle_vel[1];
现在:
- 处理生死
#define TTL 5 * 25 // 5 seconds * 25 simulation steps per second.
for (size_t i = 0; i < NP; ++i) {
if (particle_birth_tick[i] + TTL == current_tick) {
particle_pos_r[i].x = somewhere behind viewer;
particle_pos_r[i].y = somewhere behind viewer;
particle_pos_r[i].z = somewhere behind viewer;
particle_vel_r[i].x = 0;
particle_vel_r[i].y = 0;
particle_vel_r[i].z = 0;
free_list.add(i);
}
}
void add_particle(struct vector p, struct vector v) {
size_t i = free_list.pop_any();
particle_pos_r[i] = p;
particle_vel_r[i] = v;
}
- 更新速度
for (size_t i = 0; i < 1024 * 1024; ++i) {
particle_vel_w[i].x = do_calculations(particle_vel_r[i].x)
particle_vel_w[i].y = do_calculations(particle_vel_r[i].y)
particle_vel_w[i].z = do_calculations(particle_vel_r[i].z)
}
swap(particle_vel_r, particle_vel_w);
- 更新职位
for (size_t i = 0; i < 1024 * 1024; ++i) {
particle_pos_w[i].x = particle_pos_r[i].x + particle_vel_r[i].x;
particle_pos_w[i].y = particle_pos_r[i].y + particle_vel_r[i].y;
particle_pos_w[i].z = particle_pos_r[i].z + particle_vel_r[i].z;
}
swap(particle_pos_r, particle_pos_w);
- Alpha 混合排序
sort a bit...
- 将纹理数据传输到顶点数据
copy the pos texture into a vbo
- 渲染粒子
actually draw particles
这里有趣的一点是,步骤 2-5 都只发生在 GPU 上(步骤 1 发生在 GPU 和 CPU 上)。因此,术语“渲染”。因为在 2 和 3 中循环只是“渲染”“纹理”particle_vel_r 和/或particle_pos_r 到“帧缓冲区”particle_vel_w 或particle_pos_w 完全填充帧缓冲区“屏幕大小的四边形”源纹理。