您的位置:首页 > 其它

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

2010-02-08 23:11 232 查看
在学习粒子系统后,继续学习到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联合更新粒子系统。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: