人气 275

[游戏程序] [转帖]基于GPU的粒子系统实现概要 [复制链接]

九艺网 2017-3-10 17:01:38

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

x
         在学习粒子系统后,继续学习到shader编程时,我忽然想到:能不能把很多粒子运算从CPU上转移到GPU上去? 一般情况下计算粒子位置好解决,可是生命周期处理不好办,因为shader不能写顶点buffer,这个一时把我难住了,后来在网上看到篇GPU处理粒子系统的文章,受到点启发,再经过一翻思考,自己的实现方案产生了,遂整理出文章一篇,以抛砖引玉。
          经常在网上看到一些翻译别人国外作者的文章,排版又乱,自己又不给点见解和创新,我强烈建议大家发挥自己的想象力,先想,再做,再学,不要盲目崇洋名外。我写这篇文章不主要为写知识技巧,而是想唤醒你那无限的想象力,然后大家一起探讨创新!
      
         目标及好处:减少粒子系统更新中GPU和CPU之间的频繁通信,系统内存和显存间的频繁数据传送。充分利用显卡的图形处理能力,降低CPU的负担。
      
        首先描叙下一般情况下粒子系统运行的步骤,为了使读者更清晰的看到主题我略了颜色纹理等的处理(颜色衰减处理类似与位置处理):
        初始化粒子
        循环
-----------------------------一般是在CPU中进行----------------------------------------------------
                如果粒子没有死亡
                         更新离子的位置
                         修改粒子的生命值
                处理死亡的粒子
                         如果粒子生命小于某个阀值(比如0.0)
                         设置粒子状态为死亡,或者干脆从粒子队列里删除
                增加新的粒子
                          增加新的粒子到粒子队列中,或者查找生命为死亡的粒子,用计算出的
                          新粒子属性覆盖之(这里有新的生命值)
-------------------------------下面在显卡中进行--------------------------------------------------------------------
                渲染
                         把没有死亡的粒子的位置信息送入显卡中,进行渲染 (采用AGP内存的话,最终还是要把一个个的粒子信息送入显卡处理)
               
   
        这样每个粒子的具体信息都要CPU处理然后送入显卡中渲染,不言而喻效率极底,我们要想办法把在CPU里处理的动作转移到GPU中去。下面逐一列出解决方法:        

         1。生命周期模拟:
         在初始化粒子系统时,计算出粒子生命值life(如果生命值都一样的话,就不用了),记下粒子的开始时间startTime。比如有100个粒子,一个在时间0开始发射,生命周期为10秒,下一个在时间0.1秒开始发射,生命周期为9秒,。。。一直到100个,计算好后把它们送入显卡。
         现在到了一个难点的问题了,就是粒子生命结束了改怎么处理?那就是让新的粒子信息覆盖这个死亡粒子的存储空间。可以通过CPU实现也可以通过GPU模拟,CPU实现下次讨论。这里说说GPU中模拟,就是用粒子系统全局时间 — 粒子初始时间然后再模除以生命周期:float aliveTime = fmod(GlobeTime, lifevalue);
这样在粒子到达生命终点时它又从新复活,开始自己下一个生命周期的旅程(也许你注意到了,它的生命周期没有改变:)  
         2。每个粒子的位置更新:
        positionNew =EmitteredPistion + Velocity*aliveTime + 1/2*acceleration*aliveTime*aliveTime
        //新位置 = 初始位置+ 速度*已存活时间+ 1/2*加速度*已存活时间*已存活时间
        速度在初始化粒子系统时,计算粒子位置然后把它们送入显卡顶点缓冲区(vertex buffer),初始位置就是发射点的位置它和时间、加速度以全局变量的方式,在运行期送传给shader全局变量。
        
---------------------------------------实现片段------------------------------------------------------------
          粒子的生命值,开始发射时间,速度可以存放在没有使用到的纹理坐标和颜色VertexBuffer上:
------------------------------------------在C++程序中--------------------------------------------------------
//顶点格式声明
const static D3DVERTEXELEMENT9 g_VertexElements[] =
{
       { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_POSITION, 0},             //这里存放生命值lifevalue,开始发射时间startTime
       { 1, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT,  D3DDECLUSAGE_TEXCOORD, 0},            //这里存放速度
//如还有信息继续添加.... ....
       D3DDECL_END()
};      
//设置effect全局变量
pEffect->SetVector("GlobeTime", &GlobeTime );                //整个粒子系统的生存时间
pEffect->SetVector("acceleration", &acceleration );            //加速度
pEffect->SetVector("EmitteredPistion", &EmitteredPistion );//发射器的位置
---------------------------------D3DFX代码片(效果文件,.fx后缀的)--------------------------------
float4 GlobeTime;                                                         // 自创建粒子系统后逝去的时间
float4 EmitteredPistion;                                                 //粒子被发射的位置
float4 acceleration;                                                       //加速度
struct VS_INPUT
{
    float3 Position;
    POSITION;            //lifevalue,startTime;   
    float3 Tex0                                 : TEXCOORD0; // Velocity
    ... ....   //其它信息      
};
struct VS_OUTPUT
{
    float4 Position                      : POSITION;
    ... ...  //其它信息
};
VS_OUTPUT VS(const VS_INPUT Input)
{
    VS_OUTPUT    Out = (VS_OUTPUT) 0;
    //float aliveTime = fmod(GlobeTime — startTime, lifevalue);
    float aliveTime = fmod(GlobeTime.x — Position.y, Position.x);
    //positionNew = positionInit + Velocity*aliveTime + 1/2*acceleration*aliveTime*aliveTime,计算位移
    //Velocity*aliveTime ,初速度*时间增加的位移部分
    float4 positionNew = EmitteredPistion + mul( float4(Input.Tex0,0), aliveTime);  
    //+ 1/2*acceleration*aliveTime*aliveTime部分,+加速度增加的位移部分
    positionNew =  positionNew +mul( acceleration ,(0.5*aliveTime*aliveTime) );  
    positionNew.w = 0;
    Out.Position = positionNew;
   //其他处理信息... ...
    return Out;
}
technique tec0
{
    pass p0
    {
        VertexShader = compile vs_1_1 VS();
        PixelShader = NULL;
    }
}
       这个技术可以在vs1.1及以上版本,VS3.0及以上版本会有更好的解决方案,但基本原理类似,因为这里实现的是无状态粒子系统,无状态粒子系统有它的使用范围。在这个方法的基础上可以实现 GPU、CPU联合更新粒子系统。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

QQ|手机版|小黑屋|九艺游戏动画论坛 ( 津ICP备2022000452号-1 )

GMT+8, 2024-4-25 07:21 , Processed in 0.066367 second(s), 23 queries .

Powered by Discuz! X3.4  © 2001-2017 Discuz Team.