通过渲染四边形来更新粒子系统的速度和位置

时间:2016-11-27 20:47:53

标签: c opengl gpu particle-system

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

我无法理解两件事:

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

1 个答案:

答案 0 :(得分:2)

  1. 术语“粒子已死”是一个术语,仅描述粒子的语义死亡。从GPU的角度来看,所有粒子都是一直存在的,所有粒子都会在每帧中计算出来。 (或者至少,将处理粒子0到N,但即使是死亡的粒子也是如此)。

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

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

  2. 该算法使用片段着色器进行速度计算。它从一个纹理(包含x / y / z坐标而不是r / g / b颜色信息)读取数据并写入不同的纹理(也包含x / y / z坐标而不是r / g / b颜色信息) ),使用源和目标纹理之间的1:1映射,并将整个源纹理渲染到目标纹理。这与稍后将在步骤6. Render Particles中呈现的实际粒子无关。

    或换句话说:“屏幕大小的四边形”在这里实际上是一个错误的术语,它应该是“纹理大小的四边形”,因为在这一点上,根本没有任何东西被绘制到屏幕上。目标纹理(即将保存新位置信息的纹理)是屏幕。

  3. /再次编辑:

    好的,也许改写文件:

    您有struct

    struct color {
        float r, g, b;
    };
    

    和一些#define s:

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

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