特效的批量绘制

特效的批量绘制

张嘉华(newzjh@126.com)

特效系统是游戏中的一个重要组成部分,在场景布谷,角色技能等有广泛应用。一个特效往往包含多种多样的组成元素:粒子系统,公告板,音效,图元轨迹,模型特效,镜头滤镜/震动/模糊,动态光源等。其中以粒子系统和公告板在场景中用得最多,往往一个特效由上述这些元素构成多个轨道,沿着时间轴以不同起始时间(相位)和周期播放。批量绘制和几何实例化(Batching and Geometry Instancing)相信读者已经不太陌生,那么比较困难的是既要满足美术开发时每个实例的多样性灵活性(多种渲染状态同时存在),又要保留批量绘制的高效(不同状态,不同成分的实例一起绘制)。根据我们的实验,在一个场景同时绘制100多个特效,每个特效包含4~10个粒子系统轨道,2~6个公告板轨道能够有好的效果和效率。接下来本文以粒子系统和公共板为例简单介绍一下我引擎中的实现方式。

effect1

1. 多个粒子系统的批量绘制

单个粒子系统的实现主要有状态保持和非状态保持两类。状态保持就是粒子系统中的每个粒子每帧都进行更新,根据各样的规则分别更新加速度,速度,位移,这种方式能够让美术在周期内对加速度或速度的变化根据曲线或者规则变化,也能够在周期中中途临时改变粒子的走向等,但是这种方式往往需要用若干RenderTarget纹理保存粒子当前位移,速度等状态,通过渲染到纹理来每帧更新纹理中每个粒子的这些状态;另外一种方式就是非状态保持,粒子的状态在每帧由公式根据平均加速度,最大速度,最小速度计算出当前的位置,这种方式比较利于在GPU的Vertex Shader中为每个粒子直接用中学物理公式:Pt=P0+vt+0.5*a*t*t计算出位置,而不用像状态保持方式那样通过Shader Model 3.0支持的tex2DLod这样的指令读取上一帧的状态。在我的游戏中,由于临时改变状态的行为比较少,因此单个粒子系统的实现只在GPU中采用了比较简单和易于实现的非状态保持方式。

1.1 多个粒子系统批量绘制的需求

无论状态保持和非状态保持,对于有经验的3D程序员来说,实现都不会太困难,而比较困难的是跨系统的粒子之间如何也进行批量绘制。那么接下来研究下多个粒子系统批量绘制的需求,也就是看看粒子系统之间究竟有多少差异需要进行提取和合并:

1, 不同的粒子系统采用不同的贴图,贴图需要合并

2, 不同的粒子系统下面这些参数可能不一样:最小速度,最大速度,最小角速度,最大角速度,最小生命周期,最大生命周期,每次发射粒子数,粒子发射间距等

3, 粒子系统跟帧缓冲的混合模式不一样:0暗的叠加(srcblend= srcalpha, destblend=invsrcalpha),1亮的叠加(srcblend=srcalpha,destblend=one)

4, 粒子的朝向不一样:0朝向某个法线,1朝向镜头

5, 粒子的开始相位和随机数生成不一样

1.2 Constants Instancing

根据这些需求,我们开始进行合并和批量绘制,采用的方法是GPU Gems2 Chapter3里面提到的四种实例方法的第三种:Constants Instancing
http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter03.html)。既提供一些对不支持硬件Instancing(不支持SetStreamSourceFreq)的向后兼容,也提供了能够每帧通过Constants设置粒子系统参数的灵活性。

1.3 顶点缓冲和索引缓冲的创建填充

首先,我们需要创建足够存储多个粒子系统的所有粒子的顶点缓冲和索引缓冲:

ret=pd3dDevice->CreateVertexBuffer(MAX_PARTICLE_SYSTEM_BATCH*MAX_PARTICLE_NUMBER*4*sizeof(PNCT1Vertex),0, D3DFVF_PNCT1Vertex,D3DPOOL_MANAGED, &g_pVB, NULL );

ret=pd3dDevice->CreateIndexBuffer(MAX_PARTICLE_SYSTEM_BATCH*MAX_PARTICLE_NUMBER*6*sizeof(int),0,D3DFMT_INDEX32,D3DPOOL_MANAGED,&g_pIB,NULL);

MAX_PARTICLE_SYSTEM_BATCH是定义每批次绘制的最大粒子系统数量的宏

MAX_PARTICLE_NUMBER是每个粒子系统中最大的粒子数量

所有批次的粒子系统都是共用这个顶点缓冲和索引缓冲,接下来就是填充顶点和索引数据,这里每个顶点的position存的是构成每个粒子的四边形面片的每个顶点在对象空间中的坐标,Shader的语义是POSITION0;每个顶点的索引index的三个分量存的是i粒子系统序号,j单个粒子系统中粒子序号和顶点在粒子面片4个顶点中的序号,语义是BLENDINDICES;每个顶点的uv存放的是纹理坐标,这里减少了0.01f是为了在合并后的纹理采样时看不到纹理间的裂缝。

int vertexcount=0;

//设置顶点

PNCT1Vertex* pVertices=NULL;

ret=g_pVB->Lock(0,0,(void**)&pVertices,0);

vertexcount=0;

for(int i=0;i

{

for(int j=0;j

{

pVertices[vertexcount+0].position=float3(-1.0f,-1.0f,0.0f);

pVertices[vertexcount+0].index=int3(i, j,0);

pVertices[vertexcount+0].uv=float2(0.01f,0.99f);

pVertices[vertexcount+1].position=float3(-1.0f,1.0f,0.0f);

pVertices[vertexcount+1].index= int3 (i, j,1);

pVertices[vertexcount+1].uv=float2(0.01f,0.01f);

pVertices[vertexcount+2].position=float3(1.0f,-1.0f,0.0f);

pVertices[vertexcount+2].index= int3 (i,j,2);

pVertices[vertexcount+2].uv=float2(0.99f,0.99f);

pVertices[vertexcount+3].position=float3(1.0f,1.0f,0.0f);

pVertices[vertexcount+3].index= int3 (i,j,3);

pVertices[vertexcount+3].uv=float2(0.99f,0.01f);

vertexcount+=4;

}

}

ret=g_pVB->Unlock();

//设置索引

int* pIndices=NULL;

ret=g_pIB->Lock(0,0,(void**)&pIndices,0);

vertexcount=0;

for(int i=0;i

{

for(int j=0;j

{

pIndices[0]=vertexcount+0;

pIndices[1]=vertexcount+1;

pIndices[2]=vertexcount+2;

pIndices[3]=vertexcount+2;

pIndices[4]=vertexcount+1;

pIndices[5]=vertexcount+3;

vertexcount+=4;

pIndices+=6;

}

}

ret=g_pIB->Unlock();

1.4 渲染时粒子系统参数的传递

接下来就是在渲染函数ParticleSystemManager::Render()把多个粒子系统的参数一次送到GPU的常量寄存器。首先计算总共有多少个批次batchnumber,用总共要绘制的特效系统实例数除以MAX_PARTICLE_SYSTEM_BATCH等到。接下来循环对每个批次中的各个粒子系统,计算该批次中粒子系统数量batchsize,等到每个粒子系统实例的数据指针pEntity,得到实例中用到的特效元数据指针pEffectElement和粒子系统数据指针pParticleSystem,接下来把这些参数编排到一个float4数组构成的结构ParticleSystemParameters,把这个数据通过
g_pEffect->SetFloatArray("batchdata",(float*)(&ParticleSystemParameters[0]),28*batchsize)一次设置到常量寄存器,通过ret=g_pEffect->SetFloat("batchstart",(float)batchstart)设置这批次的起始粒子系统序号,g_pEffect->SetTexture("particletexture",GEffectManager::GetTexture())设置合并后的特效纹理

static GParticleSystemParameter ParticleSystemParameters[MAX_PARTICLE_SYSTEM_BATCH];

UINT cPasses2=0;

ret=g_pEffect->Begin(&cPasses2,D3DXFX_DONOTSAVESTATE);

int batchnumber=nEntityNum/MAX_PARTICLE_SYSTEM_BATCH+1;

for(int batchindex=0;batchindex

{

int batchsize=MAX_PARTICLE_SYSTEM_BATCH;

if (batchindex==batchnumber-1)

batchsize=nEntityNum%MAX_PARTICLE_SYSTEM_BATCH;

int batchstart=batchindex*MAX_PARTICLE_SYSTEM_BATCH;

for (int i=0;i

{

GEffectElementEntity* pEntity=&pElementEntities[batchstart+i];

GEffectElement* pEffectElement=&pEntity->EffectElement;

GParticleSystem* pParticleSystem=(GParticleSystem*)(&pEffectElement->data[0]);

ParticleSystemParameters[i].parameters[0]=float4(vTranslation.x,vTranslation.y,vTranslation.z,pParticleSystem->m_fEmissionInterval);

ParticleSystemParameters[i].parameters[1]=float4(pParticleSystem->m_vMinVelocity.x,pParticleSystem->m_vMinVelocity.y,pParticleSystem->m_vMinVelocity.z,pParticleSystem->m_fParticlesPerEmission);

ParticleSystemParameters[i].parameters[2]=float4(pParticleSystem->m_vMaxVelocity.x,pParticleSystem->m_vMaxVelocity.y,pParticleSystem->m_vMaxVelocity.z,pParticleSystem->m_fMinLifeSpan);

ParticleSystemParameters[i].parameters[3]=float4(pParticleSystem->m_vAcceleration.x,pParticleSystem->m_vAcceleration.y,pParticleSystem->m_vAcceleration.z,pParticleSystem->m_fMaxLifeSpan);

ParticleSystemParameters[i].parameters[4]=float4(fSize,pParticleSystem->m_fWidthRatio,pParticleSystem->m_fMinAngularVelocity,pParticleSystem->m_fMaxAngularVelocity);

ParticleSystemParameters[i].parameters[5]=float4(pParticleSystem->m_fMinRadius,pParticleSystem->m_fMaxRadius,pParticleSystem->m_eBlendModel,(float)pEffectElement->nTextureIndex);

ParticleSystemParameters[i].parameters[6]=vColor;

}

ret=g_pEffect->SetFloat("batchstart",(float)batchstart);

ret=g_pEffect->SetFloatArray("batchdata",(float*)(&ParticleSystemParameters[0]),28*batchsize);

ret=g_pEffect->SetTexture("particletexture",GEffectManager::GetTexture());

for (int iPass = 0; iPass < (int)cPasses2; iPass++)

{

ret=g_pEffect->BeginPass(iPass);

ret=pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0, 0 ,batchsize*MAX_PARTICLE_NUMBER*4, 0 ,batchsize*MAX_PARTICLE_NUMBER*2);

ret=g_pEffect->EndPass();

}

}

ret=g_pEffect->End();

1.5粒子系统批量绘制的Shaders

接下来,我们开始编写FX文件来实现批量绘制。在Vertex Shader中,首先从BLENDINDICES语义中读入每个顶点的三个索引: i粒子系统在这批次中的序号,j粒子在单个粒子系统中的序号,k顶点在粒子面片中的序号。接下来根据序号从常量参数batchdata读出单个粒子系统的所有参数parameter0~parameter7。接下来一个很重要的事情就是为这些粒子根据序号j+i+batchstart作为种子编写的线性同余法产生确定性的伪随机数,用于后面给每个粒子在系统约束的最小最大速度,最小最大面积等之间分配数值,由此得到4个伪随机数randnum0~randnum4,并把前3个伪随机数写成3维向量randthree方便后面运算。接下来从parameter0~parameter7提取需要的参数。然后根据这些参数构造一系列公式计算粒子的具体当前参数,这些公式是本文的主要贡献之一。

粒子周期内时间:

Half ftime=saturate ( fmod ( time – floor ( j/ fParticlesPerEmission ) *fEmissionInterval , fTotalEmissionInterval ) / fLifeSpan ) * fLifeSpan;

上面这条公式首先对当前绝对时间time,根据初始相位进行偏移。由于每次发射fParticlesPerEmission 个粒子,那么j/ fParticlesPerEmission 就是第j个粒子所处的发生起始次数,乘以粒子发射间隔fEmissionInterval 就得到该粒子的初始相位floor ( j/ fParticlesPerEmission ) *fEmissionInterval。把这个相位叠加到绝对时间,对该粒子总的发射间隔fTotalEmissionInterval求模就得到该第j个粒子的发生区间。这里fEmissionInterval是从起始时间算起单个粒子系统中每批粒子发射间隔,fTotalEmissionInterval则是第j个粒子两次发射之间的间隔,因为粒子在GPU中周期到了会循环再利用。

粒子的循环再用周期:

half fTotalEmissionInterval = max ( MAX_PARTICLE_NUMBER * fEmissionInterval / fParticlesPerEmission , fLifeSpan );

fTotalEmissionInterval是第j个粒子两次发射之间的间隔,当该粒子的生命周期大于同一个系统中所有粒子循环再用的间隔,则以该粒子的生命周期作为循环再用周期,否则以同一个系统中所有粒子循环再用的间隔作为该粒子循环再用的周期。

粒子的生命周期:

half fLifeSpan=lerp(fMinLifeSpan,fMaxLifeSpan,randnum3);

粒子的生命周期根据前面算出的伪随机数在该系统的最小生命周期和最大生命周期作线性插值得到,由于伪随机数randnum3是根据粒子系统的序号和系统中的粒子序号j+i+batchstart作种子得到的,因此每个粒子得到的这个随机数都不一样,但又是确定的,也就是说只要粒子系统中的粒子序号j,粒子系统序号i,这批次中粒子系统起始序号batchstart都确定,这个随机数也就确定,生命周期也就随之确定。

粒子的速度:

half3 velocity=lerp(minvelocity,maxvelocity,randthree);

类似地,根据三维伪随机向量randthree在粒子系统的最小最大速度之间线性插值出该粒子的速度,这个得到的速度向量velocity也是关于j,i,batchstart具有确定性的。

粒子的角速度:

half fAngularVelocity=lerp(fMinAngularVelocity,fMaxAngularVelocity,randnum3);

half fAngular=fAngularVelocity*time;

half2 rotatevec=half2(cos(fAngular),sin(fAngular));

initpos.xy=half2(initpos.x*rotatevec.x-initpos.y*rotatevec.y,initpos.y*rotatevec.x+initpos.x*rotatevec.y);

类似地,根据伪随机数randnum3在粒子系统的最小最大角速度之间线性插值出该粒子的角速度,这个得到的角速度fAngularVelocity也是关于j,i,batchstart具有确定性的。当前的旋转角度fAngular就是角速度乘以时间fAngularVelocity*time。根据这个角度fAngular可以构造一个选择复数向量rotatevec,根据复数乘法对原顶点坐标向量initpos乘以旋转复数向量rotatevec得到旋转后的顶点坐标向量。

粒子的位移:

half3 localpos=velocity*ftime+0.5f*acceleration*ftime*ftime;

中学物理公式,就不解析了。

粒子的面积:

half fMinSize=parameter4.x;

half fMaxSize=parameter4.y;

half fSize=lerp(fMinSize,fMaxSize,randnum3);

half fWidthRatio=parameter4.y;

half3 objectpos= initpos*half3(fSize,fSize*fWidthRatio,fSize);

half3 billboardpos =mul((half3x3)View, objectpos);

类似地,根据伪随机数randnum3在粒子系统的最小最大面积之间线性插值出该粒子的面积,这个得到的面积fSize同样具有关于j,i,batchstart的确定性。把面积胜于长宽比构造出当前顶点在对象空间的位置向量
initpos*half3(fSize,fSize*fWidthRatio,fSize)。把这个向量作为右矩阵跟当前的视锥矩阵View的3X3部分作乘法得到沿镜头朝着用户眼睛的顶点坐标。这里值得注意的是mul第一个参数是矩阵的3X3部分因为这里是要根据View矩阵的三个向量构造一个微分几何的Frenet标架,用于变换对象空间的顶点向量在世界空间朝着摄像机。这个乘法等价于:

half3 xCamera=normalize(half3(View[0][0],View[1][0],View[2][0]));

half3 yCamera=normalize(half3(View[0][1],View[1][1],View[2][1]));

half3 zCamera=normalize(half3(View[0][2],View[1][2],View[2][2]));

half3 billboardpos = objectpos.x*xCamera+ objectpos.y*yCamera+ objectpos.z*zCamera;

粒子的发生半径:

half fMinRadius=parameter5.x;

half fMaxRadius=parameter5.y;

half fRadius=lerp(fMinRadius,fMaxRadius,randnum3);

half3 offsetpos=(randthree*2.0f-1.0f)*fRadius;

类似地,根据伪随机数randnum3在粒子系统的最小最大发射半径之间线性插值出该粒子的发射半径,这个得到的发射半径fRadius也是关于j,i,batchstart具有确定性的。

顶点的世界空间坐标:

half4 worldpos=half4(billboardpos+localpos+offsetpos+translation,1.0f);

顶点的世界坐标就是把前面算好的特效实例的平移值,该粒子的发射半径,粒子的位移,顶点在对象空间的位置加起来得到。

顶点的纹理坐标:

half textureid=parameter5.w;

half globalu=fmod(textureid,16.0f)/16.0f;

half globalv=floor(textureid/16.0f)/16.0f;

Out.uv=half2(globalu,globalv)+uv0/16.0f;

顶点的纹理坐标就是把原始的纹理坐标变换到合并后的特效贴图上的纹理坐标。先读取当前特效用到的纹理序号textureid,求出这个纹理在合并后的纹理的位置(globalu,gloablv)把这个位置加上原始顶点在自己纹理中的采样位置就得到新的变换后的纹理坐标。

特效的混合模式:

特效的混合模式是指特效与帧缓冲的alpha混合模式,在编辑器里有几种设置: 0暗混合(srcblend= srcalpha, destblend=invsrcalpha),1亮混合(srcblend=srcalpha,destblend=one)。如果混合模式不能统一,那么不同的混合模式就得分组绘制,会产生更多的批次。因此我们想方设法进行合并。首先看看这两种混合的公式:

暗混合:Cf= AsCs+AdCd; Ad=1-As

亮混合:Cf=AsCs+AdCd; Ad=1

Cf是帧缓冲混合完的颜色,Cs是Pixel Shader输出的颜色,As是Pixel Shader输出的alpha值,Cd是帧缓冲混合前的颜色。因此要对这混合模式进行合并就是得同时逐特效指定As和Ad。但是目前的D3D只支持指定这两者的其中一个。由于帧缓冲混合前的颜色Cd是无法修改的,如果我们在Pixel Shader中指定As,我们必须修改destblend这个状态。尽管我们无法修改帧缓冲的颜色,但是我们修改Pixel Shader输出的颜色,也可以用Pixel Shader输出的alpha值As’作为混合中帧缓冲的Alpha值(destblend= srcalpha)同时让Pixel Shader输出的颜色直接乘上原来需要的源混合因子As,即srcblend=1且Cs’=AsCs。

暗混合:Cf= Cs’+As’Cd; As’=1-As, Cs’=AsCs

亮混合:Cf=Cs’+As’Cd; As’=1, Cs’=AsCs

对上面两个式子合并一下有:

Cf=Cs’+As’Cd; As’=1-(1-blend)As

当blend=0暗混合模式时, As’=1-As; 当blend=1亮混合模式时, As’=1

最终我们有下面这段诡异的Shader

result.rgb*=result.a;

result.a=1.0f-(1.0f-blend)*result.a;

return result;

srcblend=one;

destblend=srcalpha;

下面是FX文件中主要的Shaders:

uniform float4 batchdata[238];

float rand(float value)

{

float n=511123;

float b=534;

return fmod(value*n+b,RANDMAX+1.0f);

}

Vertex VS_main(half3 initpos: POSITION0,half3 localindex: BLENDINDICES, half2 uv0: TEXCOORD0)

{

Vertex Out;

int i=localindex.x; //particle system index

int j=localindex.y; //particle index

int k=localindex.z; //vertex index

half4 parameter0=batchdata[i*7+0];

half4 parameter1=batchdata[i*7+1];

half4 parameter2=batchdata[i*7+2];

half4 parameter3=batchdata[i*7+3];

half4 parameter4=batchdata[i*7+4];

half4 parameter5=batchdata[i*7+5];

half4 parameter6=batchdata[i*7+6];

half randnum0=rand(j+i+batchstart)/RANDMAX;

half randnum1=rand(randnum0)/RANDMAX;

half randnum2=rand(randnum1)/RANDMAX;

half randnum3=rand(randnum2)/RANDMAX;

half3 randthree=half3(randnum0,randnum1,randnum2);

half fEmissionInterval=parameter0.w;

half fParticlesPerEmission=parameter1.w;

half fMinLifeSpan=parameter2.w;

half fMaxLifeSpan=parameter3.w;

half fLifeSpan=lerp(fMinLifeSpan,fMaxLifeSpan,randnum3);

half fTotalEmissionInterval=max(MAX_PARTICLE_NUMBER * fEmissionInterval/fParticlesPerEmission,fLifeSpan);

half ftime=saturate(fmod(time-floor(j/fParticlesPerEmission)*fEmissionInterval,fTotalEmissionInterval)/fLifeSpan)*fLifeSpan;

half3 translation=parameter0.xyz;

half3 minvelocity=parameter1.xyz;

half3 maxvelocity=parameter2.xyz;

half3 acceleration=parameter3.xyz;

half3 velocity=lerp(minvelocity,maxvelocity,randthree);

half3 localpos=velocity*ftime+0.5f*acceleration*ftime*ftime;

half fMinAngularVelocity=parameter4.z;

half fMaxAngularVelocity=parameter4.w;

half fAngularVelocity=lerp(fMinAngularVelocity,fMaxAngularVelocity,randnum3);

if (fAngularVelocity>0.01f)

{

half fAngular=fAngularVelocity*time;

half2 rotatevec=half2(cos(fAngular),sin(fAngular));

initpos.xy=half2(initpos.x*rotatevec.x-initpos.y*rotatevec.y,initpos.y*rotatevec.x+initpos.x*rotatevec.y);

}

half fMinSize=parameter4.x;

half fMaxSize=parameter4.y;

half fSize=lerp(fMinSize,fMaxSize,randnum3);

half fWidthRatio=parameter4.y;

half3 objectpos= initpos*half3(fSize,fSize*fWidthRatio,fSize);

half3 billboardpos=mul((half3x3)View,objectpos);

half fMinRadius=parameter5.x;

half fMaxRadius=parameter5.y;

half fRadius=lerp(fMinRadius,fMaxRadius,randnum3);

half3 offsetpos=(randthree*2.0f-1.0f)*fRadius;

half4 worldpos;

worldpos.xyz=billboardpos+localpos+offsetpos+translation;

worldpos.w=1.0f;

Out.worldpos=worldpos;

half4 prjpos=mul(worldpos, ViewProjection); //把顶点从世界空间变换到投影空间

Out.Pos = prjpos;

half textureid=parameter5.w;

half globalu=fmod(textureid,16.0f)/16.0f;

half globalv=floor(textureid/16.0f)/16.0f;

Out.uv=half2(globalu,globalv)+uv0/16.0f;

Out.color=parameter6;

Out.color.a*=1.0f-floor(ftime/fLifeSpan);

Out.blend=parameter5.z;

return Out;

}

half4 PS_main(half4 worldpos:TEXCOORD0,half2 uv:TEXCOORD1,half4 color:TEXCOORD2,half blend:TEXCOORD3) : COLOR0

{

half4 result=tex2D(ParticleSampler,uv)*color;

//远处云雾淡出

half fdistance=distance(worldpos.xz,CameraEye.xz);

half ffog=(fdistance-ffogstart)/(ffogend-ffogstart);

result.a*=1.0f-ffog;

//原纹理的混合比例直接让颜色乘以源的比例

//目的的比例通过destblend=srcalpha来设上去,因为混合模式不一样这个值不一样,用这个来根据混合模式设定帧缓冲的alpha混合比例

result.rgb*=result.a;

result.a=1.0f-(1.0f-blend)*result.a;

return result;

}

technique Render

{

pass Single_Pass

{

AlphaBlendEnable =true;

srcblend=one; //值得注意

destblend=srcalpha; //值得注意

Lighting = false;

cullmode=none;

zenable=true;

zwriteenable=false;

fogenable=false;

VertexShader = compile vs_2_0 VS_main();

PixelShader=compile ps_2_0 PS_main();

}

}

2. 公告板的批量绘制

相对来说,公告板特效的合并要简单些,公告板可以看作是粒子系统中只有一个粒子的粒子系统。

2.1顶点缓冲和索引缓冲的创建填充

首先,我们需要创建足够存储多个粒子系统的所有粒子的顶点缓冲和索引缓冲:

ret=pd3dDevice->CreateVertexBuffer(MAX_SPLITE_BATCH*4*sizeof(PNCT1Vertex),0, D3DFVF_PNCT1Vertex,D3DPOOL_MANAGED, &g_pVB, NULL );

ret=pd3dDevice->CreateIndexBuffer(MAX_SPLITE_BATCH*6*sizeof(int),0,D3DFMT_INDEX32,D3DPOOL_MANAGED,&g_pIB,NULL);

MAX_SPLITE_BATCH是公告板的数量

所有批次的公告板都是共用这个顶点缓冲和索引缓冲,接下来就是填充顶点和索引数据,这里每个顶点的position存的是构成每个粒子的四边形面片的每个顶点在对象空间中的坐标,Shader的语义是POSITION0;每个顶点的索引index的三个分量存的是i公告板的序号,固定的一个0值(公布板内没有再细分的元素了)和顶点在公告板面片4个顶点中的序号,语义是BLENDINDICES;每个顶点的uv存放的是纹理坐标,这里减少了0.01f是为了在合并后的纹理采样时看不到纹理间的裂缝。

vertexcount=0;

for(int i=0;i

{

pVertices[vertexcount+0].position=float3(-1.0f,-1.0f,0.0f);

pVertices[vertexcount+0].index=int3(i,0,0);

pVertices[vertexcount+0].uv=float2(0.01f,0.99f);

pVertices[vertexcount+1].position=float3(-1.0f,1.0f,0.0f);

pVertices[vertexcount+1].index = int3 (i,0,1);

pVertices[vertexcount+1].uv=float2(0.01f,0.01f);

pVertices[vertexcount+2].position=float3(1.0f,-1.0f,0.0f);

pVertices[vertexcount+2].index = int3 (i,0,2);

pVertices[vertexcount+2].uv=float2(0.99f,0.99f);

pVertices[vertexcount+3].position=float3(1.0f,1.0f,0.0f);

pVertices[vertexcount+3].index = int3 (i,0,3);

pVertices[vertexcount+3].uv=float2(0.99f,0.01f);

vertexcount+=4;

}

2.2公告板批量绘制的Shaders

公告板和粒子系统的主要差别是,公告板的旋转要根据轴来进行,要根据公告板的角速度fAngularVelocity计算出旋转角度fAngular,进而根据旋转轴rotatedir构造一个旋转矩阵matRotate进行旋转。这里的角速度fAngularVelocity就是输入的旋转向量rotatevec的模长,旋转轴rotatedir就是旋转向量rotatevec的单位向量。输入的zPlane是公告板的法线。对zPlane进行两次叉乘,可以构造出一个以输入法线zPlane为z轴,叉乘得到的xPlane和yPlane为x轴和y轴的3×3 Frenet标架(xPlane,yPlane,zPlane),也就是一个坐标基。因此根据输入的法线zPlane可以计算出一个局部坐标objectpos0,根据摄像机的朝向View可以构造出另外一个朝着用户的局部坐标objectpos1。对这两个值作线性插值,当normaltype=0,全局法线模式时,objectpos取objectpos0,当normaltype=1,朝着镜头模式时,objectpos取objectpos1,也可以取浮点的normaltype在两者之间取值。

half3 yPlane=cross(zPlane,half3(1,0,0));

half3 xPlane=cross(yPlane,zPlane);

half3 objectpos0=localpos.x*xPlane+localpos.y*yPlane+localpos.z*zPlane;

half3 objectpos1=mul((half3x3)View,localpos);

half3 objectpos=lerp(objectpos0,objectpos1,normaltype);

Vertex VS_main(half3 initpos: POSITION0,float3 localindex:NORMAL0, half2 uv0: TEXCOORD0)

{

Vertex Out;

int i=localindex.x; //splite system index [0~40]

int k=localindex.z; //vertex index

float4 parameter0=batchdata[i*6+0];

float4 parameter1=batchdata[i*6+1];

float4 parameter2=batchdata[i*6+2];

float4 parameter3=batchdata[i*6+3];

float4 parameter4=batchdata[i*6+4];

float4 parameter5=batchdata[i*6+5];

float randnum0=rand(i+batchstart)/1241.0f;

float randnum1=rand(randnum0)/1241.0f;

float randnum2=rand(randnum1)/1241.0f;

float randnum3=rand(randnum2)/1241.0f;

float fLifeSpan=parameter4.x;

float fLifeStart=parameter4.y;

float fTimeRange=parameter4.z;

float ftime=saturate((fmod(time-fLifeStart,fTimeRange))/fLifeSpan)*fLifeSpan;

half3 translation=parameter0.xyz;

half normaltype=parameter1.w;

half3 zPlane=normalize(parameter1.xyz);

half3 rotatevec=parameter2.xyz;

half fAngularVelocity=length(rotatevec);

half3 rotatedir=normalize(rotatevec);

half fSize=parameter3.x;

half fWidthRatio=parameter3.y;

half3 localpos=initpos*half3(fSize,fSize*fWidthRatio,fSize);

if (fAngularVelocity>0.01f)

{

half fAngular=fAngularVelocity*time;

float4x4 matRotate;

float fCos = cos( fAngular );

float fSin = sin( fAngular );

matRotate[0][0] = ( rotatedir.x * rotatedir.x ) * ( 1.0f - fCos ) + fCos;

matRotate[0][1] = ( rotatedir.x * rotatedir.y ) * ( 1.0f - fCos ) - (rotatedir.z * fSin);

matRotate[0][2] = ( rotatedir.x * rotatedir.z ) * ( 1.0f - fCos ) + (rotatedir.y * fSin);

matRotate[1][0] = ( rotatedir.y * rotatedir.x ) * ( 1.0f - fCos ) + (rotatedir.z * fSin);

matRotate[1][1] = ( rotatedir.y * rotatedir.y ) * ( 1.0f - fCos ) + fCos ;

matRotate[1][2] = ( rotatedir.y * rotatedir.z ) * ( 1.0f - fCos ) - (rotatedir.x * fSin);

matRotate[2][0] = ( rotatedir.z * rotatedir.x ) * ( 1.0f - fCos ) - (rotatedir.y * fSin);

matRotate[2][1] = ( rotatedir.z * rotatedir.y ) * ( 1.0f - fCos ) + (rotatedir.x * fSin);

matRotate[2][2] = ( rotatedir.z * rotatedir.z ) * ( 1.0f - fCos ) + fCos;

matRotate[0][3] = matRotate[1][3] = matRotate[2][3] = matRotate[3][0] = matRotate[3][1] = matRotate[3][2] = 0.0f;

matRotate[3][3] = 1.0f;

localpos=mul(localpos,matRotate);

}

half3 yPlane=cross(zPlane,half3(1,0,0));

half3 xPlane=cross(yPlane,zPlane);

half3 objectpos0=localpos.x*xPlane+localpos.y*yPlane+localpos.z*zPlane;

half3 objectpos1=mul((half3x3)View,localpos);

half3 objectpos=lerp(objectpos0,objectpos1,normaltype);

half4 worldpos;

worldpos.xyz=objectpos+translation;

worldpos.w=1.0f;

Out.worldpos=worldpos;

//把顶点从世界空间变换到投影空间

half4 prjpos=mul(worldpos, ViewProjection);

Out.Pos = prjpos;

half textureid=parameter0.w;

half globalu=fmod(textureid,16.0f)/16.0f;

half globalv=floor(textureid/16.0f)/16.0f;

Out.uv=half2(globalu,globalv)+uv0/16.0f;

Out.color=parameter5;

Out.color.a*=1.0f-floor(ftime/fLifeSpan);

Out.blend=parameter2.w;

return Out;

}

half4 PS_main(half4 worldpos:TEXCOORD0,half2 uv:TEXCOORD1,half4 color:TEXCOORD2,half blend:TEXCOORD3) : COLOR0

{

half4 result=tex2D(SpliteSampler,uv)*color;

//远处云雾淡出

half fdistance=distance(worldpos.xz,CameraEye.xz);

half ffog=(fdistance-ffogstart)/(ffogend-ffogstart);

result.a*=1.0f-ffog;

//原纹理的混合比例直接让颜色乘以源的比例

//目的的比例通过destblend=srcalpha来设上去,因为混合模式不一样这个值不一样,用这个来根据混合模式设定帧缓冲的alpha混合比例

result.rgb*=result.a;

result.a=1.0f-(1.0f-blend)*result.a;

return result;

}

3. 特效纹理的合并

这个就比较简单了,就是把纹理进行一下拼图算法。这个纹理是各种特效粒子系统,公告板,图元轨迹,模型特效等共用的。下面是一个合并的特效纹理例子。

effect2

已标记关键词 清除标记
相关推荐
DirectX修复工具(DirectX Repair)是一款系统级工具软件,简便易用。本程序为绿色版,无需安装,可直接运行。 本程序的主要功能是检测当前系统的DirectX状态,如果发现异常则进行修复。程序主要针对0xc000007b问题设计,可以完美修复该问题。本程序中包含了最新版的DirectX redist(Jun2010),并且全部DX文件都有Microsoft的数字签名,安全放心。 本程序为了应对一般电脑用户的使用,采用了易用的一键式设计,只要点击主界面上的“检测并修复”按钮,程序就会自动完成校验、检测、下载、修复以及注册的全部功能,无需用户的介入,大大降低了使用难度。在常规修复过程中,程序还会自动检测DirectX加速状态,在异常时给予用户相应提示。 本程序适用于多个操作系统,如Windows XP(需先安装.NET 2.0,详情请参阅“致Windows XP用户.txt”文件)、Windows Vista、Windows 7、Windows 8、Windows 8.1、Windows 8.1 Update、Windows 10,同时兼容32位操作系统和64位操作系统。本程序会根据系统的不同,自动调整任务模式,无需用户进行设置。 本程序的V4.0版分为标准版、增强版以及在线修复版。所有版本都支持修复DirectX的功能,而增强版则额外支持修复c++的功能。在线修复版功能与标准版相同,但其所需的数据包需要在修复时自动下载。各个版本之间,主程序完全相同,只是其配套使用的数据包不同。因此,标准版和在线修复版可以通过补全扩展包的形式成为增强版。本程序自V3.5版起,自带扩展功能。只要在主界面的“工具”菜单下打开“选项”对话框,找到“扩展”标签,点击其中的“开始扩展”按钮即可。扩展过程需要Internet连接,扩展成功后新的数据包可自动生效。扩展用时根据网络速度不同而不同,最快仅需数秒,最慢需要数分钟,烦请耐心等待。如扩展失败,可点击“扩展”界面左上角小锁图标切换为加密连接,即可很大程度上避免因防火墙或其他原因导致的连接失败。 本程序自V2.0版起采用全新的底层程序架构,使用了异步多线程编程技术,使得检测、下载、修复单独进行,互不干扰,快速如飞。新程序更改了自我校验方式,因此使用新版本的程序时不会再出现自我校验失败的错误;但并非取消自我校验,因此程序安全性与之前版本相同,并未降低。 程序有更新系统c++功能。由于绝大多数软件运行时需要c++的支持,并且c++的异常也会导致0xc000007b错误,因此程序在检测修复的同时,也会根据需要更新系统中的c++组件。自V3.2版本开始使用了全新的c++扩展包,可以大幅提高工业软件修复成功的概率。修复c++的功能仅限于增强版,标准版及在线修复版在系统c++异常时(非丢失时)会提示用户使用增强版进行修复。除常规修复外,新版程序还支持C++强力修复功能。当常规修复无效时,可以到本程序的选项界面内开启强力修复功能,可大幅提高修复成功率。请注意,请仅在常规修复无效时再使用此功能。 程序有两种窗口样式。正常模式即默认样式,适合绝大多数用户使用。另有一种简约模式,此时窗口将只显示最基本的内容,修复会自动进行,修复完成10秒钟后会自动退出。该窗口样式可以使修复工作变得更加简单快速,同时方便其他软件、游戏将本程序内嵌,即可进行无需人工参与的快速修复。开启简约模式的方法是:打开程序所在目录下的“Settings.ini”文件(如果没有可以自己创建),将其中的“FormStyle”一项的值改为“Simple”并保存即可。 新版程序支持命令行运行模式。在命令行中调用本程序,可以在路径后直接添加命令进行相应的设置。常见的命令有7类,分别是设置语言的命令、设置窗口模式的命令,设置安全级别的命令、开启强力修复的命令、设置c++修复模式的命令、控制Direct加速的命令、显示版权信息的命令。具体命令名称可以通过“/help”或“/?”进行查询。 程序有高级筛选功能,开启该功能后用户可以自主选择要修复的文件,避免了其他不必要的修复工作。同时,也支持通过文件进行辅助筛选,只要在程序目录下建立“Filter.dat”文件,其中的每一行写一个需要修复文件的序号即可。该功能仅针对高级用户使用,并且必须在正常窗口模式下才有效(简约模式时无效)。 本程序有自动记录日志功能,可以记录每一次检测修复结果,方便在出现问题时,及时分析和查找原因,以便找到解决办法。 程序的“选项”对话框中包含了7项高级功能。点击"常规”选项卡可以调整程序的基本运行情况,包括日志记录、安全级别控制、调试模式开启等。只有开启调试模式后才能在C
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页