基于GemetryShader的粒子系统
发布:siney | 发布时间: 2008 09 19这篇文章的主要内容是Dx10的一个例子,ParticlesGS Sample,Siney最近在研究dx10,说起来,已经“研究”好久了,但一直没有系统的写点例子,了解整个dx10的架构,所以写一篇dx10的文章,1)是总结学习,2)是整理下思路,根据自己的理解把相关的内容讲述清楚。
dx10与dx9相比,有很多变化,其中之一就是渲染管线的变化,如图:
可以看到,主要是加入了Stream Output和Geometry-Shader阶段,而ParticlesGS Sample正好演示这两个阶段的用处和功能,这个例子演示了如何实现一个基于gs的粒子系统,该粒子系统是模拟焰火,由于焰火需要爆炸出新例子的特性,使用gs可以动态构建新primitive的特性,可以很方便的模拟这个系统,在初始阶段,我只要在IA阶段传入一个luancher粒子,他的作用就是定时的产生新的shell例子,然后shell粒子经过一段时间后,爆炸出n多ember粒子,ember粒子再经过一段时间后就消亡了,往复这个过程,我们就可以看见一个焰火的模拟例子系统。
以luancher产生shell粒子为例,可以看到gs如下:
[maxvertexcount(128)]
void GSAdvanceParticlesMain(point VSParticleIn input[1], inout PointStream<VSParticleIn> ParticleOutputStream)
{
if( input[0].Type == PT_LAUNCHER )
GSLauncherHandler( input[0], ParticleOutputStream );
else if ( input[0].Type == PT_SHELL )
GSShellHandler( input[0], ParticleOutputStream );
else if ( input[0].Type == PT_EMBER1 ||
input[0].Type == PT_EMBER3 )
GSEmber1Handler( input[0], ParticleOutputStream );
else if( input[0].Type == PT_EMBER2 )
GSEmber2Handler( input[0], ParticleOutputStream );
}
void GSLauncherHandler( VSParticleIn input, inout PointStream<VSParticleIn> ParticleOutputStream )
{
if(input.Timer <= 0)
{
float3 vRandom = normalize( RandomDir( input.Type ) );
//time to emit a new SHELL
VSParticleIn output;
output.pos = input.pos + input.vel*g_fElapsedTime;
output.vel = input.vel + vRandom*8.0;
output.Timer = P_SHELLLIFE + vRandom.y*0.5;
output.Type = PT_SHELL;
ParticleOutputStream.Append( output );
//reset our timer
input.Timer = g_fSecondsPerFirework + vRandom.x*0.4;
}
else
{
input.Timer -= g_fElapsedTime;
}
//emit ourselves to keep us alive
ParticleOutputStream.Append( input );
}
除去vs和ps没有的语法外,上述gs还是蛮好理解的,简单来说,就是在定时器结束的时候,new一个新的shell例子,并Append到ParticleOutputStream,注意这里的primitive为point,而不是triangle,所以在后面渲染的时候,我们还需要在gs中,展开为triangle以渲染一个billboard。还需要注意的是,我们需要重置luancher例子的计时器,并也把他Append到ParticleOutputStream,没有了这个发动机,下次就不会产生shell例子了。
上述gs代码需要stream output,如下:
// Point to the correct output buffer
pBuffers[0] = g_pParticleStreamTo;
pd3dDevice->SOSetTargets( 1, pBuffers, offset );
[maxvertexcount(128)]的语意是告诉gpu,这段gs代码最多会产生128个顶点,当然也可能没有这么多,比如产生shell例子就仅需要[maxvertexcount(1)],而128这个数值是针对shell产生ember粒子。能够动态产生point的粒子了,我们还需要渲染这些粒子,我们都知道粒子的渲染一般是billboard,而仅仅是一个point是不能构成billboard,所以我们还需要一段gs,展开point成为2个triangle,如下gs:
[maxvertexcount(4)]
void GSScenemain(point VSParticleDrawOut input[1], inout TriangleStream<PSSceneIn> SpriteStream)
{
PSSceneIn output;
//
// Emit two new triangles
//
for(int i=0; i<4; i++)
{
float3 position = g_positions[i]*input[0].radius;
position = mul( position, (float3x3)g_mInvView ) + input[0].pos;
output.pos = mul( float4(position,1.0), g_mWorldViewProj );
output.color = input[0].color;
output.tex = g_texcoords[i];
SpriteStream.Append(output);
}
SpriteStream.RestartStrip();
}
这段gs,产生4个vertex,其实就是strip的4边形,最后需要RestartStrip(),告诉gpu一段strip完成,这样就会产生独立的4边形,否则可能会导致后续的vertex也进入strip,从而渲染错误。
在开始渲染之前,我们需要关闭stream output,否则后续的gs代码又会stream output,如下:
// Get back to normal
pBuffers[0] = NULL;
pd3dDevice->SOSetTargets( 1, pBuffers, offset );
这样后续的gs阶段后,会进入ps阶段。
最后我们还需要注意,由于在gs中我们可能增加vertex,也可能不增加,但对于外部的draw函数来说,并不知道实际的顶点数量,或者primitive数量,这使得我们不能直接调用传统的Draw函数,为此dx10为我们提供了DrawAuto函数,他会根据gpu实际产生的primitive数量来渲染,不需要任何参数。
用gs实现粒子系统确实很方便,也很高效,与传统的基于dx9的粒子系统,我们避免了频繁的lock & unlock,形体计算等cpu密集的操作,而是完全交给gpu去计算、更新,能够更好的并行cpu和gpu,最大发挥gpu的威力。
- 相关文章:
- 1.tomorrowmorn
- 能不能在新手阶段和重要系统的介绍,加点flash和语音。比如新手登陆游戏后的基本操作,领取灵兽后,对灵兽使用和操作的flash介绍。
之所以这样,是最近练了个小号,发觉很多新手要到20级了,还在遍地图跑,不会用神石。尤其是女性玩家更严重,幻化这些不知道怎么使用。有个玩家我“教”她很久,还是不会;后来她把mima和账号,让我登陆,帮她操作。 - 2008-09-19 17:49:11 回复该留言
发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。






