【问题标题】:update velocities and position of particle system by rendering quad通过渲染四边形更新粒子系统的速度和位置
【发布时间】:2017-04-11 13:12:39
【问题描述】:

使用本文算法实现基于GPU的粒子系统: http://www.gamasutra.com/view/feature/130535/building_a_millionparticle_system.php?print=1

有两件事我无法理解:

  1. 为什么要使用堆栈或堆来存储可用的粒子索引? 如果一个粒子在时间 t 死亡,那么它将在时间 t+1 从零开始它的生命。有一个参数N来控制屏幕上的粒子数。如果我可以重用所有粒子,我为什么要关心所有可用粒子的索引,甚至使用堆来存储它?
  2. 要更新速度和位置,它说,“实际模拟是在片段着色器中实现的。着色器是通过渲染屏幕大小的四边形来为渲染目标的每个像素执行的......”如果我想要要绘制为点的粒子,我需要将四边形更改为点吗? 为什么是四边形?它如何绘制点数?

【问题讨论】:

  • 顺便说一句:刚读完:»粒子可以渲染为点精灵、三角形或四边形 [...]。如果要为每个粒子生成多个顶点,则 [...] 概念 [...] 需要在渲染之前手动复制粒子位置。在进一步的渲染步骤中,需要将位置渲染三到四次到纹理中彼此相邻的像素。 [...]« 和 »由于上一节中提到的原因以及为了减少顶点单元的工作量,粒子目前被渲染为点精灵。«

标签: c opengl gpu particle-system


【解决方案1】:
  1. 术语“粒子已死”是一个术语,仅描述粒子的语义死。从 GPU 的角度来看,所有的粒子都是实时的,所有的粒子都会在每一帧中计算出来。 (或者至少,粒子 0 到 N 将被处理,但即使是死粒子)。

    一旦 CPU“检测到”一个粒子已经死亡(例如,它的年龄是 5 秒左右),就需要记住该粒子的索引,以便新粒子可以重用该粒子索引。这可以通过许多不同的方式完成,两种明显的方式是堆栈或堆。

    仅当粒子的最大年龄不同时,才需要存储这些死粒子索引的特殊数据结构。如果它们没有区别,您可以只实现一个环形缓冲区。但大多数情况下,您会将此粒子引擎用于各种粒子,而这些粒子可能具有可变的生存时间值。然后你需要那些数据结构。

  2. 该算法使用片段着色器进行速度计算。它从一个纹理(包含 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];

现在:

  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;
}
  1. 更新速度
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);
  1. 更新职位
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);
  1. Alpha 混合排序
sort a bit...
  1. 将纹理数据传输到顶点数据
copy the pos texture into a vbo
  1. 渲染粒子
actually draw particles

这里有趣的一点是,步骤 2-5 都只发生在 GPU 上(步骤 1 发生在 GPU 和 CPU 上)。因此,术语“渲染”。因为在 2 和 3 中循环只是“渲染”“纹理”particle_vel_r 和/或particle_pos_r 到“帧缓冲区”particle_vel_wparticle_pos_w 完全填充帧缓冲区“屏幕大小的四边形”源纹理。

【讨论】:

  • 对于问题1,是否意味着我们首先创建最大的纹理(例如1024*1024),如果我们只想在一帧的屏幕上出现100个粒子,我们只需操作一个100大小的堆,弹出它们的索引,只更新 1024*1024 纹理中这 100 个粒子的属性。
  • 对于问题2,我可以理解位置纹理用作片段着色器的输入,因为位置纹理包含所有粒子位置信息。我无法理解的是为什么渲染屏幕大小的四边形可以进行计算!渲染四边形后,所有粒子都出现在屏幕上吗?片段着色器如何知道我想将粒子绘制为点还是广告牌?
  • Q2:查看编辑后的答案。 Q1:我不确定你现在的意思。但我想,你现在是。或者,您可以让 GPU 从一开始就处理整个 1k*1k 纹理,或者您可以只处理其中的一部分——比如“前 5 行”,因为所有“活”粒子都适合其中。
  • Q2:渲染的意思是在屏幕上绘制,对吧?片段缓冲区将粒子的位置 (x,y,z) 作为输入,然后绘制一个四边形。为什么要在屏幕上绘制位置?我的意思是,通常我们使用 glBegin(GL_POINTS) 绘制点,然后指定点的位置。在渲染四边形时,GPU 如何知道您正在渲染点?如果我想将每个粒子渲染为广告牌怎么办?
  • 再次:他们滥用 GPU 进行数学计算。 GPU 可以从纹理读取数据(通常是颜色数据)并将数据写入纹理(同样,通常是颜色数据)。但是在这里,正如我所说,他们滥用 GPU 进行计算。因此,它们不是读取颜色数据,而是读取 xyz 坐标,对它们进行数学运算并将它们写入新纹理。此过程也称为渲染,因为它与移动颜色数据的操作相同。请记住:在该文档的第 6 步之前,实际包含用户可见图片的帧缓冲区不会发生任何事情。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-16
  • 1970-01-01
  • 1970-01-01
  • 2012-09-04
  • 1970-01-01
  • 2020-11-11
  • 1970-01-01
相关资源
最近更新 更多