在学习粒子系统后,继续学习到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联合更新粒子系统。
|