[引] 微软DirectX9.0中粒子系统示例的源代码注释
创建点精灵顶点结构与顶点FVF:
struct POINTVERTEX
{
D3DXVECTOR3 v;
D3DCOLOR color;
static const DWORD FVF;
};
const DWORD POINTVERTEX::FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;
粒子结构:
struct PARTICLE
{
BOOL m_bSpark; // spark火星,是指主粒子碰撞到地面后能量较弱的粒子
D3DXVECTOR3 m_vPos; // Current position当前位置
D3DXVECTOR3 m_vVel; // Current velocity当前速度
D3DXVECTOR3 m_vPos0; // Initial position初始位置
D3DXVECTOR3 m_vVel0; // Initial velocity初始速度
FLOAT m_fTime0; // Time of creation创建时间
D3DXCOLOR m_clrDiffuse; // Initial diffuse color初始散射光颜色
D3DXCOLOR m_clrFade; // Faded diffuse color消失时颜色
FLOAT m_fFade; // Fade progression消隐进程
PARTICLE* m_pNext; // Next particle in list下一粒子指针
};
enum PARTICLE_COLORS { COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, NUM_COLORS };
D3DXCOLOR g_clrColor[NUM_COLORS] =
{
D3DXCOLOR( 1.0f, 1.0f, 1.0f, 1.0f ),
D3DXCOLOR( 1.0f, 0.5f, 0.5f, 1.0f ),
D3DXCOLOR( 0.5f, 1.0f, 0.5f, 1.0f ),
D3DXCOLOR( 0.125f, 0.5f, 1.0f, 1.0f )
};
DWORD g_clrColorFade[NUM_COLORS] =
{
D3DXCOLOR( 1.0f, 0.25f, 0.25f, 1.0f ),
D3DXCOLOR( 1.0f, 0.25f, 0.25f, 1.0f ),
D3DXCOLOR( 0.25f, 0.75f, 0.25f, 1.0f ),
D3DXCOLOR( 0.125f, 0.25f, 0.75f, 1.0f )
};
粒子管理系统:
class CParticleSystem
{
protected:
FLOAT m_fRadius;
DWORD m_dwBase; //顶点缓冲区的基值
DWORD m_dwFlush; //顶点缓冲区的块的大小
DWORD m_dwDiscard; //顶点缓冲区的大小
DWORD m_dwParticles; //活动粒子的数目
DWORD m_dwParticlesLim; //活动粒子数目限制
PARTICLE* m_pParticles;//活动粒子链表
PARTICLE* m_pParticlesFree;//消亡粒子链表
// Geometry
LPDIRECT3DVERTEXBUFFER9 m_pVB;
public:
CParticleSystem( DWORD dwFlush, DWORD dwDiscard, FLOAT fRadius );
~CParticleSystem();
HRESULT RestoreDeviceObjects( LPDIRECT3DDEVICE9 pd3dDevice );
HRESULT InvalidateDeviceObjects();
//更新函数
HRESULT Update( FLOAT fSecsPerFrame, DWORD dwNumParticlesToEmit,
const D3DXCOLOR &dwEmitColor, const D3DXCOLOR &dwFadeColor,
FLOAT fEmitVel, D3DXVECTOR3 vPosition );
//渲染函数
HRESULT Render( LPDIRECT3DDEVICE9 pd3dDevice );
};
class CMyD3DApplication中有关粒子的成员:
// Particle stuff
LPDIRECT3DTEXTURE9 m_pParticleTexture;
CParticleSystem* m_pParticleSystem;
DWORD m_dwNumParticlesToEmit;
DWORD m_dwParticleColor;
BOOL m_bAnimateEmitter;
BYTE m_bKey[256];
BOOL m_bDrawReflection;
BOOL m_bCanDoAlphaBlend;
BOOL m_bDrawHelp;
CMyD3DApplication::CMyD3DApplication()中:(用于粒子结构中某些变量的初始化)
m_pParticleSystem = new CParticleSystem( 512, 2048, 0.03f );
m_pParticleTexture = NULL;
m_dwNumParticlesToEmit = 10;
m_bAnimateEmitter = FALSE;
m_dwParticleColor = COLOR_WHITE;
m_bDrawReflection = FALSE;
m_bCanDoAlphaBlend = FALSE;
m_bDrawHelp = FALSE;
CMyD3DApplication::InitDeviceObjects()中:
if( FAILED( D3DUtil_CreateTexture( m_pd3dDevice, _T("Particle.bmp"),
&m_pParticleTexture ) ) ) //创建了所使用了纹理
return D3DAPPERR_MEDIANOTFOUND;
//查询是否能够使用Alpha混合
m_bCanDoAlphaBlend = (m_d3dCaps.SrcBlendCaps & D3DPBLENDCAPS_SRCALPHA) &&
(m_d3dCaps.DestBlendCaps & D3DPBLENDCAPS_INVSRCALPHA);
if( m_bCanDoAlphaBlend )
m_bDrawReflection = TRUE;
CMyD3DApplication::RestoreDeviceObjects()中:
// Initialize the particle system
if( FAILED( hr = m_pParticleSystem->RestoreDeviceObjects( m_pd3dDevice ) ) )
return hr;
CMyD3DApplication::FrameMove()中:
HRESULT CMyD3DApplication::FrameMove()
{
// Slow things down for the REF device如果使用REF,将速度减慢
if( m_d3dCaps.DeviceType == D3DDEVTYPE_REF )
m_fElapsedTime = 0.05f;
// Determine emitter position确定发射位置
D3DXVECTOR3 vEmitterPostion( 0.0f, 0.0f, 0.f );
if( m_bAnimateEmitter ) //如果为动态发射,发射位置为圆形轨道
vEmitterPostion = D3DXVECTOR3( 3*sinf(m_fTime), 0.0f, 3*cosf(m_fTime) );
// Update particle system更新粒子系统
m_pParticleSystem->Update(m_fElapsedTime, m_dwNumParticlesToEmit, g_clrColor[m_dwParticleColor],
g_clrColorFade[m_dwParticleColor], 8.0f, vEmitterPostion );
return S_OK;
}
CMyD3DApplication::Render()中:
// Draw reflection of particles渲染反射粒子
if( m_bDrawReflection ) //如果支持alpha混合,才能够渲染反射粒子
{
D3DXMATRIXA16 matReflectedView;
D3DXMatrixReflect( &matReflectedView, &m_planeGround );
D3DXMatrixMultiply( &matReflectedView, &matReflectedView, &m_matView );
m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE );
m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE );
m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );
m_pd3dDevice->SetTransform( D3DTS_VIEW, &matReflectedView );
m_pd3dDevice->SetTexture( 0, m_pParticleTexture );
m_pParticleSystem->Render( m_pd3dDevice );
m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );//不使用alpha混合
}
// Draw the ground
if( m_bDrawReflection )
{
m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );//打开alpha混合
m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
}
m_pd3dDevice->SetTransform( D3DTS_VIEW, &m_matView );
m_pd3dDevice->SetTexture( 0, m_pGroundTexture );
m_pd3dDevice->SetFVF( COLORVERTEX::FVF );
m_pd3dDevice->SetStreamSource( 0, m_pGroundVB, 0, sizeof(COLORVERTEX) );
m_pd3dDevice->SetIndices( m_pGroundIB );
m_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, m_dwNumGroundVertices, 0,
m_dwNumGroundIndices/3 );
// Draw particles
m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE );
m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE );
m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );
m_pd3dDevice->SetTexture(0, m_pParticleTexture );
m_pParticleSystem->Render( m_pd3dDevice );
m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
CParticleSystem类的实现:
CParticleSystem::CParticleSystem( DWORD dwFlush, DWORD dwDiscard, float fRadius ) //512, 2048, 0.03
{
m_fRadius = fRadius; //粒子的半径,初始值为0.03
m_dwBase = dwDiscard; //顶点缓冲区的基值,初始值为2048
m_dwFlush = dwFlush; //顶点缓冲区的块的大小,初始值为512
m_dwDiscard = dwDiscard; //顶点缓冲区的大小限制,初始值为2048
m_dwParticles = 0; //当前粒子数,初始值为0
m_dwParticlesLim = 2048; //最多粒子数
m_pParticles = NULL;//活动粒子链表
m_pParticlesFree = NULL; //消亡粒子链表
m_pVB = NULL;
}
CParticleSystem::~CParticleSystem()
{
InvalidateDeviceObjects();
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;
}
}
HRESULT CParticleSystem::RestoreDeviceObjects( LPDIRECT3DDEVICE9 pd3dDevice )
{
HRESULT hr;
// Create a vertex buffer for the particle system. The size of this buffer
// does not relate to the number of particles that exist. Rather, the
// buffer is used as a communication channel with the device. we fill in
// a bit, and tell the device to draw. While the device is drawing, we
// fill in the next bit using NOOVERWRITE. We continue doing this until
// we run out of vertex buffer space, and are forced to DISCARD the buffer
// and start over at the beginning.
//为粒子系统创建顶点缓冲区。该缓冲区的大小与已存在粒子的数目无关。
//该缓冲区被用作与设备的沟通通道。我们填写一部分后,告知设备进行渲染。
//当设备进行渲染时,我们通过使用NOOVERWRITE再填写下一部分。
//我们持续这样的过程。直至用完顶点缓冲区的空间,随后强迫DISCARD缓冲区并从新开始。
if(FAILED(hr = pd3dDevice->CreateVertexBuffer( m_dwDiscard *
sizeof(POINTVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY | D3DUSAGE_POINTS,
POINTVERTEX::FVF, D3DPOOL_DEFAULT, &m_pVB, NULL )))
{
return E_FAIL;
}
return S_OK;
}
HRESULT CParticleSystem::InvalidateDeviceObjects()
{
SAFE_RELEASE( m_pVB );
return S_OK;
}
HRESULT CParticleSystem::Update( FLOAT fSecsPerFrame, //传入值为0.05
DWORD dwNumParticlesToEmit,//传入值为10
const D3DXCOLOR &clrEmitColor,//白色
const D3DXCOLOR &clrFadeColor, //白色
float fEmitVel, //8.0
D3DXVECTOR3 vPosition )
{
PARTICLE *pParticle, **ppParticle;
static float fTime = 0.0f;
fTime += fSecsPerFrame; //程序运行时间
ppParticle = &m_pParticles; //处理活动粒子链表
while( *ppParticle )
{
pParticle = *ppParticle;
// Calculate new position计算粒子新位置
float fT = fTime - pParticle->m_fTime0; //粒子已生存时间
float fGravity;
if( pParticle->m_bSpark ) //如果粒子为火星,受重力影响小,消亡快
{
fGravity = -5.0f;
pParticle->m_fFade -= fSecsPerFrame * 2.25f;// 粒子生成时,m_fFade=1.0
}
Else //如果粒子为火花,受重力影响大,消亡慢
{
fGravity = -9.8f;
pParticle->m_fFade -= fSecsPerFrame * 0.25f;
}
pParticle->m_vPos = pParticle->m_vVel0 * fT + pParticle->m_vPos0; //粒子位置
pParticle->m_vPos.y += (0.5f * fGravity) * (fT * fT); //粒子受重力影响后的位置
pParticle->m_vVel.y = pParticle->m_vVel0.y + fGravity * fT; //粒子y方向上的速度
if( pParticle->m_fFade < 0.0f )
pParticle->m_fFade = 0.0f;
// Kill old particles去除老粒子(如果粒子为火花且y坐标小于粒子半径,或粒子为火星并已消亡)
if( pParticle->m_vPos.y < m_fRadius ||
pParticle->m_bSpark && pParticle->m_fFade <= 0.0f )
{
// Emit sparks如果粒子为火花,则由火花变为火星
if( !pParticle->m_bSpark )
{
for( int i=0; i<4; i++ ) //一个火花生成4个火星
{
PARTICLE *pSpark;
if( m_pParticlesFree ) //查看消亡粒子链表中是否有粒子
{
pSpark = m_pParticlesFree;
m_pParticlesFree = pSpark->m_pNext;
}
Else //如果没有则生成新粒子
{
if( NULL == ( pSpark = new PARTICLE ) )
return E_OUTOFMEMORY;
}
//将火星插入到活动粒子链表
pSpark->m_pNext = pParticle->m_pNext;
pParticle->m_pNext = pSpark;
//设置火星初始值
pSpark->m_bSpark = TRUE;
pSpark->m_vPos0 = pParticle->m_vPos;
pSpark->m_vPos0.y = m_fRadius;
FLOAT fRand1 = ((FLOAT)rand()/(FLOAT)RAND_MAX) * D3DX_PI * 2.00f;
FLOAT fRand2 = ((FLOAT)rand()/(FLOAT)RAND_MAX) * D3DX_PI * 0.25f;
//计算火星初始速度
pSpark->m_vVel0.x = pParticle->m_vVel.x * 0.25f + cosf(fRand1) * sinf(fRand2);
pSpark->m_vVel0.z = pParticle->m_vVel.z * 0.25f + sinf(fRand1) * sinf(fRand2);
pSpark->m_vVel0.y = cosf(fRand2);
pSpark->m_vVel0.y *= ((FLOAT)rand()/(FLOAT)RAND_MAX) * 1.5f;
//最初火星的当前位置与速度
pSpark->m_vPos = pSpark->m_vPos0;
pSpark->m_vVel = pSpark->m_vVel0;
D3DXColorLerp( &pSpark->m_clrDiffuse, &pParticle->m_clrFade,
&pParticle->m_clrDiffuse, pParticle->m_fFade );
pSpark->m_clrFade = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f);
pSpark->m_fFade = 1.0f;
pSpark->m_fTime0 = fTime;
}
}
// Kill particle将该粒子从活动粒子链表中删除,添加到消亡粒子链表
*ppParticle = pParticle->m_pNext;
pParticle->m_pNext = m_pParticlesFree;
m_pParticlesFree = pParticle;
if(!pParticle->m_bSpark)
m_dwParticles--;
}
else //如果不需要去除该粒子,则指向下一粒子
{
ppParticle = &pParticle->m_pNext;
}
}
// Emit new particles产生新粒子(不超过m_dwParticlesLim时,增加dwNumParticlesToEmit个粒子)
DWORD dwParticlesEmit = m_dwParticles + dwNumParticlesToEmit;
while( m_dwParticles < m_dwParticlesLim && m_dwParticles < dwParticlesEmit )
{
if( m_pParticlesFree )
{
pParticle = m_pParticlesFree;
m_pParticlesFree = pParticle->m_pNext;
}
else
{
if( NULL == ( pParticle = new PARTICLE ) )
return E_OUTOFMEMORY;
}
pParticle->m_pNext = m_pParticles;
m_pParticles = pParticle;
m_dwParticles++;
// Emit new particle发射新粒子
FLOAT fRand1 = ((FLOAT)rand()/(FLOAT)RAND_MAX) * D3DX_PI * 2.0f;
FLOAT fRand2 = ((FLOAT)rand()/(FLOAT)RAND_MAX) * D3DX_PI * 0.25f;
pParticle->m_bSpark = FALSE;
pParticle->m_vPos0 = vPosition + D3DXVECTOR3( 0.0f, m_fRadius, 0.0f );
pParticle->m_vVel0.x = cosf(fRand1) * sinf(fRand2) * 2.5f;
pParticle->m_vVel0.z = sinf(fRand1) * sinf(fRand2) * 2.5f;
pParticle->m_vVel0.y = cosf(fRand2);
pParticle->m_vVel0.y *= ((FLOAT)rand()/(FLOAT)RAND_MAX) * fEmitVel;
pParticle->m_vPos = pParticle->m_vPos0;
pParticle->m_vVel = pParticle->m_vVel0;
pParticle->m_clrDiffuse = clrEmitColor;
pParticle->m_clrFade = clrFadeColor;
pParticle->m_fFade = 1.0f;
pParticle->m_fTime0 = fTime;
}
return S_OK;
}
//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Renders the particle system using either pointsprites (if supported)
// or using 4 vertices per particle
//-----------------------------------------------------------------------------
HRESULT CParticleSystem::Render( LPDIRECT3DDEVICE9 pd3dDevice )
{
HRESULT hr;
// Set the render states for using point sprites设置点精灵的渲染状态
pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE );
pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE, TRUE );
pd3dDevice->SetRenderState( D3DRS_POINTSIZE, FtoDW(0.08f) );
pd3dDevice->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW(0.00f) );
pd3dDevice->SetRenderState( D3DRS_POINTSCALE_A, FtoDW(0.00f) );
pd3dDevice->SetRenderState( D3DRS_POINTSCALE_B, FtoDW(0.00f) );
pd3dDevice->SetRenderState( D3DRS_POINTSCALE_C, FtoDW(1.00f) );
// Set up the vertex buffer to be rendered建立渲染用顶点缓冲区
pd3dDevice->SetStreamSource( 0, m_pVB, 0, sizeof(POINTVERTEX) );
pd3dDevice->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.
//锁定顶点缓冲区,通过使用D3DLOCK_NOOVERWRITE,我们将一块块的填充顶点缓存区。
//我们填充完一块后,调用DrawPrimitive,并锁定下一块缓冲区。
//当我们用完顶点缓冲区内的空间后,通过使用D3DLOCK_DISCARD,重新开始。
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;
}
// 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;
vVel *= -0.04f / (FLOAT)dwSteps; //渲染位置的改变量
//通过插值计算粒子颜色
D3DXCOLOR clrDiffuse;
D3DXColorLerp(&clrDiffuse, &pParticle->m_clrFade, &pParticle->m_clrDiffuse, pParticle->m_fFade);
DWORD dwDiffuse = (DWORD) clrDiffuse;
// 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=pd3dDevice->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.
//锁定下一块顶点缓冲区,如果我们处于顶点缓冲区的末尾,DISCARD该顶点缓冲区,并重新开始。//否则,指定为NOOVERWRITE,在描绘前一块缓冲区后,我们可以继续填充顶点缓冲区。
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 = pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, m_dwBase, dwNumParticlesToRender )))
return hr;
}
// Reset render states重新设置渲染状态
pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE );
pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE, FALSE );
return S_OK;
}