DirectX学习笔记(十五):粒子系统实现

本文详细介绍了如何使用DirectX实现粒子系统,特别是针对雪花飞扬的粒子效果。从粒子系统的基本认知、原理出发,讨论了雪花粒子的特殊设计,包括不需要公告板技术和粒子的动态消亡。接着,文章提供了雪花粒子系统的实现细节,包括粒子属性结构体、顶点缓存对象和纹理加载。最后,展示了粒子系统在实际场景中的应用和效果,包括不同数量粒子对性能的影响,以及实际运行的截图。
摘要由CSDN通过智能技术生成
本系列文章由zhmxy555(毛星云)编写,转载请注明出处。  
文章链接:http://blog.csdn.net/zhmxy555/article/details/8744805
作者:毛星云(浅墨)    邮箱: happylifemxy@163.com

 

本篇文章中,我们将一起探讨三维游戏中粒子系统的方方面面,首先对粒子系统的基本概念特性做一个全面的认知,然后我们依旧是把粒子系统封装在一个C++类中,模拟了三维游戏中唯美的雪花飞扬的景象,让我们之前的综合三维游戏场景更加炫。依旧是放出一张本篇文章的配套程序的截图:



这个帅气的大天使为我们唯美的雪花飞扬示例程序平添了几分霸气有木有?

 PS:示例程序的源代码在文章末尾提供下载


大家应该记得,我们之前也用GDI实现过雪花粒子系统,那个时候由于图形库GDI的限制,实现效果或多或少显得有些拙劣,这篇文章中,我们在DirectX的帮助下,专门用粒子系统重新实现了唯美雪花的飞扬景象,算是在为强大的粒子系统正名吧。

 

 



一、对粒子系统的基本认知


1983年,奇才Reeves.V.T在它发表的论文《Particle Systems A Technique for Modeling a Class of Fuzzy Objects》中首次提出了粒子系统的概念。从此,粒子系统就开始广泛运用于计算机中各种模糊景物的模拟。经常使用粒子系统模拟的现象有火焰、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者像发光轨迹这样的抽象视觉效果等等。这些物体模型在计算机中往往很难用具体的形状、大小来描述,但是我们可以通过粒子系统的思想,去描述组成这些物体的每个元素和它的变化。

一般情况下,粒子的几何特征都十分的简单,可以采用一个像素或者是一个小的多边形来表示。需要注意的是,粒子系统的最大的缺陷是,当粒子数量达到很大的规模的时候,对运行时机器性能的要求会更加苛刻,如果机器的性能跟不上,就会显得达不到实时的运行效果,俗话说,就是粒子太多了,我们的电脑跑不动了,就会很卡。

在许多三维建模及渲染包内部就可以创建、修改粒子系统,如 3D Studio Max、Maya 以及 Blender 等。这些编辑程序使艺术家能够立即看到他们设定的特性或者规则下粒子系统的表现,另外还有一些插件能够提供增强的粒子系统效果,例如 AfterBurn 以及用于流体的 RealFlow。而2D的粒子特效软件中particleIllusion最为出色,因为他的渲染比一般的3D软件快较为平面化。Combustion 这样的多用途软件或者只能用于粒子系统的 Particle Studio 等都可以用来生成电影或者视频中的粒子系统。而目前,粒子系统技术被广泛用于大型3D游戏地制作中。首先看几张用粒子系统制作出来的效果图吧:



然后下图是DirectX SDK中自带sample一个和粒子系统相关的非常华丽的demo,推荐大家去运行一下,如果你的DirectX SDK安装在D盘,那么路径就是

D:\Program Files\Microsoft DirectXSDK (Jun 2010)\Samples\C++\Direct3D11\NBodyGravityCS11

放几张运行的截图:





粒子系统通常有三个要素:群体性、统一性和随机性。下面我们分别来简单看一下:

 

群体性:粒子系统是由大量的可见元素构成的。因此用粒子系统描述一团烟雾是合情合理的,但是我们所用粒子系统去描述一粒烟雾显然只有闹笑话了。

统一性:粒子系统的每个元素有具有相同的表现规律。比如,雪花粒子系统中的每一片雪花,都是白色无暇、轻盈灵动的。如果雪花粒子系统中出现了彩色的粒子,那显然就是异类了。

随机性粒子系统中每个元素又随机表现出不同的特性。比如烟雾中每一个烟雾粒子的运动轨迹往往都是杂乱无章的,但是对于整个烟雾系统来说,这些烟雾粒子往往都有一个大体的运动方向。



 


二、粒子系统的基本原理


 

粒子通常都是一个带有纹理的四边形。我们通过这个使用了纹理映射的四边形,可以认为粒子实际上是一个很小的网格模型,只不过是纹理赋予了它特殊的外表罢了。绘制粒子就如果绘制多边形一样简单,因为一个粒子说白了就是一个可改变大小并映射了纹理的四边形罢了。

下图就是一个20个单位大小的粒子。

 

如果给出了粒子中心点的坐标和粒子的大小,不难计算出绘制粒子所需要的4个顶点坐标,这样往往比直接给4个顶点坐标来得直观和节省空间。

另外,很多情况下,因为一个例子是使用两个三角形组成的一个矩形来表示的,所以通常需要使粒子四边形始终面向观察者,这就用到了我们在Direct3D中的广告板(Billboard)技术,也叫公告版技术。公告版技术的基本原理在这里也提一下吧,后面有机会就专门用一次更新来讲解。公告版技术的基本原理就是在渲染一个多边形时,首先根据观察方向构造一个旋转矩阵,利用这个旋转矩阵旋转多边形让这个多边形始终是面向观察者的,如果观察方向是不断变化的,那么我们这个旋转矩阵也要不断进行调节。这样,我们始终看到的是这个多边形“最美好”的一面。这样先让多边形面向观察者,然后再渲染的技术,就是传说中的广告板(Billboard)技术。

我们知道,粒子系统都由大量的粒子构成,每个粒子都有一组属性,如位置、大小以及纹理,还比如颜色、透明度、运动速度、加速度、自旋周期,生命周期等等属性。一个粒子需要具有什么样的属性,当然是取决于具体的运用了。

另外,粒子属性的初始值常常都是随机值,而粒子的产生也常常是由位于空间中某个位置的粒子源产生的。


粒子系统在宏观和微观上都是随时间不断变化的,一套粒子系统在它生命周期的每一刻,一般都需完成以下的四步曲的工作:

 

1.产生新的粒子

这一步当中,我们会根据预定的要求,产生一定数目的新粒子。粒子的各项初始属性都可以用rand函数来在一定的范围内赋上随机的值。


2.更新现有粒子的属性

比如粒子有位置和移动速度,自旋速度等等属性,这就需要在每一帧当中根据原来的粒子的位置、移动速度和自旋速度重新进行计算和赋值更新。


3.删除已经消亡的粒子

这一步是可选的,具体情况具体分析,因为有些粒子系统中粒子是一直都存在的,没有消亡一说。在规定了粒子生命周期的一套粒子系统中,就需要判断每个粒子是否生命走到了尽头,如果是的话,那么它就game over,消亡了,得用相关代码把它从粒子系统中消除。


4.绘制出粒子

这步没有的话什么都不是,不显示出来叫什么粒子系统啊。人家可不管你在之前做了多少工作,算了多少东西,反正玩家是要看到最终的显示效果的。

 

在Direct3D 8.0以后,我们可以通过一种称为点精灵(Point Sprite)的特殊点元来描述粒子系统中的粒子。和一般点元不同的是,点精灵可以进行纹理映射并改变大小。点精灵的使用常常是伴随着SetRenderState中第一个参数取如下的几个值:

  D3DRS_POINTSIZE                    = 154,
  D3DRS_POINTSIZE_MIN                = 155,
  D3DRS_POINTSPRITEENABLE            = 156,
  D3DRS_POINTSCALEENABLE             = 157,
  D3DRS_POINTSCALE_A                 = 158,
  D3DRS_POINTSCALE_B                 = 159,
  D3DRS_POINTSCALE_C                 = 160,

另外,粒子系统中的一个重要要素是保存粒子的存储结构。我们可以用数组,如果需要动态插入和删除原始的话,就进一步使用链表用或者模板了。

需要注意的是,因为粒子系统中会有很多粒子需要不断地产生、消亡,如果在每个粒子产生时都分配内存,或者在每个粒子消亡时都释放内存,这显然会造成巨大的资源开销,非常不推荐。这里我们按链表这种方案来讲解。我们通常采用的做法是,未雨绸缪,预先为所有的粒子分配内存,并将这些粒子保存到一个链表当中。当需要产生新的粒子时,从这个链表中取出所需数量的粒子,并将它们加入到渲染链表中,而当一个粒子消亡后,重新将它们放回到原链表中,并从渲染链表中删除这些粒子。最后,在程序结束时,统一一次性释放所有粒子所占的内存空间。这就是比较科学的做法。

呼,不讲概念了,太闷了,开始准备动手做些东西吧。

 

 



三、雪花粒子系统的设计

 

 

我们之前已经提到过,粒子系统可以模拟很多的现象,比如火焰、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者发光轨迹。对于现象的模拟,粒子的特性往往需要根据模拟的现象的属性来具体地设计。


对于我们今天要用粒子系统来模拟的雪花飞扬场景,有两个比较特殊的地方:

1.在雪花飞扬场景中,不需要用点精灵或者公告版技术来让粒子的四个顶点所在的面始终朝向观察者,因为雪花飞舞起来是非常优雅的,会悠扬地绕着不同的轴打转,用了公告板技术反而画蛇添足,显得不那么真实了。你见过圆圆的雪花始终面朝着你转圈的吗,见鬼了吧!

2. 在雪花飞扬场景中,可以不需要粒子的动态消亡与产生,可以让雪花粒子在一定区域内下落,如果下落到Y轴的临界区域,就把粒子的Y坐标设为预定的临界最高点的Y坐标,就像粒子都是从这个地方产生的一样,这样就会模拟出源源不断地下雪景象。其实,我们又在用惯用伎俩来欺骗玩家了。

就像SisMVG童鞋在浅墨上篇文章中评论的一样“其实世界上最大的骗子团体就是程序员”。我们只是其实是在让规定数量的雪花粒子不断地在跑堂,到了终点再让他们从起点重新开始跑,一遍一遍地,只要程序不停止运行,那么就永不止息。你以为你体验了无数的雪花从你眼前呼啸而过优雅下落的样子,其实也就是那么来来回回PARTICLE_NUMBER= 8000个而已,哈哈。而这个PARTICLE_NUMBER就是下面我们要封装雪花飞扬粒子系统的类中给粒子数量规定的宏。

 

好了,我们开始写这个雪花粒子系统的内容吧。


首先依旧是写出这个名为SnowParticleClass类的大体轮廓。

首先当头棒喝怒写4个宏,方便宏观调控。这四个宏分别用于表示雪花粒子数量,雪花飞扬区域的长度,雪花飞扬区域的宽度,雪花飞扬区域的高度。

 

[cpp]  view plain   copy
  print ?
  1. #define PARTICLE_NUMBER  100000   //雪花粒子数量,显卡不好、运行起来卡的的童鞋请取小一点。  
  2. #define SNOW_SYSTEM_LENGTH_X  20000   //下雪区域的长度  
  3. #define SNOW_SYSTEM_WIDTH_Z          20000   //下雪区域的宽度  
  4. #define SNOW_SYSTEM_HEIGHT_Y    20000   //下雪区域的高度  

这里的PARTICLE_NUMBER雪花粒子数量我们取的10万,那么后面我们写出来的游戏场景中就有10万个雪花粒子,当然,前提是你的显卡是糕富帅,才禁得住10万及以上的粒子数量。如果像浅墨这样的显卡明日黄花ATI Radeon HD 5730,取八万的雪花粒子,跑起来帧数就只有10帧左右了,卡的飞起。当然,你取20万的粒子数量,在下雪区域是在20000x20000x20000的区域中就是超级大暴雪了。。。建议这时候把长度和宽度调大一些,来让这20万的粒子的活动区域更大。


然后我们写出雪花粒子的FVF灵活顶点格式。顶点属性当然是顶点坐标加上纹理坐标了:

 

[cpp]  view plain   copy
  print ?
  1. //-----------------------------------------------------------------------------  
  2. //点精灵顶点结构和顶点格式  
  3. //-----------------------------------------------------------------------------  
  4. struct POINTVERTEX  
  5. {  
  6.        floatx, y, z;    //顶点位置  
  7.        floatu,v ;               //顶点纹理坐标  
  8. };  
  9. #define D3DFVF_POINTVERTEX(D3DFVF_XYZ|D3DFVF_TEX1)  


 

接下来是雪花粒子的属性结构体,想一想现实生活中的雪花有哪些特定的属性呢?唯美的雪花,有特定的位置,会旋转,有下降速度,样子不同,嗯,好,那么我们就这样写:

 

[cpp]  view plain   copy
  print ?
  1. //-----------------------------------------------------------------------------  
  2. // Desc: 雪花粒子结构体的定义  
  3. //-----------------------------------------------------------------------------  
  4. struct SNOWPARTICLE  
  5. {  
  6.        floatx, y, z;      //坐标位置  
  7.        floatRotationY;         //雪花绕自身Y轴旋转角度  
  8.        floatRotationX;       //雪花绕自身X轴旋转角度  
  9.        floatFallSpeed;       //雪花下降速度  
  10.        floatRotationSpeed;       //雪花旋转速度  
  11.        int   TextureIndex;     //纹理索引数  
  12. };  



好,边角废料写完了,下面正式来设计这个类吧。首先来看一看要写哪些成员变量,LPDIRECT3DDEVICE9类型的设备接口指针m_pd3dDevice不能少吧,雪花粒子数组m_Snows要有吧,顶点缓存对象m_pVertexBuffer要有吧,保存不同雪花纹理样式的雪花纹理数组m_pTexture要有吧,嗯,成员变量就这些。

然后看看要有哪些成员函数,构造函数,析构函数先显式地写出来,然后粒子系统初始化函数InitSnowParticle,粒子系统更新函数UpdateSnowParticle,粒子系统渲染函数RenderSnowParticle,嗯,成员函数也就是这些了。整体来看,这里类的轮廓就是如下,即贴出SnowParticleClass.h的全部代码:

[cpp]  view plain   copy
  print ?
  1. //=============================================================================  
  2. // Name: SnowParticleClass.h  
  3. //    Des:一个封装了雪花粒子系统系统的类的头文件  
  4. // 2013年 3月31日 Create by 浅墨  
  5. //=============================================================================  
  6.    
  7.    
  8. #pragma once  
  9. #include "D3DUtil.h"  
  10. #define PARTICLE_NUMBER  20000   //雪花粒子数量,显卡不好、运行起来卡的童鞋请取小一点。  
  11. #define SNOW_SYSTEM_LENGTH_X  20000   //下雪区域的长度  
  12. #define SNOW_SYSTEM_WIDTH_Z          20000   //下雪区域的宽度  
  13. #define SNOW_SYSTEM_HEIGHT_Y    20000   //下雪区域的高度  
  14.    
  15. //-----------------------------------------------------------------------------  
  16. //点精灵顶点结构和顶点格式  
  17. //-----------------------------------------------------------------------------  
  18. struct POINTVERTEX  
  19. {  
  20.        floatx, y, z;    //顶点位置  
  21.        floatu,v ;               //顶点纹理坐标  
  22. };  
  23. #define D3DFVF_POINTVERTEX(D3DFVF_XYZ|D3DFVF_TEX1)  
  24.    
  25.    
  26. //-----------------------------------------------------------------------------  
  27. // Desc: 雪花粒子结构体的定义  
  28. //-----------------------------------------------------------------------------  
  29. struct SNOWPARTICLE  
  30. {  
  31.        floatx, y, z;      //坐标位置  
  32.        floatRotationY;         //雪花绕自身Y轴旋转角度  
  33.        floatRotationX;       //雪花绕自身X轴旋转角度  
  34.        floatFallSpeed;       //雪花下降速度  
  35.        floatRotationSpeed;       //雪花旋转速度  
  36.        int   TextureIndex;     //纹理索引数  
  37. };  
  38.    
  39. //-----------------------------------------------------------------------------  
  40. // Desc: 粒子系统类的定义  
  41. //-----------------------------------------------------------------------------  
  42. class SnowParticleClass  
  43. {  
  44. private:  
  45.        LPDIRECT3DDEVICE9                            m_pd3dDevice;                 //D3D设备对象  
  46.        SNOWPARTICLE                                    m_Snows[PARTICLE_NUMBER];    //雪花粒子数组  
  47.        LPDIRECT3DVERTEXBUFFER9   m_pVertexBuffer;      //保存粒子数据的顶点缓存  
  48.        LPDIRECT3DTEXTURE9                  m_pTexture[6];  //雪花纹理  
  49.    
  50. public:  
  51.        SnowParticleClass(LPDIRECT3DDEVICE9pd3dDevice);   //构造函数  
  52.        ~SnowParticleClass();                                  //析构函数  
  53.        HRESULTInitSnowParticle();        //粒子系统初始化函数  
  54.        HRESULTUpdateSnowParticle( float fElapsedTime);   //粒子系统更新函数  
  55.        HRESULTRenderSnowParticle( );   //粒子系统渲染函数  
  56. };  


 

 

 

四、雪花粒子系统的实现

 

又到了做填空题的时候,对着上面我们写勾勒出来的SnowParticleClass类,我们有5个函数需要填上实现代码,还等什么,我们开始吧。

首先呢,构造函数:

 

[cpp]  view plain   copy
  print ?
  1. //-------------------------------------------------------------------------------------------------  
  2. // Desc: 构造函数  
  3. //-------------------------------------------------------------------------------------------------  
  4. SnowParticleClass::SnowParticleClass(LPDIRECT3DDEVICE9pd3dDevice)  
  5. {  
  6.        //给各个参数赋初值  
  7.        m_pd3dDevice=pd3dDevice;  
  8.        m_pVertexBuffer=NULL;   
  9.        for(inti=0; i<5; i++)  
  10.               m_pTexture[i]= NULL;  
  11. }  


接下来,粒子系统初始化函数InitSnowParticle()。首先呢,调用srand重新播种一下随机数种子。然后for循环为所有的雪花粒子赋予独一无二的各项属性值。接着,用讲烂了的顶点缓存使用五步曲的其中的三步为代表着所有雪花粒子属性的一个顶点缓存赋值,最后调用6次D3DXCreateTextureFromFile从文件加载6种不同的雪花纹理进来。这6种雪花纹理图是浅墨按照素材PS出来,分别导出的,效果图在下面,还不错,各有特点,非常漂亮:

 

              

 

 


经过上面的思考,InitSnowParticle()函数的实现代码我们就知道怎么写了:

[cpp]  view plain   copy
  print ?
  1. //-------------------------------------------------------------------------------------------------  
  2. // Name: SnowParticleClass::InitSnowParticle( )  
  3. // Desc: 粒子系统初始化函数  
  4. //-------------------------------------------------------------------------------------------------  
  5. HRESULTSnowParticleClass::InitSnowParticle( )  
  6. {  
  7.        //初始化雪花粒子数组  
  8.        srand(GetTickCount());  
  9.        for(inti=0; i<PARTICLE_NUMBER; i++)  
  10.        {       
  11.               m_Snows[i].x        =float(rand()%SNOW_SYSTEM_LENGTH_X-SNOW_SYSTEM_LENGTH_X/2);  
  12.               m_Snows[i].z        = float(rand()%SNOW_SYSTEM_WIDTH_Z-SNOW_SYSTEM_WIDTH_Z/2);  
  13.               m_Snows[i].y        = float(rand()%SNOW_SYSTEM_HEIGHT_Y);  
  14.               m_Snows[i].RotationY     = (rand()%100)/50.0f*D3DX_PI;  
  15.               m_Snows[i].RotationX   = (rand()%100)/50.0f*D3DX_PI;  
  16.               m_Snows[i].FallSpeed   = 300.0f + rand()%500;  
  17.               m_Snows[i].RotationSpeed   = 5.0f + rand()%10/10.0f;  
  18.               m_Snows[i].TextureIndex= rand()%6;  
  19.        }  
  20.    
  21.    
  22.        //创建雪花粒子顶点缓存  
  23.        m_pd3dDevice->CreateVertexBuffer(4*sizeof(POINTVERTEX), 0,  
  24.               D3DFVF_POINTVERTEX,D3DPOOL_MANAGED,&m_pVertexBuffer, NULL );  
  25.    
  26.        //填充雪花粒子顶点缓存  
  27.        POINTVERTEXvertices[] =  
  28.        {  
  29.               {-20.0f, 0.0f, 0.0f,   0.0f, 1.0f, },  
  30.               {-20.0f, 40.0f, 0.0f,   0.0f, 0.0f, },  
  31.               {  20.0f, 0.0f, 0.0f,   1.0f, 1.0f, },  
  32.               {  20.0f, 40.0f, 0.0f,   1.0f, 0.0f, }  
  33.        };  
  34.        //加锁  
  35.        VOID*pVertices;  
  36.        m_pVertexBuffer->Lock(0, sizeof(vertices), (void**)&pVertices, 0 );  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值