6.6 粒子系统
6.6.1 粒子系统的概念
很多自然现象都可以由具有相似风格运行的小粒子集合来模拟,粒子系统就是专门用来产生、控制及渲染这些粒子的系统,如图6-13所示。粒子系统可以实现的效果包括:
(1)火把上面的火苗;
(2)汽车排出的尾气;
(3)一群狂怒的异形生物攻击时的编队;
(4)成群或者成串的物体;
(5)运动效果,如击起的草皮碎片。

图6-13 粒子系统
在粒子系统中,有一个重要的概念是粒子发射体,粒子发射体是用来发射粒子的,类似草坪上的喷水枪。所有的粒子都将由粒子发射体来产生,设计人员需要确定某个粒子系统需要发射多少个粒子。通常不要考虑太多的粒子,因为这将占用大量的处理器时间。
粒子的行为将受到作用于粒子上的力的影响,这些力称为场。每种力场都有各种吸引因子和排斥因子,引导不同的粒子运动行为,可以用来构造更为复杂的粒子行为。
6.6.2 粒子系统的实现
在Direct3D中可以按照下面的步骤实现一个粒子系统。
1)设置粒子属性
首先定义一个粒子的结构体,用来描述粒子:
struct PARTICLE
{
D3DXVECTOR3 m_vPos; // 当前位置
D3DXVECTOR3 m_vVel; // 当前速度
D3DXVECTOR3 m_vPos0; // 初始位置
D3DXVECTOR3 m_vVel0; // 初始速度
FLOAT m_fTime0; // 创建时间
D3DXCOLOR m_clrDiffuse; // 初始的颜色
D3DXCOLOR m_clrFade; // 消失时颜色
FLOAT m_fFade; // 上两种颜色的中间过渡颜色的程度,
//如1.0时是m_clrDiffuse,0.0时是m_clrFade,0~1之间是两者的混合色
PARTICLE* m_pNext; // 这里的粒子使用链表方式管理
};
2)粒子管理
粒子管理是影响粒子系统性能的最重要因素,而主要的原因就出在内存管理上。有一点很重要,就是在粒子消亡后尽量不要急着释放内存,尽可能一次释放全部粒子。可以使用两个链表,一个记录处于活动期的粒子(m_pParticles),一个记录已经消亡的粒子(m_pParticlesFree)。当生成新粒子的时候,先检查m_pParticlesFree链表中是否有粒子,有就直接使用,把此粒子添加到m_pParticles链表中,并从m_pParticlesFree链表中删除。而当m_pParticles链表中有粒子进入死亡期时,就把此粒子添加到m_pParticlesFree链表中,并从m_pParticles链表中删除。这样操作就可以不用释放内存,在绘制时也不用判断消亡粒子,所以效率是很高的。
下面的程序片断用于粒子的管理。
【例6-7】粒子的管理:
PARTICLE* ParticleSystem::AddParticle()
{
PARTICLE *pParticle=NULL;
if(m_dwParticles>=m_dwParticlesLim)return NULL;//是否达到粒子限制数
if( m_pParticlesFree )
{
pParticle = m_pParticlesFree;
m_pParticlesFree = pParticle->m_pNext;
}
else
{
if( NULL == ( pParticle = new PARTICLE ) )
return NULL;
}
pParticle->m_pNext = m_pParticles;
m_pParticles = pParticle;
m_dwParticles++;
return pParticle;
}
HRESULT ParticleSystem::Release()
{
while( m_pParticles )
{
PARTICLE* pSpark = m_pParticles;
m_pParticles = pSpark->m_pNext;
delete pSpark;
}
while( m_pParticlesFree )
{
PARTICLE *pSpark = m_pParticlesFree;
m_pParticlesFree = pSpark->m_pNext;
delete pSpark;
}
return S_OK;
}
3)粒子绘制
粒子的绘制是发生在每一帧中的。在每一帧中,由于粒子系统显示的内容总是动态的小东西(如果是静态的,或者是大的图片,就不应该采用粒子系统),所以要对所有的粒子进行遍历访,问并根据情况修改粒子的显示位置。对于粒子位置的计算则应该由游戏的要求来决定,比如显示的是下雨的雨点,则它的位置轨迹应是自由落体的效果,也许为了更真实还会加入一些风的效果。而如果是下雪,则看起来更应该像是匀速下落,风的作用则必须要加上(不受风影响的雪花,好像不符合大多数人的生活经验吧)。
另一个重要的问题是,粒子系统的显示与否是由系统是否支持点精灵技术决定的。点精灵技术是指在一个点的位置可以显示一幅图。要想使用这项技术,需要明确说明。
m_pDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE );
m_pDevice->SetRenderState( D3DRS_POINTSCALEENABLE, TRUE );
【例6-8】粒子显示:
HRESULT CParticleSystem::Render(float tFrame)
{
Update(tFrame);
HRESULT hr;
D3DMATRIX matWorld;
memset(&matWorld,0,sizeof(matWorld));
matWorld._11=1;
matWorld._22=1;
matWorld._33=1;
matWorld._44=1;
m_pDevice->SetTransform(D3DTS_WORLD,&matWorld);
m_pDevice->SetTexture(0,m_pTexture);
m_pDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE );
m_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
m_pDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE );
m_pDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );
m_pDevice->SetTextureStageState(0,D3DTSS_COLORARG2,
D3DTA_DIFFUSE);
m_pDevice->SetTextureStageState(0,D3DTSS_COLOROP,
D3DTOP_MODULATE);
m_pDevice->SetRenderState( D3DRS_LIGHTING,FALSE);
// Set the render states for using point sprites
m_pDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE );
m_pDevice->SetRenderState( D3DRS_POINTSCALEENABLE, TRUE );
m_pDevice->SetRenderState( D3DRS_POINTSIZE,
FtoDW(m_fParticleSize) );
m_pDevice->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW(0.00f) );
m_pDevice->SetRenderState( D3DRS_POINTSCALE_A, FtoDW(0.00f) );
m_pDevice->SetRenderState( D3DRS_POINTSCALE_B, FtoDW(0.00f) );
m_pDevice->SetRenderState( D3DRS_POINTSCALE_C, FtoDW(1.00f) );
// Set up the vertex buffer to be rendered
m_pDevice->SetStreamSource( 0, m_pVB, 0, sizeof(POINTVERTEX) );
m_pDevice->SetFVF( POINTVERTEX::FVF );
PARTICLE* pParticle = m_pParticles;
POINTVERTEX* pVertices;
DWORD dwNumParticlesToRender = 0;
// Lock the vertex buffer. We fill the vertex buffer in small
// chunks, using D3DLOCK_NOOVERWRITE. When we are done filling
// each chunk, we call DrawPrim, and lock the next chunk. When
// we run out of space in the vertex buffer, we start over at
// the beginning, using D3DLOCK_DISCARD.
//锁定顶点缓冲区,以小块填充,如果所有的小块都填充了,
//就绘制它们,然后再锁定下一个块,
//如果空间用完了,就从头开始,使用DISCARD方式销毁
m_dwBase += m_dwFlush;
if(m_dwBase >= m_dwDiscard)
m_dwBase = 0;
//dwBase开始是没有使用的缓冲区, 要使用m_dwFlush个顶点
if( FAILED( hr = m_pVB->Lock( m_dwBase * sizeof(POINTVERTEX),
m_dwFlush * sizeof(POINTVERTEX),
(void**) &pVertices,
m_dwBase ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD ) ) )
{
return hr;
}
// Render each particle
while( pParticle )
{
D3DXVECTOR3 vPos(pParticle->m_vPos);
D3DXVECTOR3 vVel(pParticle->m_vVel);
FLOAT fLengthSq = D3DXVec3LengthSq(&vVel);
UINT dwSteps;
if( fLengthSq < 1.0f ) dwSteps = 2;
else if( fLengthSq < 4.00f ) dwSteps = 3;
else if( fLengthSq < 9.00f ) dwSteps = 4;
else if( fLengthSq < 12.25f ) dwSteps = 5;
else if( fLengthSq < 16.00f ) dwSteps = 6;
else if( fLengthSq < 20.25f ) dwSteps = 7;
else dwSteps = 8;
dwSteps=1;
vVel *= -0.04f / (FLOAT)dwSteps;
DWORD dwDiffuse=ColorLerp(pParticle->m_clrEmit,
pParticle->m_clrFade,
1-pParticle->m_fFade/(m_fParticleFade));
// Render each particle a bunch of times to get a blurring effect
for( DWORD i = 0; i < dwSteps; i++ )
{
pVertices->v = vPos;
pVertices->color = dwDiffuse;
pVertices++;
if( ++dwNumParticlesToRender == m_dwFlush )
{
// Done filling this chunk of the vertex buffer
//Lets unlock and
// draw this portion so we can begin filling the next chunk
m_pVB->Unlock();
if(FAILED(hr =
m_pDevice->DrawPrimitive( D3DPT_POINTLIST,
m_dwBase, dwNumParticlesToRender)))
return hr;
// Lock the next chunk of the vertex buffer
//If we are at the
// end of the vertex buffer,
//DISCARD the vertex buffer and start
// at the beginning. Otherwise,
//specify NOOVERWRITE, so we can
// continue filling the VB while
//the previous chunk is drawing
m_dwBase += m_dwFlush;
if(m_dwBase >= m_dwDiscard)
m_dwBase = 0;
if(FAILED(hr=m_pVB->Lock(m_dwBase*sizeof(POINTVERTEX),
m_dwFlush * sizeof(POINTVERTEX),
(void**) &pVertices,
m_dwBase ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD ) ) )
{
return hr;
}
dwNumParticlesToRender = 0;
}
vPos += vVel;
}
pParticle = pParticle->m_pNext;
}
// Unlock the vertex buffer
m_pVB->Unlock();
// Render any remaining particles
if( dwNumParticlesToRender )
{
if(FAILED(hr = m_pDevice->DrawPrimitive( D3DPT_POINTLIST,
m_dwBase, dwNumParticlesToRender )))
return hr;
}
// Reset render states
m_pDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE );
m_pDevice->SetRenderState( D3DRS_POINTSCALEENABLE, FALSE );
m_pDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
m_pDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
m_pDevice->SetRenderState( D3DRS_LIGHTING,TRUE);
m_pDevice->SetTextureStageState(0,D3DTSS_COLOROP ,D3DTOP_SELECTARG1);
return S_OK;
}







