本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
文章链接:http://blog.csdn.net/zhmxy555/article/details/8744805
作者:毛星云(浅墨) 邮箱: [email protected]
本篇文章中,我们将一起探讨三维游戏中粒子系统的方方面面,首先对粒子系统的基本概念特性做一个全面的认知,然后我们依旧是把粒子系统封装在一个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个宏,方便宏观调控。这四个宏分别用于表示雪花粒子数量,雪花飞扬区域的长度,雪花飞扬区域的宽度,雪花飞扬区域的高度。
#define PARTICLE_NUMBER 100000 //雪花粒子数量,显卡不好、运行起来卡的的童鞋请取小一点。 #define SNOW_SYSTEM_LENGTH_X 20000 //下雪区域的长度 #define SNOW_SYSTEM_WIDTH_Z 20000 //下雪区域的宽度 #define SNOW_SYSTEM_HEIGHT_Y 20000 //下雪区域的高度这里的PARTICLE_NUMBER雪花粒子数量我们取的10万,那么后面我们写出来的游戏场景中就有10万个雪花粒子,当然,前提是你的显卡是糕富帅,才禁得住10万及以上的粒子数量。如果像浅墨这样的显卡明日黄花ATI Radeon HD 5730,取八万的雪花粒子,跑起来帧数就只有10帧左右了,卡的飞起。当然,你取20万的粒子数量,在下雪区域是在20000x20000x20000的区域中就是超级大暴雪了。。。建议这时候把长度和宽度调大一些,来让这20万的粒子的活动区域更大。
然后我们写出雪花粒子的FVF灵活顶点格式。顶点属性当然是顶点坐标加上纹理坐标了:
//----------------------------------------------------------------------------- //点精灵顶点结构和顶点格式 //----------------------------------------------------------------------------- struct POINTVERTEX { floatx, y, z; //顶点位置 floatu,v ; //顶点纹理坐标 }; #define D3DFVF_POINTVERTEX(D3DFVF_XYZ|D3DFVF_TEX1)
接下来是雪花粒子的属性结构体,想一想现实生活中的雪花有哪些特定的属性呢?唯美的雪花,有特定的位置,会旋转,有下降速度,样子不同,嗯,好,那么我们就这样写:
//----------------------------------------------------------------------------- // Desc: 雪花粒子结构体的定义 //----------------------------------------------------------------------------- struct SNOWPARTICLE { floatx, y, z; //坐标位置 floatRotationY; //雪花绕自身Y轴旋转角度 floatRotationX; //雪花绕自身X轴旋转角度 floatFallSpeed; //雪花下降速度 floatRotationSpeed; //雪花旋转速度 int TextureIndex; //纹理索引数 };
好,边角废料写完了,下面正式来设计这个类吧。首先来看一看要写哪些成员变量,LPDIRECT3DDEVICE9类型的设备接口指针m_pd3dDevice不能少吧,雪花粒子数组m_Snows要有吧,顶点缓存对象m_pVertexBuffer要有吧,保存不同雪花纹理样式的雪花纹理数组m_pTexture要有吧,嗯,成员变量就这些。
然后看看要有哪些成员函数,构造函数,析构函数先显式地写出来,然后粒子系统初始化函数InitSnowParticle,粒子系统更新函数UpdateSnowParticle,粒子系统渲染函数RenderSnowParticle,嗯,成员函数也就是这些了。整体来看,这里类的轮廓就是如下,即贴出SnowParticleClass.h的全部代码:
//============================================================================= // Name: SnowParticleClass.h // Des:一个封装了雪花粒子系统系统的类的头文件 // 2013年 3月31日 Create by 浅墨 //============================================================================= #pragma once #include "D3DUtil.h" #define PARTICLE_NUMBER 20000 //雪花粒子数量,显卡不好、运行起来卡的童鞋请取小一点。 #define SNOW_SYSTEM_LENGTH_X 20000 //下雪区域的长度 #define SNOW_SYSTEM_WIDTH_Z 20000 //下雪区域的宽度 #define SNOW_SYSTEM_HEIGHT_Y 20000 //下雪区域的高度 //----------------------------------------------------------------------------- //点精灵顶点结构和顶点格式 //----------------------------------------------------------------------------- struct POINTVERTEX { floatx, y, z; //顶点位置 floatu,v ; //顶点纹理坐标 }; #define D3DFVF_POINTVERTEX(D3DFVF_XYZ|D3DFVF_TEX1) //----------------------------------------------------------------------------- // Desc: 雪花粒子结构体的定义 //----------------------------------------------------------------------------- struct SNOWPARTICLE { floatx, y, z; //坐标位置 floatRotationY; //雪花绕自身Y轴旋转角度 floatRotationX; //雪花绕自身X轴旋转角度 floatFallSpeed; //雪花下降速度 floatRotationSpeed; //雪花旋转速度 int TextureIndex; //纹理索引数 }; //----------------------------------------------------------------------------- // Desc: 粒子系统类的定义 //----------------------------------------------------------------------------- class SnowParticleClass { private: LPDIRECT3DDEVICE9 m_pd3dDevice; //D3D设备对象 SNOWPARTICLE m_Snows[PARTICLE_NUMBER]; //雪花粒子数组 LPDIRECT3DVERTEXBUFFER9 m_pVertexBuffer; //保存粒子数据的顶点缓存 LPDIRECT3DTEXTURE9 m_pTexture[6]; //雪花纹理 public: SnowParticleClass(LPDIRECT3DDEVICE9pd3dDevice); //构造函数 ~SnowParticleClass(); //析构函数 HRESULTInitSnowParticle(); //粒子系统初始化函数 HRESULTUpdateSnowParticle( float fElapsedTime); //粒子系统更新函数 HRESULTRenderSnowParticle( ); //粒子系统渲染函数 };
四、雪花粒子系统的实现
又到了做填空题的时候,对着上面我们写勾勒出来的SnowParticleClass类,我们有5个函数需要填上实现代码,还等什么,我们开始吧。
首先呢,构造函数:
//------------------------------------------------------------------------------------------------- // Desc: 构造函数 //------------------------------------------------------------------------------------------------- SnowParticleClass::SnowParticleClass(LPDIRECT3DDEVICE9pd3dDevice) { //给各个参数赋初值 m_pd3dDevice=pd3dDevice; m_pVertexBuffer=NULL; for(inti=0; i<5; i++) m_pTexture[i]= NULL; }
接下来,粒子系统初始化函数InitSnowParticle()。首先呢,调用srand重新播种一下随机数种子。然后for循环为所有的雪花粒子赋予独一无二的各项属性值。接着,用讲烂了的顶点缓存使用五步曲的其中的三步为代表着所有雪花粒子属性的一个顶点缓存赋值,最后调用6次D3DXCreateTextureFromFile从文件加载6种不同的雪花纹理进来。这6种雪花纹理图是浅墨按照素材PS出来,分别导出的,效果图在下面,还不错,各有特点,非常漂亮:
经过上面的思考,InitSnowParticle()函数的实现代码我们就知道怎么写了:
//------------------------------------------------------------------------------------------------- // Name: SnowParticleClass::InitSnowParticle( ) // Desc: 粒子系统初始化函数 //------------------------------------------------------------------------------------------------- HRESULTSnowParticleClass::InitSnowParticle( ) { //初始化雪花粒子数组 srand(GetTickCount()); for(inti=0; i<PARTICLE_NUMBER; i++) { m_Snows[i].x =float(rand()%SNOW_SYSTEM_LENGTH_X-SNOW_SYSTEM_LENGTH_X/2); m_Snows[i].z = float(rand()%SNOW_SYSTEM_WIDTH_Z-SNOW_SYSTEM_WIDTH_Z/2); m_Snows[i].y = float(rand()%SNOW_SYSTEM_HEIGHT_Y); m_Snows[i].RotationY = (rand()%100)/50.0f*D3DX_PI; m_Snows[i].RotationX = (rand()%100)/50.0f*D3DX_PI; m_Snows[i].FallSpeed = 300.0f + rand()%500; m_Snows[i].RotationSpeed = 5.0f + rand()%10/10.0f; m_Snows[i].TextureIndex= rand()%6; } //创建雪花粒子顶点缓存 m_pd3dDevice->CreateVertexBuffer(4*sizeof(POINTVERTEX), 0, D3DFVF_POINTVERTEX,D3DPOOL_MANAGED,&m_pVertexBuffer, NULL ); //填充雪花粒子顶点缓存 POINTVERTEXvertices[] = { {-20.0f, 0.0f, 0.0f, 0.0f, 1.0f, }, {-20.0f, 40.0f, 0.0f, 0.0f, 0.0f, }, { 20.0f, 0.0f, 0.0f, 1.0f, 1.0f, }, { 20.0f, 40.0f, 0.0f, 1.0f, 0.0f, } }; //加锁 VOID*pVertices; m_pVertexBuffer->Lock(0, sizeof(vertices), (void**)&pVertices, 0 ); //访问 memcpy(pVertices, vertices, sizeof(vertices) ); //解锁 m_pVertexBuffer->Unlock(); //创建6种雪花纹理 D3DXCreateTextureFromFile(m_pd3dDevice, L"GameMedia\\snow1.jpg", &m_pTexture[0] ); D3DXCreateTextureFromFile(m_pd3dDevice, L"GameMedia\\snow2.jpg", &m_pTexture[1] ); D3DXCreateTextureFromFile(m_pd3dDevice, L"GameMedia\\snow3.jpg", &m_pTexture[2] ); D3DXCreateTextureFromFile(m_pd3dDevice, L"GameMedia\\snow4.jpg", &m_pTexture[3] ); D3DXCreateTextureFromFile(m_pd3dDevice, L"GameMedia\\snow5.jpg", &m_pTexture[4] ); D3DXCreateTextureFromFile(m_pd3dDevice, L"GameMedia\\snow6.jpg", &m_pTexture[5] ); returnS_OK; }
接着我们来看一下粒子系统更新函数UpdateSnowParticle怎么实现。其实非常简单,就是一个for循环遍历所有的粒子,看有哪些需要更新的就可以了,对于我们雪花粒子系统,需要更新一下每个粒子的Y坐标,然后判断是否到了“地面”,然后还要改变其自旋角度。那么,代码写出来就是这样了:
//------------------------------------------------------------------------------------------------- // Name: SnowParticleClass::UpdateSnowParticle( ) // Desc: 粒子系统更新函数 //------------------------------------------------------------------------------------------------- HRESULTSnowParticleClass::UpdateSnowParticle( float fElapsedTime) { //一个for循环,更新每个雪花粒子的当前位置和角度 for(inti=0; i<PARTICLE_NUMBER; i++) { m_Snows[i].y-= m_Snows[i].FallSpeed*fElapsedTime; //如果雪花粒子落到地面, 重新将其高度设置为最大 if(m_Snows[i].y<0) m_Snows[i].y= SNOW_SYSTEM_WIDTH_Z; //更改自旋角度 m_Snows[i].RotationY += m_Snows[i].RotationSpeed * fElapsedTime; m_Snows[i].RotationX += m_Snows[i].RotationSpeed * fElapsedTime; } returnS_OK; }
最后来看一下最关键的粒子系统渲染函数RenderSnowParticle怎么写。首先禁用照明,然后设置纹理状态,接着设置设置Alpha混合系数,设置背面消隐模式为不剔除,然后就开始渲染了。需要注意的是设置Alpha混合系数这里依旧是我们之前没有专门花一篇文章讲,这里理解它的功能为进行透明贴图就行了。就是我们在写GDI游戏小程序的时候一直在做的纠结事情,把图片背景的黑边去掉。好了,思路有了,写代码还会难吗:
//------------------------------------------------------------------------------------------------- // Name: SnowParticleClass::RenderSnowParticle( ) // Desc: 粒子系统渲染函数 //------------------------------------------------------------------------------------------------- HRESULT SnowParticleClass::RenderSnowParticle( ) { //禁用照明效果 m_pd3dDevice->SetRenderState(D3DRS_LIGHTING, false ); //设置纹理状态 m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); //将纹理颜色混合的第一个参数的颜色值用于输出 m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE ); //纹理颜色混合的第一个参数的值就取纹理颜色值 m_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR ); //缩小过滤状态采用线性纹理过滤 m_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR ); //放大过滤状态采用线性纹理过滤 //设置Alpha混合系数 m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,true); //打开Alpha混合 m_pd3dDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_ONE); //源混合系数设为1 m_pd3dDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE); //目标混合系数设为1 //设置剔出模式为不剔除任何面 m_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE ); //渲染雪花 for(inti=0; i<PARTICLE_NUMBER; i++) { //构造并设置当前雪花粒子的世界矩阵 staticD3DXMATRIX matYaw, matPitch, matTrans, matWorld; D3DXMatrixRotationY(&matYaw,m_Snows[i].RotationY); D3DXMatrixRotationX(&matPitch,m_Snows[i].RotationX); D3DXMatrixTranslation(&matTrans,m_Snows[i].x, m_Snows[i].y, m_Snows[i].z); matWorld= matYaw * matPitch * matTrans; m_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld); //渲染当前雪花粒子 m_pd3dDevice->SetTexture(0, m_pTexture[m_Snows[i].TextureIndex] ); //设置纹理 m_pd3dDevice->SetStreamSource(0,m_pVertexBuffer, 0, sizeof(POINTVERTEX)); //把包含的几何体信息的顶点缓存和渲染流水线相关联 m_pd3dDevice->SetFVF(D3DFVF_POINTVERTEX); //设置FVF灵活顶点格式 m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP,0, 2); //绘制 } //恢复相关渲染状态:Alpha混合 、剔除状态、光照 m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,false); m_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW ); m_pd3dDevice->SetRenderState(D3DRS_LIGHTING, true ); returnS_OK; }
析构函数就很简单了,就是在收拾残局,看有什么COM接口要释放的:
//------------------------------------------------------------------------------------------------- // Desc: 析构函数 //------------------------------------------------------------------------------------------------- SnowParticleClass::~SnowParticleClass() { SAFE_RELEASE(m_pVertexBuffer); for(inti=0;i<3; i++) { SAFE_RELEASE(m_pTexture[i]); }
五,雪花飞扬粒子类的使用
与类打交道封装功能和我们经历的生活一样,也是一个先苦后甜的过程。
设计和实现这个类的时候或许是苦涩的,但是先苦后甜是必须的,写完这个类之后,用起来非常的方便,只用几行代码而已,一个唯美的雪花飞扬景象就加入到我们的游戏场景中了。。
也就是如下的三步:
Ⅰ.首先,定义一个SnowParticleClass类的全局指针实例:
SnowParticleClass* g_pSnowParticles= NULL; //雪花粒子系统的指针实例
Ⅱ.然后,在初始化阶段拿着雪花飞扬类的指针对象SnowParticleClass到处“指”,创建并初始化粒子系统:
//创建并初始化雪花粒子系统 g_pSnowParticles= new SnowParticleClass(g_pd3dDevice); g_pSnowParticles->InitSnowParticle();
Ⅲ.最后,就是在Render函数中依然是拿着天空类的指针对象g_pSnowParticles先指一下UpdateSnowParticle函数,更新粒子系统,然后再指一下RenderSnowParticle函数,进行渲染。
//绘制雪花粒子系统 g_pSnowParticles->UpdateSnowParticle(fTimeDelta); g_pSnowParticles->RenderSnowParticle();
另外需要注意上面更新粒子系统的UpdateSnowParticle函数我们用到了一个流逝时间参数fTimeDelta,所以我们就要把我们服役多年的消息循环中改成如下含有流逝时间的更加先进的消息循环体系,然后让Direct3D_Update和Direct3D_Render各增加一个代表流逝时间的fTimeDelta参数。
//消息循环过程 MSGmsg = { 0 }; //初始化msg while(msg.message != WM_QUIT ) //使用while循环 { staticFLOAT fLastTime =(float)::timeGetTime(); staticFLOAT fCurrTime =(float)::timeGetTime(); staticFLOAT fTimeDelta = 0.0f; fCurrTime = (float)::timeGetTime(); fTimeDelta= (fCurrTime - fLastTime) / 1000.0f; fLastTime = fCurrTime; if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 { TranslateMessage(&msg ); //将虚拟键消息转换为字符消息 DispatchMessage(&msg ); //该函数分发一个消息给窗口程序。 } else { Direct3D_Update(hwnd,fTimeDelta); //调用更新函数,进行画面的更新 Direct3D_Render(hwnd,fTimeDelta); //调用渲染函数,进行画面的渲染 } }
嗯,既然类都写完了,用起来就是这么简单。
六、详细注释的源代码欣赏
本篇文章配套的源代码在之前的基础上又增加了两个文件,也就是实现雪花飞扬粒子系统类的源文件和头文件。全部文件数量增加到了12个,它们的列表如下:
我们依旧只贴出核心代码main.cpp,其他的众多文件大家下源代码回去看就好了。
//***************************************************************************************** // //【Visual C++】游戏开发笔记系列配套源码五十 浅墨DirectX教程十八 雪花飞扬:实现唯美的粒子系统 // VS2010版 // 2013年 3月31日 Create by 浅墨 //图标素材出处: VAMPIRE_SWEETIE //背景音乐素材出处:魔兽争霸3 暗夜精灵战斗曲 //人物模型素材出处:英雄无敌6 //更多内容请访问我的博客: http://blog.csdn.net/zhmxy555 // //***************************************************************************************** //***************************************************************************************** // Desc: 宏定义部分 //***************************************************************************************** #define SCREEN_WIDTH 932 //为窗口宽度定义的宏,以方便在此处修改窗口宽度 #define SCREEN_HEIGHT 700 //为窗口高度定义的宏,以方便在此处修改窗口高度 #define WINDOW_TITLE _T("【Visual C++】游戏开发笔记系列配套示例程序五十 浅墨DirectX教程十八 雪花飞扬:实现唯美的粒子系统") //为窗口标题定义的宏 //***************************************************************************************** // Desc: 头文件定义部分 //***************************************************************************************** #include <d3d9.h> #include <d3dx9.h> #include <tchar.h> #include <time.h> #include "DirectInputClass.h" #include "CameraClass.h" #include "TerrainClass.h" #include "SkyBoxClass.h" #include "SnowParticleClass.h" //***************************************************************************************** // Desc: 库文件定义部分 //***************************************************************************************** #pragma comment(lib,"d3d9.lib") #pragma comment(lib,"d3dx9.lib") #pragma comment(lib, "dinput8.lib") // 使用DirectInput必须包含的库文件,注意这里有8 #pragma comment(lib,"dxguid.lib") #pragma comment(lib, "winmm.lib") //***************************************************************************************** // Desc: 全局变量声明部分 //***************************************************************************************** LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D设备对象 LPD3DXFONT g_pTextFPS =NULL; //字体COM接口 LPD3DXFONT g_pTextAdaperName = NULL; // 显卡信息的2D文本 LPD3DXFONT g_pTextHelper = NULL; // 帮助信息的2D文本 LPD3DXFONT g_pTextInfor= NULL; // 绘制信息的2D文本 float g_FPS= 0.0f; //一个浮点型的变量,代表帧速率 wchar_t g_strFPS[50] ={0}; //包含帧速率的字符数组 wchar_t g_strAdapterName[60] ={0}; //包含显卡名称的字符数组 D3DXMATRIX g_matWorld; //世界矩阵 LPD3DXMESH g_pMesh = NULL; // 网格对象 D3DMATERIAL9* g_pMaterials= NULL; // 网格的材质信息 LPDIRECT3DTEXTURE9* g_pTextures = NULL; // 网格的纹理信息 DWORD g_dwNumMtrls = 0; // 材质的数目 LPD3DXMESH g_cylinder = NULL; //柱子网格对象 D3DMATERIAL9 g_MaterialCylinder; //柱子的材质 D3DLIGHT9 g_Light; //全局光照 DInputClass* g_pDInput = NULL; //DInputClass类的指针实例 CameraClass* g_pCamera = NULL; //摄像机类的指针实例 TerrainClass* g_pTerrain = NULL; //地形类的指针实例 SkyBoxClass* g_pSkyBox=NULL; //天空盒类的指针实例 SnowParticleClass* g_pSnowParticles = NULL; //雪花粒子系统的指针实例 //***************************************************************************************** // Desc: 全局函数声明部分 //***************************************************************************************** LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance); HRESULT Objects_Init(); void Direct3D_Render( HWND hwnd,FLOAT fTimeDelta); void Direct3D_Update( HWND hwnd,FLOAT fTimeDelta); void Direct3D_CleanUp( ); float Get_FPS(); void HelpText_Render(HWND hwnd); //***************************************************************************************** // Name: WinMain( ) // Desc: Windows应用程序入口函数 //***************************************************************************************** int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) { //开始设计一个完整的窗口类 WNDCLASSEX wndClass={0} ; //用WINDCLASSEX定义了一个窗口类,即用wndClass实例化了WINDCLASSEX,用于之后窗口的各项初始化 wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小 wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式 wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针 wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。 wndClass.hIcon=(HICON)::LoadImage(NULL,_T("GameMedia\\icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //从全局的::LoadImage函数从本地加载自定义ico图标 wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。 wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //为hbrBackground成员指定一个灰色画刷句柄 wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。 wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop"); //用一个以空终止的字符串,指定窗口类的名字。 if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口 return -1; HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, SCREEN_WIDTH, SCREEN_HEIGHT, NULL, NULL, hInstance, NULL ); //Direct3D资源的初始化,调用失败用messagebox予以显示 if (!(S_OK==Direct3D_Init (hwnd,hInstance))) { MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0); //使用MessageBox函数,创建一个消息窗口 } PlaySound(L"GameMedia\\NightElf1.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP); //循环播放背景音乐 MoveWindow(hwnd,200,10,SCREEN_WIDTH,SCREEN_HEIGHT,true); //调整窗口显示时的位置,窗口左上角位于屏幕坐标(200,0)处 ShowWindow( hwnd, nShowCmd ); //调用Win32函数ShowWindow来显示窗口 UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 //进行DirectInput类的初始化 g_pDInput = new DInputClass(); g_pDInput->Init(hwnd,hInstance,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); //消息循环过程 MSG msg = { 0 }; //初始化msg while( msg.message != WM_QUIT ) //使用while循环 { static FLOAT fLastTime = (float)::timeGetTime(); static FLOAT fCurrTime = (float)::timeGetTime(); static FLOAT fTimeDelta = 0.0f; fCurrTime = (float)::timeGetTime(); fTimeDelta = (fCurrTime - fLastTime) / 1000.0f; fLastTime = fCurrTime; if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 { TranslateMessage( &msg ); //将虚拟键消息转换为字符消息 DispatchMessage( &msg ); //该函数分发一个消息给窗口程序。 } else { Direct3D_Update(hwnd,fTimeDelta); //调用更新函数,进行画面的更新 Direct3D_Render(hwnd,fTimeDelta); //调用渲染函数,进行画面的渲染 } } UnregisterClass(_T("ForTheDreamOfGameDevelop"), wndClass.hInstance); return 0; } //***************************************************************************************** // Name: WndProc() // Desc: 对窗口消息进行处理 //***************************************************************************************** LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) //窗口过程函数WndProc { switch( message ) //switch语句开始 { case WM_PAINT: // 客户区重绘消息 Direct3D_Render(hwnd,0.0f); //调用Direct3D_Render函数,进行画面的绘制 ValidateRect(hwnd, NULL); // 更新客户区的显示 break; //跳出该switch语句 case WM_KEYDOWN: // 键盘按下消息 if (wParam == VK_ESCAPE) // ESC键 DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息 break; case WM_DESTROY: //窗口销毁消息 Direct3D_CleanUp(); //调用Direct3D_CleanUp函数,清理COM接口对象 PostQuitMessage( 0 ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息 break; //跳出该switch语句 default: //若上述case条件都不符合,则执行该default语句 return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程来为应用程序没有处理的窗口消息提供缺省的处理。 } return 0; //正常退出 } //***************************************************************************************** // Name: Direct3D_Init( ) // Desc: 初始化Direct3D // Point:【Direct3D初始化四步曲】 // 1.初始化四步曲之一,创建Direct3D接口对象 // 2.初始化四步曲之二,获取硬件设备信息 // 3.初始化四步曲之三,填充结构体 // 4.初始化四步曲之四,创建Direct3D设备接口 //***************************************************************************************** HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance) { //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象 //-------------------------------------------------------------------------------------- LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建 if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商 return E_FAIL; //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息 //-------------------------------------------------------------------------------------- D3DCAPS9 caps; int vp = 0; if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) ) { return E_FAIL; } if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的 else vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算 //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体 //-------------------------------------------------------------------------------------- D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.BackBufferWidth = SCREEN_WIDTH; d3dpp.BackBufferHeight = SCREEN_HEIGHT; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferCount = 2; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.MultiSampleQuality = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hwnd; d3dpp.Windowed = true; d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = 0; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口 //-------------------------------------------------------------------------------------- if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, vp, &d3dpp, &g_pd3dDevice))) return E_FAIL; //获取显卡信息到g_strAdapterName中,并在显卡名称之前加上“当前显卡型号:”字符串 wchar_t TempName[60]=L"当前显卡型号:"; //定义一个临时字符串,且方便了把"当前显卡型号:"字符串引入我们的目的字符串中 D3DADAPTER_IDENTIFIER9 Adapter; //定义一个D3DADAPTER_IDENTIFIER9结构体,用于存储显卡信息 pD3D->GetAdapterIdentifier(0,0,&Adapter);//调用GetAdapterIdentifier,获取显卡信息 int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);//显卡名称现在已经在Adapter.Description中了,但是其为char类型,我们要将其转为wchar_t类型 MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);//这步操作完成后,g_strAdapterName中就为当前我们的显卡类型名的wchar_t型字符串了 wcscat_s(TempName,g_strAdapterName);//把当前我们的显卡名加到“当前显卡型号:”字符串后面,结果存在TempName中 wcscpy_s(g_strAdapterName,TempName);//把TempName中的结果拷贝到全局变量g_strAdapterName中,大功告成~ if(!(S_OK==Objects_Init())) return E_FAIL; SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉 return S_OK; } HRESULT Objects_Init() { //创建字体 D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("Calibri"), &g_pTextFPS); D3DXCreateFont(g_pd3dDevice, 20, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"华文中宋", &g_pTextAdaperName); D3DXCreateFont(g_pd3dDevice, 23, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微软雅黑", &g_pTextHelper); D3DXCreateFont(g_pd3dDevice, 26, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑体", &g_pTextInfor); // 从X文件中加载网格数据 LPD3DXBUFFER pAdjBuffer = NULL; LPD3DXBUFFER pMtrlBuffer = NULL; D3DXLoadMeshFromX(L"angle.X", D3DXMESH_MANAGED, g_pd3dDevice, &pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh); // 读取材质和纹理数据 D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息 g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls]; g_pTextures = new LPDIRECT3DTEXTURE9[g_dwNumMtrls]; for (DWORD i=0; i<g_dwNumMtrls; i++) { //获取材质,并设置一下环境光的颜色值 g_pMaterials[i] = pMtrls[i].MatD3D; g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse; //创建一下纹理对象 g_pTextures[i] = NULL; D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]); } SAFE_RELEASE(pAdjBuffer) SAFE_RELEASE(pMtrlBuffer) //创建柱子 D3DXCreateCylinder(g_pd3dDevice, 280.0f, 10.0f, 3000.0f, 60, 60, &g_cylinder, 0); g_MaterialCylinder.Ambient = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); g_MaterialCylinder.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); g_MaterialCylinder.Specular = D3DXCOLOR(0.5f, 0.0f, 0.3f, 0.3f); g_MaterialCylinder.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f); // 设置光照 ::ZeroMemory(&g_Light, sizeof(g_Light)); g_Light.Type = D3DLIGHT_DIRECTIONAL; g_Light.Ambient = D3DXCOLOR(0.7f, 0.7f, 0.7f, 1.0f); g_Light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); g_Light.Specular = D3DXCOLOR(0.9f, 0.9f, 0.9f, 1.0f); g_Light.Direction = D3DXVECTOR3(1.0f, 1.0f, 1.0f); g_pd3dDevice->SetLight(0, &g_Light); g_pd3dDevice->LightEnable(0, true); g_pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true); g_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, true); // 创建并初始化虚拟摄像机 g_pCamera = new CameraClass(g_pd3dDevice); g_pCamera->SetCameraPosition(&D3DXVECTOR3(0.0f, 1400.0f, -1800.0f)); //设置摄像机所在的位置 g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 1200.0f, 0.0f)); //设置目标观察点所在的位置 g_pCamera->SetViewMatrix(); //设置取景变换矩阵 g_pCamera->SetProjMatrix(); //设置投影变换矩阵 // 创建并初始化地形 g_pTerrain = new TerrainClass(g_pd3dDevice); g_pTerrain->LoadTerrainFromFile(L"GameMedia\\heighmap.raw", L"GameMedia\\terrainstone.jpg"); //从文件加载高度图和纹理 g_pTerrain->InitTerrain(200, 200, 60.0f, 8.0f); //四个值分别是顶点行数,顶点列数,顶点间间距,缩放系数 //创建并初始化天空对象 g_pSkyBox = new SkyBoxClass( g_pd3dDevice ); g_pSkyBox->LoadSkyTextureFromFile(L"GameMedia\\TropicalSunnyDayFront2048.png",L"GameMedia\\TropicalSunnyDayBack2048.png",L"GameMedia\\TropicalSunnyDayRight2048.png",L"GameMedia\\TropicalSunnyDayLeft2048.png", L"GameMedia\\TropicalSunnyDayUp2048.png");//从文件加载前、后、左、右、顶面5个面的纹理图 g_pSkyBox->InitSkyBox(50000); //设置天空盒的边长 //创建并初始化雪花粒子系统 g_pSnowParticles = new SnowParticleClass(g_pd3dDevice); g_pSnowParticles->InitSnowParticle(); return S_OK; } void Direct3D_Update( HWND hwnd,FLOAT fTimeDelta) { //使用DirectInput类读取数据 g_pDInput->GetInput(); // 沿摄像机各分量移动视角 if (g_pDInput->IsKeyDown(DIK_A)) g_pCamera->MoveAlongRightVec(-3.0f); if (g_pDInput->IsKeyDown(DIK_D)) g_pCamera->MoveAlongRightVec( 3.0f); if (g_pDInput->IsKeyDown(DIK_W)) g_pCamera->MoveAlongLookVec( 3.0f); if (g_pDInput->IsKeyDown(DIK_S)) g_pCamera->MoveAlongLookVec(-3.0f); if (g_pDInput->IsKeyDown(DIK_R)) g_pCamera->MoveAlongUpVec( 3.0f); if (g_pDInput->IsKeyDown(DIK_F)) g_pCamera->MoveAlongUpVec(-3.0f); //沿摄像机各分量旋转视角 if (g_pDInput->IsKeyDown(DIK_LEFT)) g_pCamera->RotationUpVec(-0.003f); if (g_pDInput->IsKeyDown(DIK_RIGHT)) g_pCamera->RotationUpVec( 0.003f); if (g_pDInput->IsKeyDown(DIK_UP)) g_pCamera->RotationRightVec(-0.003f); if (g_pDInput->IsKeyDown(DIK_DOWN)) g_pCamera->RotationRightVec( 0.003f); if (g_pDInput->IsKeyDown(DIK_Q)) g_pCamera->RotationLookVec(0.001f); if (g_pDInput->IsKeyDown(DIK_E)) g_pCamera->RotationLookVec( -0.001f); //鼠标控制右向量和上向量的旋转 g_pCamera->RotationUpVec(g_pDInput->MouseDX()* 0.001f); g_pCamera->RotationRightVec(g_pDInput->MouseDY() * 0.001f); //鼠标滚轮控制观察点收缩操作 static FLOAT fPosZ=0.0f; fPosZ += g_pDInput->MouseDZ()*0.03f; //计算并设置取景变换矩阵 D3DXMATRIX matView; g_pCamera->CalculateViewMatrix(&matView); g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //把正确的世界变换矩阵存到g_matWorld中 D3DXMatrixTranslation(&g_matWorld, 0.0f, 0.0f, fPosZ); //以下这段代码用于限制鼠标光标移动区域 POINT lt,rb; RECT rect; GetClientRect(hwnd,&rect); //取得窗口内部矩形 //将矩形左上点坐标存入lt中 lt.x = rect.left; lt.y = rect.top; //将矩形右下坐标存入rb中 rb.x = rect.right; rb.y = rect.bottom; //将lt和rb的窗口坐标转换为屏幕坐标 ClientToScreen(hwnd,<); ClientToScreen(hwnd,&rb); //以屏幕坐标重新设定矩形区域 rect.left = lt.x; rect.top = lt.y; rect.right = rb.x; rect.bottom = rb.y; //限制鼠标光标移动区域 ClipCursor(&rect); ShowCursor(false); //隐藏鼠标光标 } //***************************************************************************************** // Name: Direct3D_Render() // Desc: 进行图形的渲染操作 // Point:【Direct3D渲染五步曲】 // 1.渲染五步曲之一,清屏操作 // 2.渲染五步曲之二,开始绘制 // 3.渲染五步曲之三,正式绘制 // 4.渲染五步曲之四,结束绘制 // 5.渲染五步曲之五,翻转显示 //***************************************************************************************** void Direct3D_Render(HWND hwnd,FLOAT fTimeDelta) { //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之一】:清屏操作 //-------------------------------------------------------------------------------------- g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100, 255, 255), 1.0f, 0); //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之二】:开始绘制 //-------------------------------------------------------------------------------------- g_pd3dDevice->BeginScene(); // 开始绘制 //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之三】:正式绘制 //-------------------------------------------------------------------------------------- //绘制人物 D3DXMATRIX mScal,mRot2,mTrans,mFinal; //定义一些矩阵,准备对大黄蜂进行矩阵变换 D3DXMatrixTranslation(&mTrans,50.0f,1200.0f,0.0f); D3DXMatrixScaling(&mScal,3.0f,3.0f,3.0f); mFinal=mScal*mTrans*g_matWorld; g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal);//设置模型的世界矩阵,为绘制做准备 // 用一个for循环,进行模型的网格各个部分的绘制 for (DWORD i = 0; i < g_dwNumMtrls; i++) { g_pd3dDevice->SetMaterial(&g_pMaterials[i]); //设置此部分的材质 g_pd3dDevice->SetTexture(0, g_pTextures[i]);//设置此部分的纹理 g_pMesh->DrawSubset(i); //绘制此部分 } //绘制柱子 D3DXMATRIX TransMatrix, RotMatrix, FinalMatrix; D3DXMatrixRotationX(&RotMatrix, -D3DX_PI * 0.5f); g_pd3dDevice->SetLight(0, &g_Light); g_pd3dDevice->SetMaterial(&g_MaterialCylinder); g_pd3dDevice->SetTexture(0, NULL);//设置此部分的纹理 for(int i = 0; i < 4; i++) { D3DXMatrixTranslation(&TransMatrix, -300.0f, 0.0f, -350.0f + (i * 500.0f)); FinalMatrix = RotMatrix * TransMatrix ; g_pd3dDevice->SetTransform(D3DTS_WORLD, &FinalMatrix); g_cylinder->DrawSubset(0); D3DXMatrixTranslation(&TransMatrix, 300.0f, 0.0f, -350.0f + (i * 500.0f)); FinalMatrix = RotMatrix * TransMatrix ; g_pd3dDevice->SetTransform(D3DTS_WORLD, &FinalMatrix); g_cylinder->DrawSubset(0); } //绘制地形 g_pTerrain->RenderTerrain(&g_matWorld, false); //渲染地形,且第二个参数设为false,表示不渲染出地形的线框 //绘制天空 D3DXMATRIX matSky,matTransSky,matRotSky; D3DXMatrixTranslation(&matTransSky,0.0f,-12000.0f,0.0f); D3DXMatrixRotationY(&matRotSky, -0.000005f*timeGetTime()); //旋转天空网格, 简单模拟云彩运动效果 matSky=matTransSky*matRotSky; g_pSkyBox->RenderSkyBox(&matSky, false); //绘制雪花粒子系统 g_pSnowParticles->UpdateSnowParticle(fTimeDelta); g_pSnowParticles->RenderSnowParticle(); //绘制文字信息 HelpText_Render(hwnd); //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之四】:结束绘制 //-------------------------------------------------------------------------------------- g_pd3dDevice->EndScene(); // 结束绘制 //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之五】:显示翻转 //-------------------------------------------------------------------------------------- g_pd3dDevice->Present(NULL, NULL, NULL, NULL); // 翻转与显示 } void HelpText_Render(HWND hwnd) { //定义一个矩形,用于获取主窗口矩形 RECT formatRect; GetClientRect(hwnd, &formatRect); //在窗口右上角处,显示每秒帧数 formatRect.top = 5; int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() ); g_pTextFPS->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_RGBA(0,239,136,255)); //显示显卡类型名 g_pTextAdaperName->DrawText(NULL,g_strAdapterName, -1, &formatRect, DT_TOP | DT_LEFT, D3DXCOLOR(1.0f, 0.5f, 0.0f, 1.0f)); // 输出帮助信息 formatRect.left = 0,formatRect.top = 380; g_pTextInfor->DrawText(NULL, L"控制说明:", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(235,123,230,255)); formatRect.top += 35; g_pTextHelper->DrawText(NULL, L" W:向前飞翔 S:向后飞翔 ", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" A:向左飞翔 D:向右飞翔", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" R:垂直向上飞翔 F:垂直向下飞翔", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" Q:向左倾斜 E:向右倾斜", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" 上、下、左、右方向键、鼠标移动:视角变化 ", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" 鼠标滚轮:人物模型Y轴方向移动", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" ESC键 : 退出程序", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); } //***************************************************************************************** // Name:Get_FPS()函数 // Desc: 用于计算帧速率 //***************************************************************************************** float Get_FPS() { //定义四个静态变量 static float fps = 0; //我们需要计算的FPS值 static int frameCount = 0;//帧数 static float currentTime =0.0f;//当前时间 static float lastTime = 0.0f;//持续时间 frameCount++;//每调用一次Get_FPS()函数,帧数自增1 currentTime = timeGetTime()*0.001f;//获取系统时间,其中timeGetTime函数返回的是以毫秒为单位的系统时间,所以需要乘以0.001,得到单位为秒的时间 //如果当前时间减去持续时间大于了1秒钟,就进行一次FPS的计算和持续时间的更新,并将帧数值清零 if(currentTime - lastTime > 1.0f) //将时间控制在1秒钟 { fps = (float)frameCount /(currentTime - lastTime);//计算这1秒钟的FPS值 lastTime = currentTime; //将当前时间currentTime赋给持续时间lastTime,作为下一秒的基准时间 frameCount = 0;//将本次帧数frameCount值清零 } return fps; } //***************************************************************************************** // Name: Direct3D_CleanUp() // Desc: 对Direct3D的资源进行清理,释放COM接口对象 //***************************************************************************************** void Direct3D_CleanUp() { //释放COM接口对象 for (DWORD i = 0; i<g_dwNumMtrls; i++) SAFE_RELEASE(g_pTextures[i]); SAFE_DELETE(g_pTextures); SAFE_DELETE(g_pMaterials); SAFE_DELETE(g_pDInput); SAFE_RELEASE(g_cylinder); SAFE_RELEASE(g_pMesh); SAFE_RELEASE(g_pd3dDevice); SAFE_RELEASE(g_pTextAdaperName) SAFE_RELEASE(g_pTextHelper) SAFE_RELEASE(g_pTextInfor) SAFE_RELEASE(g_pTextFPS) SAFE_RELEASE(g_pd3dDevice) }
学到目前为止,我们用学的知识写出来的游戏场景程序已经比较炫了,一些运行截图如下:
我们操纵着摄像机在雪中飞行,那种雪花铺面而来的感觉非常地舒服。
仰望天空,任雪花飘落到“身上”:
下面这张图可以明显看到雪花被我们规定了活动区域:
冒着显卡被烧的危险拍了一张10万粒子数量时的“暴雪”画面,大家看右上角的帧数,只有可怜的8帧了。。。。
扑,一朵雪花砸脸上了。。。。
大家如果觉得这雪花太大了,没事,这是浅墨为了表现力故意调大了一点。自己去调一下雪花顶点缓存POINTVERTEX vertices数组的大小即可。
我们可以发现,其实Direct3D的固定功能流水线学到目前,基础知识也就那么多,顶点缓存,索引缓存,四大变换,纹理映射,网格,模板缓存等 等,把他们其中的几个合理地组合在一起运用一下就是新的知识,这就衍生出了我们近期刚出的三篇文章中讲到的地形,天空,粒子系统。以及还 没讲到的 公告板,拾取等等知识。
文章最后,依旧是放出本篇文章配套源代码的下载:
本节笔记配套源代码请点击这里下载:
【浅墨DirectX提高班】配套源代码之十八下载
以上就是本节笔记的全部内容,更多精彩内容,且听下回分解。
浅墨在这里,希望喜欢游戏开发系列文章的朋友们能留下你们的评论,每次浅墨登陆博客看到大家的留言的时候都会非常开心,感觉自己正在传递一种信仰,一种精神。
文章最后,依然是【每文一语】栏目,今天的句子是:
朝着梦想的方向,迈步向前,相信,总有一天,你会看到属于自己的那道彩虹。
下周一,让我们离游戏开发的梦想更近一步。
下周一,游戏开发笔记,我们,不见不散。
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------