游戏开发论坛

 找回密码
 立即注册
搜索
查看: 25992|回复: 20

基于GPU的粒子系统实现概要

[复制链接]

35

主题

370

帖子

376

积分

中级会员

Rank: 3Rank: 3

积分
376
发表于 2006-12-12 00:35:00 | 显示全部楼层 |阅读模式
         在学习粒子系统后,继续学习到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联合更新粒子系统。
       好了我去睡觉了~大家有什么评论尽管发,不要给我面子~

106

主题

743

帖子

745

积分

高级会员

Rank: 4

积分
745
QQ
发表于 2006-12-12 09:39:00 | 显示全部楼层

Re:基于GPU的粒子系统实现概要

         现在到了一个难点的问题了,就是粒子生命结束了改怎么处理?那就是让新的粒子信息覆盖这个死亡粒子的存储空间。可以通过CPU实现也可以通过GPU模拟,CPU实现下次讨论。这里说说GPU中模拟,就是用粒子系统全局时间 — 粒子初始时间然后再模除以生命周期:float aliveTime = fmod(GlobeTime, lifevalue);
这样在粒子到达生命终点时它又从新复活,开始自己下一个生命周期的旅程(也许你注意到了,它的生命周期没有改变  


请问:粒子的系统全局时间是GPU实现,还是CPU实现?

0

主题

228

帖子

285

积分

中级会员

Rank: 3Rank: 3

积分
285
发表于 2006-12-12 10:03:00 | 显示全部楼层

Re:基于GPU的粒子系统实现概要

估计全局时间应该是CPU实现,然后传入GPU的

8

主题

716

帖子

716

积分

高级会员

Rank: 4

积分
716
发表于 2006-12-12 11:18:00 | 显示全部楼层

Re:基于GPU的粒子系统实现概要

前提是:每个particle都会产生很多变化:diffuse, life, velocity, acceleration ...
当使用gpu加速需要注意:
1. 使用static的vertex buffer,因为可以放到local video memory,所以访问速度快,但是问题在于无法修改
2. 所以“变化”的部分必须通过programable的部分,也就是vertex shader来解决
3. 那么就必须通过uniform变量来从CPU传入尽可能少的数据来表现上述的变化,而uniform是依赖于hardware的register数目

我觉得基本方向可以参考使用const uniform register实现的instancing来做

35

主题

370

帖子

376

积分

中级会员

Rank: 3Rank: 3

积分
376
 楼主| 发表于 2006-12-12 12:23:00 | 显示全部楼层

Re:基于GPU的粒子系统实现概要

       Re:基于GPU的粒子系统实现概要
       估计全局时间应该是CPU实现,然后传入GPU的
       答:对,我修改了下,在文中增加了传入时间的代码。
-------------------------------------------------------------------------------------
       const uniform register(常量寄存器)数量相对粒子系统来说不够,而且其他绘制体也需要使用。
       在shader3.0中,shader可以写纹理存储单元,故可以把位置,速度,颜色,纹理坐标信息存放在纹理存储单元中,因为位置和速度已经是随即的了,生命值可以在初始时刻按照整个粒子系统生命值概率分布预先写入,以后不改变。整个粒子系统的随即状态变换在概率上是和“继续改变各个粒子的生命值”是一样的
       在DX10中新着色引擎还将允许GPU独立于CPU外完成数据循环工作,使粒子系统可以脱离CPU束缚。

8

主题

716

帖子

716

积分

高级会员

Rank: 4

积分
716
发表于 2006-12-12 13:12:00 | 显示全部楼层

Re:基于GPU的粒子系统实现概要

你每帧都修改vertex buffer,则效率打了折扣
>const uniform register(常量寄存器)数量相对粒子系统来说不够,而且其他绘制体也需要使用。
所以需要定制一个尽量少的改动,DP调用次数 = 粒子总数 / (寄存器数 x 4 x 4 / 粒子变化参数个数)。 什么叫其他绘制体也需要使用?我想你对uniform和shader还欠理解。

如果你想用到vertex texture fetch和geometry shader,那么你的方案的实用性又打了折扣

我想你还是需要更多得了解当想要使用GPU来解脱CPU计算时所需要达成的前提条件和背景知识。

35

主题

370

帖子

376

积分

中级会员

Rank: 3Rank: 3

积分
376
 楼主| 发表于 2006-12-12 14:40:00 | 显示全部楼层

Re:基于GPU的粒子系统实现概要

         我这里没有修改 vertexbuffer,只是每桢修改了哪个粒子系统全局时间,也就是整个粒子系统已经存活的时间。而这个是传给shader 内部的extern变量值,它像uniform类型变量一样被看待。
         其它绘制体,指的是在每帧中其他需要绘制的,比如蒙皮动画需要使用const uniform register。
         关于这个问题可能我们所指的具体实现不一样,有所误会:)
         在一些方面我了解的还不是很全,但我想问下你的这些方案能在shader1.1上实现吗。现在还有很多shader1.1,shader2.0的显卡,我这篇文章是针对shader2.0及以下版本,重在描叙思路(文中有说明),是GPU粒子编程入门文章
          关于shader方面一些新技术以后还要请教大家~在这里先谢谢的热心回复。如有错误请继续评论指正。

8

主题

716

帖子

716

积分

高级会员

Rank: 4

积分
716
发表于 2006-12-12 15:40:00 | 显示全部楼层

Re:基于GPU的粒子系统实现概要

因为没有用到特别的指令,对于我提出的方案,在1.0/1.1/2.0/3.0上唯一的不同就是register的个数不同而已,所以只是批量提交的数量不同。
具体内容在我blog上曾有提到oiramario.cnblogs.com

35

主题

370

帖子

376

积分

中级会员

Rank: 3Rank: 3

积分
376
 楼主| 发表于 2006-12-12 15:46:00 | 显示全部楼层

Re:基于GPU的粒子系统实现概要

             马肝大哥来了啊呵呵,再说明下,可能你没仔细看。。我这里是一次性的创建vertexBuffer,其数量为粒子系统一般情况下运行时可见的粒子总数

35

主题

370

帖子

376

积分

中级会员

Rank: 3Rank: 3

积分
376
 楼主| 发表于 2006-12-12 20:09:00 | 显示全部楼层

Re:基于GPU的粒子系统实现概要

        哦,马肝大哥你的方案是不是用寄存器存放粒子信息(比如位置,速度),并在程序外面修改。
        你的博客打不开。。。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-6-14 01:05

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表