【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 3272|回复: 1

基于DIRECTX的3D粒子系统构建 (2) 火焰粒子系统

[复制链接]
发表于 2013-12-2 21:34:18 | 显示全部楼层 |阅读模式
这一节将学习如何生成一个接近真实火焰效果的粒子系统。火焰是常见的一种特效,在大多数游戏中都可以看到。但是要做好一个火焰效果却并不简单。在3D游戏的起步阶段,游戏的火焰大部分都是靠纹理贴图来实现的。用粒子实现的火焰非常接近真实火焰产生的视觉效果,如5-12所示。
图片1.png
火焰粒子效果
在雪花飘落的例子中,因为雪花本身需要旋转落下,所以使用的是面片。在火焰的例子中,并不需要火焰粒子的自身旋转,考虑到这个特性,所以在这里可以用点精灵来实现。
5.3.1点精灵
粒子在3D场景中采用点精灵(Point Sprite)渲染时,其单元类型为D3DPT_POINTLIST。显然,像素点太小,很少直接用于表现任何物体。常常需要借助纹理,并适当设置点的大小,单个的带纹理渲染的点可以在场景中显示为一个面向摄像机的面,因为不用处理大量的模型网格,能够快速地渲染。
点精灵是使用点类型的原始单元(D3DPT_POINTLIST)进行渲染的技术。点类型的原始单元可以有一定的大小、使用完整的纹理。点由纹理填充的矩形代替,同时矩形大小可以根据点的位置变化。点的大小是根据应用程序指定的大小和到观察点的距离计算得到的,应用程序可以指定每个点精灵的大小,也可以通过设置渲染状态D3DRS_POINTSIZE为所有点精灵指定相同的大小。点的大小在视图空间计算,除非点被定义成已转换的格式(D3DFVF_XYZRHW),这时顶点将被定义为1个像素,也不需要再根据点精灵到观察点的距离重新计算其大小。
当设置D3DRS_POINTSPRITEENABLE渲染状态为true时,顶点将使用纹理进行填充。当然,只有点的大小大于1个像素才有意义。D3DRS_POINTSPRITEENABLE渲染状态为false时,每个点使用自身的纹理坐标进行寻址渲染,点被渲染为1个像素。
  1. <p>//设置渲染属性
  2. m_pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE );    //启用点精灵
  3. m_pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE,  TRUE );  //点大小的计算方式
  4. m_pd3dDevice->SetRenderState( D3DRS_POINTSIZE,     FtoDW(m_fSize) ); //设置点的大小
  5. m_pd3dDevice->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW(1.0f) );    //点的最小值</p><p>m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_A,  FtoDW(0.0f) );   //3个系数(A)
  6. m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_B,  FtoDW(0.0f) );   //3个系数(B)
  7. m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_C,  FtoDW(1.0f) );   //3个系数(C)</p>
复制代码
D3DRS_POINTSPRITEENABLE整个当前纹理会映射到Point Sprite上。
D3DRS_POINTSCALEENABLE设置点精灵大小的计算方式。
D3DRS_POINTSIZE指定Point Sprite的大小。
D3DRS_POINTSIZE_MIN指定Point Sprite的大小可以取到的最小值。
D3DRS_POINTSCALE_A、D3DRS_POINTSCALE_B、D3DRS_POINTSCALE_C这3个常量决定了Point Sprite的大小怎样随着离摄像机的远近而变化。
点的大小由渲染状态D3DRS_POINTSCALEENABLE决定。如果该渲染状态设置为FALSE,则在程序中指顶点的大小直接作为视口空间点的大小。如果渲染状态设置为TRUE时,则使用下面的公式来计算Point Sprite的最终大小:
QQ图片20131202213718.jpg
在该公式中:
FinalSiz计算后的最终Point Sprite大小。
ViewportHeight视口Viewport的高度。
Size在D3DRS_POINTSIZE中指定的大小。
A,B,C分别由类型D3DRS_POINTSCALE_A、D3DRS_POINTSCALE_BD3DRS_POINTSCALE_C指定。
D表示Point Sprite在视图空间中距摄像机的距离。因为摄像机在视图空间的原点,所以这个值是:
QQ图片20131202213718.jpg
上面公式中参数xyz是视图空间中的Point Sprite的位置。
下面的代码设置了ABC三个常量,这样Point Sprite会随着与摄像机的距离的增加而变小:
  1. m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_A,  FtoDW(0.0f) );    //3个系数
  2. m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_B,  FtoDW(0.0f) );    //3个系数
  3. m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_C,  FtoDW(1.0f) );    //3个系数
复制代码
在以上代码中,函数FtoDW用于将浮点数的4个字节以无符号整数的形式传递给函数。函数SetRenderState第二个参数是整数,如果直接将浮点数作为参数,将会导致错误,因为此函数仍然会将这4个字节当作浮点数使用,可是已经经过了强制类型转换。此函数的定义为:
  1. inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); }
复制代码
5.1.1 火焰粒子数据结构
在这个粒子的结构体中包含了一个粒子系统的一些基本数据。
  1. struct CParticle
  2. {
  3.     float m_fWidth;                    //宽度
  4.     float m_fHeight;                    //高度
  5.     bool m_bOwnDimension;            //是否可控制粒子大小
  6.     DWORD m_Color;                 //当前颜色(通常指顶点颜色)
  7.     D3DXVECTOR3 m_vPosition;        //位置
  8.     D3DXVECTOR3 m_vDirection;       //方向(有长度的方向,包含速度)
  9.     float m_fTimeToLive;                //剩余生命
  10.     float m_fTotalTimeToLive;            //生命周期
  11.     float m_fRotation;                   //当前的旋转弧度
  12.     float m_fSpeed;                     //速度
  13. };
复制代码
粒子的基本属性包括了位置信息,颜色,朝向以及生命周期。为了让粒子既可以使用点精灵方式也可以使用公告板方式实现,所以,在结构体中依然包含了粒子的宽高以及大小可控的属性。
m_fWidth、m_fHeight如果是公告板粒子的话就会需要设置宽高。
m_vPosition在世界坐标系下的粒子位置。
m_fTotalTimeToLive粒子存活期,例如可以在经过一个给定的时间后销毁激光束的粒子。
m_fTimeToLive粒子剩余的生存期。
m_Color粒子的颜色。
m_fSpeed粒子的速度,以“单位每秒”来描述。
5.1.1 火焰粒子数据管理
在生成雪花的实例中,粒子没有生成和消亡的概念,这些雪花粒子在运动过程中改变自己的坐标,当落到“地面”的时候,位置被重新置回到起始点。在即将介绍的火焰案例中真正用到了粒子生命的概念,在系统中粒子是需要产生与销毁的,这就要用到粒子池技术。在火焰粒子系统中,使用到了两个池:存活粒子池和消亡粒子池。在程序中用两个链表表示这两个池:
  1. typedef std::list<CParticle*> ParticleActiveList;      //定义存活粒子链表类型
  2. typedef std::list<CParticle*> ParticleFreeList;        //定义空闲粒子链表类型
  3. ParticleActiveList m_ActiveParticles;              //粒子池中已被使用的粒子对象
  4. ParticleFreeList m_FreeParticles;                  //粒子池中未被使用的粒子对象
复制代码
要使用点精灵,必须用到点精灵的顶点缓冲区:
  1. void CFirePartile::CreateBuffers()
  2. {    //创建点精灵粒子顶点缓冲区
  3.     if( FAILED( m_pd3dDevice->CreateVertexBuffer(
  4.         m_uiMaxNumVertices * sizeof(CUSTOMVERTEX_PARTICLE),
  5.         D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY | D3DUSAGE_POINTS,
  6.         D3DFVF_CUSTOMEVERTEX_PARTICLE,
  7.         D3DPOOL_DEFAULT,
  8.         &m_pVB, NULL )))
  9.     {
  10.         MessageBox(NULL,"顶点缓冲创建失败!","错误",MB_OK);
  11.     }
  12. }
复制代码
函数CreateBuffers()用于创建顶点缓冲区,这里有以下几点需要说明:
1)  由于创建的顶点缓冲区用于渲染点精灵,所以在调用CreateVertexBuffer函数创建顶点缓冲区时,需要在函数的第二个参数中添加D3DUSAGE_POINTS标志予以指明。
1)  由于渲染粒子系统需要不断地锁定粒子系统的顶点缓冲区,并根据每个粒子的具体属性数据填充顶点缓冲区。为了提高性能,在调用CreateVertexBuffer函数创建顶点缓冲区时,在函数的第二个参数中添加D3DUSAGE_WRITEONLY使用标志。
2)  在后面的渲染函数Render()中,锁定顶点缓冲区填充粒子数据时,为了提高程序的运行性能,采用丢弃内存数据的方式提高运算速度。使用了D3DLOCK_DISCARD加锁标志,所以在创建顶点缓冲区时,添加D3DUSAGE_DYNAMIC标志,才能使D3DLOCK_DISCARD加锁标志有效。
3)  由于创建顶点缓冲区时使用D3DUSAGE_DYNAMIC标志,所以在创建顶点缓冲区时就不能使用管理内存,不能将函数CreateVertexBuffer中的pool参数设置为D3DPOOL_MANAGRD,而只能设置D3DPOOL_SYSTEMMEMD3DPOOL_DEFAULT。考虑到程序运行性能,选用了D3DPOOL_DEFAULT
注:关于D3DUSAGE_DYNAMIC、D3DUSAGE_WRITEONLY、D3DUSAGE_POINTS这3个标志具体细节请参考之前教材中的说明。
在粒子中需要的顶点格式只需要知道粒子的顶点坐标和颜色就足够了。
  1. struct CUSTOMVERTEX_PARTICLE
  2. {
  3.     D3DXVECTOR3  Position;        //位置
  4.     D3DCOLOR     color;           //颜色
  5. };
复制代码
相对应的灵活顶点格式应该写成:
  1. #define D3DFVF_CUSTOMEVERTEX_PARTICLE ( D3DFVF_XYZ | D3DFVF_DIFFUSE )
复制代码
系统刚刚启动的时候粒子还没有生成,所以粒子需要的空间都在粒子池中的空闲链表中,在开始的时候需要初始化一个大小足够使用的空闲链表,这个链表的大小根据实际需求具体情况而定。一个功能完善的粒子池,还具备可以动态根据需求扩充粒子池的功能。下面是初始化空闲链表的代码:
  1. //初始化粒子空闲链表
  2. for(int i=0; i<PARTICLENUM_FIRE; i++)
  3. {

  4.     CParticle * pPar =  new CParticle;
  5.     m_FreeParticles.push_back( pPar );
  6. }
复制代码
以上步骤完成了一个粒子系统基本环境的初始化。粒子系统的数据管理不仅仅是完成初始化,粒子的消亡和产生也是数据管理的一部分,而这一部分又往往和粒子的数据更新部分在一起。因此,要了解粒子的消亡和产生的过程,就需要在粒子的数据更新部分来处理。
5.1.1 火焰粒子数据的更新
存活链表m_ActiveParticles中保存的是当前活跃的粒子,也就是需要在屏幕上绘制的粒子数据。活跃的粒子内存是从空闲链表m_FreeParticles中获取的,当前活跃的粒子如果需要增加新的内存空间,首先会去检查空闲链表m_FreeParticles中是否有可用内存,如果m_FreeParticles中已经没有剩余的空间,则返回NULL。如果m_FreeParticles中还有空闲可被申请,就把这段空间插入到m_ActiveParticles中,并从m_FreeParticles中删除。
  1. CParticle*  CFirePartile::CreateParticle()
  2. {
  3.     //检查空闲链表是否为空
  4.     if( m_FreeParticles.empty() )
  5.     {
  6.         return NULL;
  7.     }
  8.     //从空闲链表中取出头一个,放入存活链表
  9.     CParticle *p = m_FreeParticles.front();
  10.   //使用到std::list的splice方法,功能:将源链表中的指定数据插入目标链表的指定位置,
  11.   //并从源链表中删除该数据
  12.     m_ActiveParticles.splice( m_ActiveParticles.end(), m_FreeParticles, m_FreeParticles.begin() );
  13.     //取随机数给粒子速度,生命
  14.     p->m_fSpeed = GetRandomMinMax(m_fMinSpeed,m_fMaxSpeed);
  15.     p->m_fTotalTimeToLive    = GetRandomMinMax(m_fMinTTL,m_fMaxTTL);
  16.   //定义朝向为Y轴正方向
  17.     p->m_vDirection.x = 0.0f;            
  18.     p->m_vDirection.y = 1.0f;
  19.     p->m_vDirection.z = 0.0f;
  20.     return p;
  21. }
复制代码
GetRandomMinMax()函数的作用是从输入的最小数和最大数之间取一个随机数,函数原型如下:
  1. inline float GetRandomMinMax(float fMin,float fMax)
  2. {
  3.     float fRandNum = (float)rand() / RAND_MAX;
  4.     return fMin + (fMax - fMin) * fRandNum;
  5. }
复制代码
生命周期结束的粒子回收也是在Update()中进行的。当一个激活的粒子生命周期耗尽的时候,它就会从存活链表m_ActiveParticles中被删除,并保存到空闲链表m_FreeParticles中。
  1. //如果当前的粒子生命已经大于生命周期,则被移到空闲粒子链表
  2. ParticleActiveList::iterator iCur = m_ActiveParticles.begin();
  3. ParticleActiveList::iterator iEnd = m_ActiveParticles.end();
  4. for( ; iCur != iEnd; )
  5. {
  6.     if((*iCur)->m_fTotalTimeToLive < 0 )
  7.     {
  8.        iCur = DeleteParticle( iCur );    //删除到死亡链表,并返回下一个数据指针
  9.     }
  10.     else
  11.     {
  12.        (*iCur)->m_fTotalTimeToLive -= 1.0;    //递减存活时间
  13.        ++iCur;    //指向下一个粒子
  14.     }
  15. }
复制代码
其中DeleteParticle函数内容如下:
  1. CFirePartile::ParticleActiveList::iterator CFirePartile::DeleteParticle(CFirePartile::ParticleActiveList::iterator &i )
  2. {
  3.      //增加粒子到空闲链表
  4.      m_FreeParticles.push_back( *i );
  5.      //删除i位置的数据,传回下一个数据的位置
  6.      return m_ActiveParticles.erase( i );
  7. }
复制代码
空闲链表和存活链表的数据都计算完成以后,接下来就是内存数据的更新了,在火焰粒子中只关心粒子的位置信息,效果是通过纹理来表现的。所以在填充顶点缓冲区的时候,更新顶点的位置信息:
  1. //重新定向迭代器
  2. iCur = m_ActiveParticles.begin();
  3. iEnd = m_ActiveParticles.end();
  4. //更新缓冲区中粒子的位置属性
  5. if( FAILED( m_pVB->Lock(0, //给偏移加锁
  6.     PARTICLENUM_FIRE * sizeof(CUSTOMVERTEX_PARTICLE),    //给大小加锁
  7.   (void**) &pVertices, D3DLOCK_DISCARD)))
  8. {
  9.     return;
  10. }
  11.     for( ; iCur != iEnd; iCur++)
  12.     {
  13.     //更新每个粒子的当前位置
  14.      (*iCur)->m_vPosition += (fElapsedTime*(*iCur)->m_fSpeed)*(*iCur)->m_vDirection;
  15.      pVertices->Position  = (*iCur)->m_vPosition;
  16.      pVertices->color =D3DCOLOR_XRGB(255,255,255);
  17.      pVertices++;
  18.      }
  19. m_pVB->Unlock();
复制代码
5.1.1 火焰粒子的渲染
火焰粒子的渲染主要就是点精灵的渲染:首先是点精灵渲染属性,D3DRS_POINTSPRITEENABLE启用点精灵,D3DRS_POINTSIZE设置点精灵的大小,D3DRS_POINTSIZE_MIN设置最小纹理值,然后是设置ABC三个常量。
一般而言,粒子都是通过纹理表现形状的,所以,在渲染它的时候Alpha混合应该是打开的。通常在粒子渲染的时候不会开启灯光运算,所以D3DRS_LIGHTING是关闭的。深度写入也是关闭的,深度测试(Z-Test)一般是开启的。
  1. void CFirePartile::Render()
  2. {
  3.     //设置渲染属性
  4.     m_pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE );   //启用点精灵
  5.     m_pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE,  TRUE );  //点大小的计算方式
  6.     m_pd3dDevice->SetRenderState( D3DRS_POINTSIZE,     FtoDW(m_fSize) ); //设置点的大小
  7.     m_pd3dDevice->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW(1.0f) );   //点的最小值表现
  8.     m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_A,  FtoDW(0.0f) );  //3个系数
  9.     m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_B,  FtoDW(0.0f) );  //
  10.     m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_C,  FtoDW(1.0f) );  //
  11.   //半透明效果
  12.     m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
  13.     m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );
  14.     //禁用照明效果
  15.     m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE);
  16.     m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE );
  17.     //..........渲染开始.............
  18.     if( m_ActiveParticles.size() )
  19.     {
  20.         m_pd3dDevice->SetStreamSource( 0, m_pVB, 0, sizeof(CUSTOMVERTEX_PARTICLE) );
  21.         m_pd3dDevice->SetFVF( D3DFVF_CUSTOMEVERTEX_PARTICLE );
  22.         m_pd3dDevice->SetTexture(0,m_pTexture);
  23.         m_pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, 0, m_ActiveParticles.size());
  24.     }
  25.     //..........渲染结束.............
  26.     //恢复渲染状态...
  27.     m_pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE );
  28.     m_pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE,  FALSE );
  29.     m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
  30. }
复制代码
D3D渲染粒子时,仍然用DrawPrimitive函数绘制,这里应该使用的类型为D3DPT_POINTLIST。
  1. m_pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, 0, m_ActiveParticles.size());
复制代码
 楼主| 发表于 2013-12-2 21:46:25 | 显示全部楼层
【实例4-2】火焰粒子类
下面是这个例题中完整的火焰粒子类的代码。
a 火焰粒子类头文件
  1. //=============================================================================
  2. //Desc: CFirePartile.h
  3. //=============================================================================
  4. #pragma once
  5. #include "d3dx9.h"
  6. #include "Utility.h"
  7. #include <vector>
  8. #include <list>
  9. using namespace std;
  10. #define  PARTICLENUM_FIRE  30
  11. //顶点结构
  12. struct CUSTOMVERTEX_PARTICLE
  13. {
  14.     D3DXVECTOR3  Position;        //位置
  15.     D3DCOLOR     color;           //颜色
  16. };
  17. #define D3DFVF_CUSTOMEVERTEX_PARTICLE ( D3DFVF_XYZ | D3DFVF_DIFFUSE )
  18. struct CParticle
  19. {
  20.     float m_fWidth;                    //宽度
  21.     float m_fHeight;                   //高度
  22.     bool m_bOwnDimension;            //是否可控制粒子大小
  23.     DWORD m_Color;                 //当前颜色(通常指顶点颜色)
  24.     D3DXVECTOR3 m_vPosition;       //位置
  25.     D3DXVECTOR3 m_vDirection;      //方向(有长度的方向,包含速度)
  26.     float m_fTimeToLive;               //剩余生命
  27.     float m_fTotalTimeToLive;           //生命周期
  28.     float m_fRotation;                  //当前的旋转弧度
  29.     float m_fSpeed;                    //速度
  30. };
  31. class CFirePartile
  32. {
  33. public:
  34.     CFirePartile(void);
  35.     ~CFirePartile(void);
  36.     typedef std::list<CParticle*> ParticleActiveList;    //定义存活粒子链表类型
  37.     typedef std::list<CParticle*> ParticleFreeList;      //定义空闲粒子链表类型
  38.     ParticleActiveList m_ActiveParticles;            //粒子池中已被使用的粒子对象
  39.     ParticleFreeList m_FreeParticles;                //粒子池中未被使用的粒子对象
  40.   UINT m_uiMaxNumVertices;
  41.     HRESULT Init( LPDIRECT3DDEVICE9 pDevice ); //初始化
  42.     void Update( float fElapsedTime); //更新粒子
  43.     void Render(); //设置合适的渲染状态,渲染粒子
  44. protected:
  45.     LPDIRECT3DVERTEXBUFFER9     m_pVB;            //顶点缓冲区
  46.     LPDIRECT3DTEXTURE9           m_pTexture;         //纹理对象   
  47.     LPDIRECT3DDEVICE9             m_pd3dDevice ;      //设备
  48.     float        m_fSize;                      //粒子火焰大小
  49.     bool        m_bDeviceSupportsPSIZE;       //是否支持点精灵改变大小
  50.     //粒子的生命周期由“最小生命值”和“最大生命值”之间的随机值决定
  51.     float        m_fMinTTL;                   //最小生命值
  52.     float        m_fMaxTTL;                   //最大生命值   
  53.     //粒子的速率由“最小速度”和“最大速度”之间的随机值决定
  54.     float m_fMinSpeed;   
  55.     float m_fMaxSpeed;
  56.     //创建缓冲区   
  57.     void CreateBuffers();   
  58.     //创建粒子
  59.     CParticle*  CreateParticle();
  60.     ParticleActiveList::iterator DeleteParticle( ParticleActiveList::iterator &i );
  61.     //释放函数
  62.     void FreeData();
  63. };
复制代码
a 火焰粒子类源文件
  1. //=============================================================================
  2. //Desc: CFirePartile.cpp
  3. //=============================================================================
  4. #include ".\firepartile.h"
  5. CFirePartile::CFirePartile(void)
  6. {
  7.     m_uiMaxNumVertices = 2048;
  8.     m_fSize= 5.0f;
  9.     m_fMinTTL = 10.0f;
  10.     m_fMaxTTL = 20.0f;
  11.     m_fMinSpeed = 6.f;
  12.     m_fMaxSpeed = 8.5f;
  13. }
  14. CFirePartile::~CFirePartile(void)
  15. {
  16.     FreeData();
  17. }
  18. void CFirePartile::FreeData()
  19. {
  20.   //释放激活链表中可能存在的数据
  21.     ParticleActiveList::iterator iACur = m_ActiveParticles.begin();
  22.     ParticleActiveList::iterator iAEnd = m_ActiveParticles.end();
  23.     for(; iACur!=iAEnd; iACur++)
  24.     {
  25.         SAFE_DELETE((*iACur));
  26.     }
  27.   //释放空闲链表中可能存在的数据
  28.     ParticleFreeList::iterator iFCur = m_FreeParticles.begin();
  29.     ParticleFreeList::iterator iFEnd = m_FreeParticles.end();
  30.     for(; iFCur!=iFEnd; iFCur++)
  31.     {
  32.         SAFE_DELETE((*iFCur));
  33.     }
  34.     m_ActiveParticles.clear();
  35.     m_FreeParticles.clear();
  36.     //释放顶点、索引缓冲区
  37.     SAFE_RELEASE( m_pVB );
  38.     //释放纹理
  39.     SAFE_RELEASE( m_pTexture );
  40. }
  41. HRESULT CFirePartile::Init( LPDIRECT3DDEVICE9 pDevice )
  42. {
  43.     if(pDevice)
  44.         m_pd3dDevice = pDevice;
  45.     //创建粒子
  46.     CreateBuffers();
  47.     //获取最大值
  48.     D3DCAPS9 d3dCaps;
  49.     m_pd3dDevice->GetDeviceCaps(&d3dCaps);
  50.     //检查是否支持点精灵粒子尺寸更改
  51.     if (d3dCaps.FVFCaps & D3DFVFCAPS_PSIZE)
  52.     {
  53.         m_bDeviceSupportsPSIZE = true;
  54.     }
  55.     else
  56.     {
  57.         m_bDeviceSupportsPSIZE = false;
  58.         return E_FAIL;
  59.     }
  60.     //创建火焰效果纹理
  61.     if( FAILED( D3DXCreateTextureFromFile( m_pd3dDevice, "fire.tga", &m_pTexture )))
  62.      return E_FAIL;
  63.     //初始化粒子空闲链表
  64.     for(int i=0; i<PARTICLENUM_FIRE; i++)
  65.     {
  66.         CParticle * pPar =  new CParticle;
  67.         m_FreeParticles.push_back( pPar );
  68.     }
  69.     return S_OK;
  70. }
  71. void CFirePartile::CreateBuffers()
  72. {
  73.     //创建点精灵粒子顶点缓冲区
  74.     if( FAILED( m_pd3dDevice->CreateVertexBuffer(
  75.         m_uiMaxNumVertices * sizeof(CUSTOMVERTEX_PARTICLE),
  76.         D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY | D3DUSAGE_POINTS,
  77.         D3DFVF_CUSTOMEVERTEX_PARTICLE,
  78.         D3DPOOL_DEFAULT,
  79.         &m_pVB, NULL )))
  80.     {
  81.         MessageBox(NULL,"顶点缓冲创建失败!","错误",MB_OK);
  82.     }
  83. }
  84. CParticle*  CFirePartile::CreateParticle()
  85. {
  86.     //检查空闲粒子链表是否为空
  87.     if( m_FreeParticles.empty() )
  88.     {
  89.         return NULL;
  90.     }
  91.     //从空闲链表中取出头一个,放入存活链表
  92.     CParticle *p = m_FreeParticles.front();
  93.   //使用到std::list的splice方法,功能:将源链表中的指定数据插入目标链表的指定位置
  94.   //并从源链表中删除该数据
  95.     m_ActiveParticles.splice( m_ActiveParticles.end(), m_FreeParticles, m_FreeParticles.begin() );
  96.     //给粒子一个随机速度,随机生命以及向上的方向
  97.     p->m_fSpeed = GetRandomMinMax(m_fMinSpeed,m_fMaxSpeed);
  98.     p->m_fTotalTimeToLive    = GetRandomMinMax(m_fMinTTL,m_fMaxTTL);
  99.     p->m_vDirection.x = 0.0f;
  100.     p->m_vDirection.y = 1.0f;
  101.     p->m_vDirection.z = 0.0f;
  102.     return p;
  103. }
  104. void CFirePartile::Update( float fElapsedTime)
  105. {
  106.     CUSTOMVERTEX_PARTICLE *pVertices;
  107.     //如果当前的粒子生命已经大于生命周期,则被移到空闲粒子链表
  108.     ParticleActiveList::iterator iCur = m_ActiveParticles.begin();
  109.     ParticleActiveList::iterator iEnd = m_ActiveParticles.end();
  110.     for( ; iCur != iEnd; )
  111.     {
  112.         if((*iCur)->m_fTotalTimeToLive < 0 )
  113.         {
  114.             iCur = DeleteParticle( iCur );    //删除到空闲链表,并返回下一个数据指针
  115.         }
  116.         else
  117.         {
  118.             (*iCur)->m_fTotalTimeToLive -= 1.0;    //递减存活时间
  119.             ++iCur;    //指向下一个粒子
  120.         }
  121.     }
  122.     //如果粒子数小于当前最大粒子数,则补充新的粒子
  123.     if(m_ActiveParticles.size()<PARTICLENUM_FIRE)
  124.     {
  125.         CParticle * pPar =  CreateParticle();
  126.         pPar->m_vPosition.x = GetRandomMinMax(1,2);
  127.         pPar->m_vPosition.y = GetRandomMinMax(0,0);
  128.         pPar->m_vPosition.z = GetRandomMinMax(0,1);
  129.     }
  130.     //重新定向迭代器
  131.     iCur = m_ActiveParticles.begin();
  132.     iEnd = m_ActiveParticles.end();
  133.     //更新缓冲区中粒子的位置属性
  134.     if( FAILED( m_pVB->Lock(0, //给偏移加锁
  135.         PARTICLENUM_FIRE * sizeof(CUSTOMVERTEX_PARTICLE),    //给大小加锁
  136.         (void**) &pVertices,
  137.         D3DLOCK_DISCARD)))
  138.     {
  139.         return;
  140.     }
  141.         for( ; iCur != iEnd; iCur++)
  142.         {
  143.         //更新每个粒子的当前位置
  144.         (*iCur)->m_vPosition += (fElapsedTime*(*iCur)->m_fSpeed)*(*iCur)->m_vDirection;
  145.         pVertices->Position  = (*iCur)->m_vPosition;
  146.        pVertices->color =D3DCOLOR_XRGB(255,255,255);
  147.         pVertices++;
  148.         }
  149.     m_pVB->Unlock();
  150. }
  151. //删除生命周期结束的粒子
  152. CFirePartile::ParticleActiveList::iterator CFirePartile::DeleteParticle( CFirePartile::ParticleActiveList::iterator &i )
  153. {
  154.         //增加粒子到空闲链表
  155.         m_FreeParticles.push_back( *i );
  156.         //删除i位置的数据,传回下一个数据的位置
  157.         return m_ActiveParticles.erase( i );
  158. }
  159. void CFirePartile::Render()
  160. {
  161.     //设置渲染属性
  162.     m_pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE );   //启用点精灵
  163.     m_pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE,  TRUE );  //点大小的计算方式
  164.     m_pd3dDevice->SetRenderState( D3DRS_POINTSIZE,   FtoDW(m_fSize) );  //设置点的大小
  165.     m_pd3dDevice->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW(1.0f) );   //点的最小值
  166.     m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_A,  FtoDW(0.0f) );  //3个系数
  167.     m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_B,  FtoDW(0.0f) );  
  168.     m_pd3dDevice->SetRenderState( D3DRS_POINTSCALE_C,  FtoDW(1.0f) );  
  169.      //半透明效果
  170.     m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
  171.     m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );
  172.     //禁用照明效果
  173.     m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE);
  174.     m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE );
  175.     //..........渲染开始.............
  176.     if( m_ActiveParticles.size() )
  177.     {
  178.         m_pd3dDevice->SetStreamSource( 0, m_pVB, 0, sizeof(CUSTOMVERTEX_PARTICLE) );
  179.         m_pd3dDevice->SetFVF( D3DFVF_CUSTOMEVERTEX_PARTICLE );
  180.         m_pd3dDevice->SetTexture(0,m_pTexture);
  181.         m_pd3dDevice->DrawPrimitive( D3DPT_POINTLIST, 0, m_ActiveParticles.size());
  182.     }
  183.     //..........渲染结束.............
  184.     //恢复渲染状态...
  185.     m_pd3dDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE );
  186.     m_pd3dDevice->SetRenderState( D3DRS_POINTSCALEENABLE, FALSE );
  187.     m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
  188. }
复制代码
a 项目主文件
下面是调用CFirePartile类的主文件:
  1. //=============================================================================
  2. //Desc:项目的主文件
  3. //=============================================================================
  4. #include <Windows.h>
  5. #include <mmsystem.h>
  6. #include <d3dx9.h>
  7. #include <strsafe.h>
  8. #include "FirePartile.h"
  9. //-----------------------------------------------------------------------------
  10. //全局变量
  11. //-----------------------------------------------------------------------------
  12. LPDIRECT3D9             g_pD3D       = NULL; //D3D接口
  13. LPDIRECT3DDEVICE9      g_pd3dDevice  = NULL; //D3D设备
  14. CFirePartile*               g_pFirePartile   =NULL;//火焰类指针
  15. //-----------------------------------------------------------------------------
  16. //Name: InitD3D()
  17. //Desc: 初始化D3D环境
  18. //-----------------------------------------------------------------------------
  19. HRESULT InitD3D( HWND hWnd )
  20. {
  21.     //创建D3D 对象
  22.     if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
  23.         return E_FAIL;
  24.     //初始化D3D 设备数据结构
  25.     D3DPRESENT_PARAMETERS d3dpp;
  26.     ZeroMemory( &d3dpp, sizeof(d3dpp) );
  27.     d3dpp.Windowed = TRUE;
  28.     d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
  29.     d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
  30.     //创建D3D设备
  31.     if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
  32.         D3DCREATE_SOFTWARE_VERTEXPROCESSING,
  33.         &d3dpp, &g_pd3dDevice ) ) )
  34.     {
  35.         return E_FAIL;
  36.     }
  37.     //生成火焰对象
  38.     g_pFirePartile = new CFirePartile;
  39.     //初始化
  40.     g_pFirePartile->Init(g_pd3dDevice);
  41.     //渲染状态关闭灯光
  42.     g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
  43.     return S_OK;
  44. }
  45. //-----------------------------------------------------------------------------
  46. //Name: Cleanup()
  47. //Desc: 释放所有申请的资源
  48. //-----------------------------------------------------------------------------
  49. VOID Cleanup()
  50. {
  51.     SAFE_DELETE(g_pFirePartile);
  52.     SAFE_RELEASE(g_pd3dDevice);
  53.     SAFE_RELEASE(g_pD3D);
  54. }
  55. void Update()
  56. {
  57.     static float fPreTime = static_cast<float>(timeGetTime());
  58.     float fCurrentTime = static_cast<float>(timeGetTime());
  59.     float fElapsedTime = (fCurrentTime - fPreTime)*0.001f;
  60.     g_pFirePartile->Update(fElapsedTime);
  61.     fPreTime = fCurrentTime;
  62. }
  63. //-----------------------------------------------------------------------------
  64. //Name: SetupMatrices()
  65. //Desc: 设置世界矩阵,视角变换矩阵和投影变换矩阵
  66. //-----------------------------------------------------------------------------
  67. VOID SetupMatrices()
  68. {
  69.     D3DXVECTOR3 vEyePt( 0.0f,1.0f,-15.0f );      //摄像机位置
  70.     D3DXVECTOR3 vLookatPt( 1.0f, 0.0f, 0.0f );    //观察点位置,前进后退,向左向右,向上向下
  71.     D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );      //摄像机正方向(本地坐标系)
  72.     D3DXMATRIXA16 matView;
  73.     D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
  74.     g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView ) ;
  75.   D3DXMATRIXA16 matProj;
  76.   //它说明物体如何随着距离而缩小
  77.   //参数定义了屏幕纵横比第四和第五个参数定义最近和最远剪切平面
  78.     D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f );
  79.     g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
  80. }
  81. //-----------------------------------------------------------------------------
  82. //Name: Render()
  83. //Desc: 渲染
  84. //-----------------------------------------------------------------------------
  85. VOID Render()
  86. {
  87.     //清屏
  88.     g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
  89.     Update();
  90.     //开始渲染
  91.     if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
  92.     {
  93.         //矩阵变换
  94.         SetupMatrices();
  95.         //渲染火焰
  96.         g_pFirePartile->Render();
  97.         //渲染结束
  98.         g_pd3dDevice->EndScene();
  99.     }
  100.     //翻转缓冲
  101.     g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
  102. }
  103. //-----------------------------------------------------------------------------
  104. //Name: MsgProc()
  105. //Desc: 消息处理函数
  106. //-----------------------------------------------------------------------------
  107. LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
  108. {
  109.     switch( msg )
  110.     {
  111.     case WM_DESTROY:
  112.         Cleanup();
  113.         PostQuitMessage( 0 );
  114.         return 0;
  115.     }
  116.     return DefWindowProc( hWnd, msg, wParam, lParam );
  117. }
  118. //-----------------------------------------------------------------------------
  119. //Name: WinMain()
  120. //Desc: Win32主程序
  121. //-----------------------------------------------------------------------------
  122. INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
  123. {
  124.     //注册窗口类
  125.     WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
  126.         GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
  127.         "D3D Tutorial", NULL };
  128.     RegisterClassEx( &wc );
  129.     //创建窗口
  130.     HWND hWnd = CreateWindow( "D3D Tutorial", "v5.0 粒子系统:火焰粒子效果",
  131.         WS_OVERLAPPEDWINDOW, 100, 100, 400, 400,
  132.         GetDesktopWindow(), NULL, wc.hInstance, NULL );
  133.     //初始化Direct3D
  134.     if( SUCCEEDED( InitD3D( hWnd ) ) )
  135.     {
  136.                   //显示窗口
  137.             ShowWindow( hWnd, SW_SHOWDEFAULT );
  138.             UpdateWindow( hWnd );
  139.             //消息循环
  140.             MSG msg;
  141.             ZeroMemory( &msg, sizeof(msg) );
  142.             while( msg.message!=WM_QUIT )
  143.             {
  144.                 if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
  145.                 {
  146.                     TranslateMessage( &msg );
  147.                     DispatchMessage( &msg );
  148.                 }
  149.                 else
  150.                     Render();    //渲染
  151.             }
  152.     }
  153.     //注销窗口类
  154.     UnregisterClass( "D3D Tutorial", wc.hInstance );
  155.     return 0;
  156. }
复制代码
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2024-4-24 05:56

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表