本系列文章由zhmxy555(毛星云)编写,转载请注明出处。

文章链接: http://blog.csdn.net/zhmxy555/article/details/8770426

作者:毛星云(浅墨)    邮箱: happylifemxy@163.com

最近几个星期,不停地收到大家的评论和邮件,大家都说希望浅墨早点讲骨骼动画。本来按浅墨拟定的写作计划是把骨骼动画放到很后面,因为骨骼动画知识需要前面的网格模型知识为基础,知识量本身有些大,很可能要占很多次更新的篇幅。

但是看到大家一致的评论,都说非常期待骨骼动画。那就好吧,我们就来开始慢慢讲。一路披荆斩棘,把网格模型相关知识讲完,然后消灭掉骨骼动画这个看似很难的大块头。

其实骨骼动画学起来并不难,而且掌握了非常好玩。一般的三维游戏中都要用到骨骼动画,没用到骨骼动画的三维游戏基本上都是那种什么小飞机啊,空战之类的小儿科游戏,弱爆了。

骨骼动画的前置知识是网格模型(把“前置”这两个字在word中打出来的时候怎么让我想起了DNF(毒奶粉)里面学技能时的“前置技能”- -)。而网格模型其实是Direct3D中非常重头的一部分内容,但是纵观已经与大家见面的十八篇Direct3D教程以来,只有《

【Visual C++】游戏开发笔记四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观

》是在纯粹讲解网格模型,所以想要掌握到骨骼动画,还得先把网格模型这部分再往后学一学,把基础部分补一补,这得花一些更新的次数。

这篇文章中我们来补的知识就是网格模型的优化以及克隆,然后把之前那篇网格模型文章中讲到的X文件的载入方法封装到了一个类中,以后就可以用几行代码,来载入多个3D模型到我们的游戏场景中了。首先放一张配套程序截图,这次我们载入了三个模型,分别是来自地狱代表队的地狱恶魔一头,来自人类代表队的人族骑士一个,来自天堂代表队的天堂雷龙一只。

一、网格模型的优化

文章开头就提到过了,我们之前讲过一次网格模型,大家如果不熟练,请移步到《

【Visual C++】游戏开发笔记四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观

》复习下吧。

无论是用手编(如果你真有这个毅力这么无聊的话)或者使用三维建模软件(3DS Max,Maya)所创建出的网格模型,都可能包含一些尸位素餐吃白饭的无用顶点和索引,况且网格中属性缓存中子集的顺序也不一定是有序的。所以,如果想要提高游戏程序的运行效率的话,网格模型的优化是可以做出卓越贡献的一块大头。

Direct3D的ID3DXMes接口为我们提供了ID3DXMesh::OptimizeInplace和ID3DXMesh::Optimize这两种方法来对网格进行优化。首先Optimize大家都知道,是优化的意思。两者的区别就在这个多出来的Inplace之上,in place,在原位的意思,那么OptimizeInplace函数就是在原来的位置上进行优化,直接对原网格进行优化,而不带Inplace的Optimize方法就生成一个优化后的新的网格对象。即调用OptimizeInplace函数进行优化,优化的是原网格,而调用Optimize函数进行优化,原网格不变,会返回一个优化后的新网格。

我们先来对OptimizeInplace方法进行剖析,MSDN中我们可以查到它的原型如下:

HRESULT OptimizeInplace([in]   DWORD Flags,[in]   const DWORD *pAdjacencyIn,[out]  DWORD *pAdjacencyOut,[out]  DWORD *pFaceRemap,[out]  LPD3DXBUFFER *ppVertexRemap
);

■第一个参数,DWORD类型的Flags,表示执行什么类型的优化方法。它在D3DXMESHOPT枚举中取一个或者多个的值,常用的一些值如下:

网格优化标识

精析

D3DXMESHOPT_COMPACT

从网格中移除没用的顶点和索引项。

D3DXMESHOPT_ATTRSORT

根据属性给三角形排序并调整属性表,这将使DrawSubset方法的绘制执行更有效率。

D3DXMESHOPT_VERTEXCACHE

增加顶点缓存的命中率

D3DXMESHOPT_STRIPREORDER

重组顶点索引,让三角带尽可能的长。

D3DXMESHOPT_IGNOREVERTS

只优化索引信息,忽略掉顶点信息。

另外需要注意的是:D3DXMESHOPT_VERTEXCACHE和D3DXMESHOPT_STRIPREORDER这两者的领域重合了,不能同时使用。

■第二个参数,const DWORD类型的*pAdjacencyIn ,指向优化前的邻接数组的指针,一般我们这样填(DWORD*)pAdjacencyBuffer->GetBufferPointer(),其中的pAdjacencyBuffer我们在D3DXLoadMeshFromX中给它附上了被载入网格的邻接信息,然后GetBufferPointer一下就是指向待优化的邻接数组的指针了。

■第三个参数,DWORD类型的*pAdjacencyOut,指向优化后的邻接数组的指针。如果不需要优化后的邻接信息,设为NULL就好了。

■第四个参数,DWORD类型的*pFaceRemap,用来填充面重影射信息。该数组必须不小于ID3DXMesh::GetNumFaces()。当一个mesh被优化时,由索引缓存定义的面可能被移动;也就是说,在pFaceRemap中的第i项表示第i个原始面被移动后的新索引值。我们一般很少用,取0或者NULL就行了。

■第五个参数,LPD3DXBUFFER类型的*ppVertexRemap,用于保存网格顶点的重映射信息,一般设为0或者NULL就可以了。

讲起来好像每个参数都很复杂,其实用起来蛮简单的,因为不少参数设为0或者NULL就行了,就像这样:

//其中pAdjacencyBuffer中存放了要被优化的网格的邻接信息//优化网格模型m_pMesh->OptimizeInplace(D3DXMESHOPT_COMPACT | D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_STRIPREORDER,(DWORD*)pAdjacencyBuffer->GetBufferPointer(),NULL, NULL, NULL );

然后再讲一下Optimize方法,在MSDN中查到它的原型如下:

HRESULT Optimize([in]       DWORD Flags,[in]       const DWORD*pAdjacencyIn,[in, out]  DWORD *pAdjacencyOut,[in, out]  DWORD *pFaceRemap,[out]      LPD3DXBUFFER*ppVertexRemap,[out]      LPD3DXMESH *ppOptMesh
);

这个方法的前五个参数和OptimizeInplace方法的的完全一模一样,大家照着OptimizeInplace看就得了。因为Optimize方法不修改原网格,所以需要额外准备一个参数来存放指向新网格的指针,这就有了它的第六个参数,LPD3DXMESH类型的*ppOptMesh。

另外,关于模型的优化问题。有些模型在制作过程中已经做了优化,所以有可能再优化也优化不出什么内容了。如果是载入一些没有经过优化或者是粗制滥造的3D模型,优化的效果就体现出来了,可以大大增加每秒渲染的帧数。

二、网格模型的克隆

另外,我们也可能要用到网格的克隆技术。网格的克隆是重新生成网格数据的一种方式。通过这种方式,我们可以创建出一个与原网格相同的网格模型,而且还能通过克隆选项重新指定网格数据的某些特殊的特性。网格的克隆是三维模型常用的一个概念,3DS Max与Maya中就有专门的功能,而在我们Direct3D中,也有专门的函数来应对,他就是ID3DXBaseMesh::CloneMeshFVF方法,我们来讲一下:

HRESULT CloneMeshFVF([in]           DWORD Options,[in]           DWORD FVF,[in]           LPDIRECT3DDEVICE9pDevice,[out, retval]  LPD3DXMESH*ppCloneMesh
);

■第一个参数,DWORD类型的Options,表示创建克隆网格时的一些选项,在一个庞大的D3DXMESH枚举体中取值,完整的介绍在这里就免了,没意义。我们介绍几个常用的就行了,其他的很少用到,就算用到了,我们可以查阅MSDN:

克隆网格标识

精析

D3DXMESH_32BIT

克隆的网格使用32位索引

D3DXMESH_MANAGED

克隆出的网格的数据的索引缓存用D3DPOOL_MANAGED标识

D3DXMESH_WRITEONLY

克隆出的网格数据只能执行写操作,不能执行读操作

D3DXMESH_DYNAMIC

克隆出的网格缓存为动态的

■第二个参数,DWORD类型的FVF,很好理解,就是为克隆出的新网格指定FVF灵活顶点格式,这就说明了克隆出的新网格是完全可以和原网格有不同的FVF灵活顶点格式的。

■第三个参数,LPDIRECT3DDEVICE9类型的pDevice,老朋友了,我们的金钥匙Direct3D设备指针。

■第四个参数,LPD3DXMESH类型的*ppCloneMesh,指向克隆出的网格模型的指针。我们事先新建一个网格模型指针,然后调用CloneMeshFVF函数的时候填在这里,调用完之后,这个指针就指向新克隆好的网格模型了。

如果大家看起来不太懂,没关系,我们来看一个调用实例:

//其中的m_pMesh为需要被克隆的原模型指针,为LPD3DXMESH类型
LPD3DXMESH  pNewMesh;
m_pMesh->CloneMeshFVF(D3DXMESH_MANAGED,m_pMesh->GetFVF() | D3DFVF_NORMAL, g_pd3dDevice, &pNewMesh);

三、X文件模型载入类的设计

上面的这些知识学起来有点闷有木有,下面我们来动手写点东西吧,然后给我们经过几次文章的更新构建起来的初具规模的游戏场景demo进一步完善,加入更多功能。这周的任务是把之前的X文件的载入与绘制功能封装在一个类中,这样我们就可以在游戏程序中使用这个类,随便几行代码就可以载入并绘制多个好看的3D模型来。

从某种意义上来说,类的出现就是为了避免重复的劳动。在游戏程序的编写过程中,多个3D模型的载入必然是需要一个或者多个专门来类的管理的,如果像我们之前的那样,每载入一个3D模型就要定义一堆全局变量,然后重复写一堆代码,完全就是乱来了。等到后面需要绘制的东西越来越多。到最后也许我们自己写出来的程序源码,都不忍直视了。。。

我们在上一节中的工程中新建一个C++类,命名为XFileModelClass。依然是老规矩,看看有哪些成员变量。

首先呢,理一理思路,我们注意到之前的示例程序中与X模型载入有关的全局变量有这些:

LPD3DXMESH                  g_pMesh = NULL;                           // 网格对象
D3DMATERIAL9*              g_pMaterials= NULL;                     //网格的材质信息
LPDIRECT3DTEXTURE9*   g_pTextures = NULL;               //网格的纹理信息
DWORD                            g_dwNumMtrls= 0;                // 材质的数目

那么我们类成员变量的书写思路就出来了。

首当其冲的是D3D设备对象m_pd3dDevice,然后网格模型的指针LPD3DXMESH类型的m_pMesh自然要有,接着表示材质数量的m_dwNumMaterials要有,模型材质的结构体实例m_pMaterials要有,最后,自然就是模型纹理的结构体的实例m_pTextures了。

然后成员函数方面,构造函数析构函数依旧显式地写出来,然后就是接下来比较关键的成员函数了。不过这次的成员函数就两个就可以了,一个用于载入,一个用于绘制。模型的载入函数我们取名为LoadModelFromXFile,模型的绘制函数我们取名为RenderModel,好了,类轮廓就被我们勾勒出来了。就是如下,即我们贴出XFileModelClass.h的全部代码:

//=============================================================================
// Name: XFileModelClass.h
// Des: 一个封装了X文件载入与渲染功能的类头文件
// 2013年 4月7日  Create by 浅墨
//=============================================================================#pragma once
#include "D3DUtil.h"class XFileModelClass
{
private:LPDIRECT3DDEVICE9       m_pd3dDevice;     //D3D设备对象LPD3DXMESH           m_pMesh;          //网格模型对象DWORD             m_dwNumMaterials; //材质的数量D3DMATERIAL9*          m_pMaterials;     //模型材质结构体的实例LPDIRECT3DTEXTURE9 *      m_pTextures;      //模型纹理结构体的实例public:XFileModelClass(IDirect3DDevice9 *pd3dDevice); //构造函数~XFileModelClass(void);     //析构函数   public:HRESULT       LoadModelFromXFile(WCHAR* strFilename ); //从.X文件读取三维模型到内存中HRESULT       RenderModel( );  //渲染三维网格模型};

四、X文件模型载入类的实现

类轮廓的框架打好了,实现这个类还不是手到擒来的事。X文件模型的载入核心方法当然是已经被我们用了无数次的D3DXLoadMeshFromX方法。依旧是先复习一下我们在之前的示例程序中是如何载入X文件的,那个时候,我们总结了一个模型载入三步曲:

X文件模型载入三步曲之一,加载网格

X文件模型载入三步曲之二,加载材质纹理,

X文件模型载入三步曲之三,绘制

X文件模型载入三步曲的核心代码如下:

// 三步曲之一,从X文件中加载网格数据 LPD3DXBUFFERpAdjBuffer  = NULL; //网格模型邻接信息LPD3DXBUFFERpMtrlBuffer = NULL; //存储网格模型材质的缓存对象D3DXLoadMeshFromX(L"miki.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  = newLPDIRECT3DTEXTURE9[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) void Direct3D_Render(HWND hwnd)
{ g_pd3dDevice->Clear(0,NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,D3DCOLOR_XRGB(100, 100, 100), 1.0f, 0);
//三步曲之三,绘制
g_pd3dDevice->BeginScene();                     // 开始绘制 //用一个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); }
g_pd3dDevice->EndScene();                       // 结束绘制
g_pd3dDevice->Present(NULL, NULL, NULL,NULL);  // 翻转与显示 } 

既然核心代码就是如上的这些,之前的四个全局变量我们已经定义为了类成员变量,那么剩下的封装起来还不简单。说白了就是把X文件模型载入三步曲的前两步的代码copy到我们自定义的X文件模型载入函数LoadModelFromXFile的函数体中,把文件路径,即D3DXLoadMeshFromX函数的第一个参数用一个参数来表示,并且把全局的变量名改成类中定义的对应的成员变量名就好了。最后再用一下我们今天所学的网格优化知识,调用一下OptimizeInplace函数,于是LoadModelFromXFile函数的实现就是这样:

//--------------------------------------------------------------------------------------
// Name:XFileModelClass::LoadModelFromXFile()
// Desc: 从.X文件读取三维模型到内存中
//--------------------------------------------------------------------------------------
HRESULTXFileModelClass::LoadModelFromXFile( WCHAR* strFilename )
{LPD3DXBUFFERpAdjacencyBuffer = NULL;  //网格模型邻接信息LPD3DXBUFFERpD3DXMtrlBuffer = NULL;   //存储网格模型材质的缓存对象//从磁盘文件加载网格模型D3DXLoadMeshFromX(strFilename, D3DXMESH_MANAGED, m_pd3dDevice, &pAdjacencyBuffer,&pD3DXMtrlBuffer,NULL, &m_dwNumMaterials, &m_pMesh );//读取材质和纹理数据D3DXMATERIAL*d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer->GetBufferPointer();m_pMaterials= new D3DMATERIAL9[m_dwNumMaterials];m_pTextures  = new LPDIRECT3DTEXTURE9[m_dwNumMaterials];//逐子集提取材质属性和纹理文件名for(DWORD i=0; i<m_dwNumMaterials; i++ ){//获取材质,并设置一下环境光的颜色值m_pMaterials[i]= d3dxMaterials[i].MatD3D;m_pMaterials[i].Ambient= m_pMaterials[i].Diffuse;//创建一下纹理对象m_pTextures[i]= NULL;if(d3dxMaterials[i].pTextureFilename != NULL &&strlen(d3dxMaterials[i].pTextureFilename)> 0 ){//创建纹理if(FAILED( D3DXCreateTextureFromFileA(m_pd3dDevice,d3dxMaterials[i].pTextureFilename, &m_pTextures[i] ) ) ){MessageBox(NULL,L"SORRY~!没有找到纹理文件!", L"XFileModelClass类读取文件错误", MB_OK);}}}//优化网格模型m_pMesh->OptimizeInplace(D3DXMESHOPT_COMPACT | D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_STRIPREORDER,(DWORD*)pAdjacencyBuffer->GetBufferPointer(),NULL, NULL, NULL );returnS_OK;
}

接着,网格模型的绘制函数RenderModel的书写,参照着“X文件模型载入三步曲之三,绘制”,也就是一个for循环,无脑把代码拷过来,改一下变量名,于是,RenderModel函数就这样被我们瞬间无耻地写完了:

//--------------------------------------------------------------------------------------
// Name: XFileModelClass::RenderModel()
// Desc: 渲染三维网格模型
//--------------------------------------------------------------------------------------
HRESULT XFileModelClass::RenderModel( )
{for(DWORD i=0; i<m_dwNumMaterials; i++ ){m_pd3dDevice->SetMaterial(&m_pMaterials[i] );m_pd3dDevice->SetTexture(0, m_pTextures[i] );m_pMesh->DrawSubset(i );}returnS_OK;
}

构造函数和析构函数也没什么技术含量,构造函数就把D3D设备赋值了进来,析构函数就是在释放一些对象,实现代码如下:

//-----------------------------------------------------------------------------
// Desc: 构造函数
//-----------------------------------------------------------------------------
XFileModelClass::XFileModelClass(IDirect3DDevice9*pd3dDevice)
{     //给各个成员变量赋初值m_pd3dDevice= pd3dDevice;
}//-----------------------------------------------------------------------------
// Desc: 析构函数
//-----------------------------------------------------------------------------
XFileModelClass::~XFileModelClass(void)
{//释放网格模型材质SAFE_DELETE_ARRAY(m_pMaterials);//释放网格模型纹理if(m_pTextures ){for(DWORD i = 0; i < m_dwNumMaterials; i++ ){SAFE_RELEASE(m_pTextures[i]);}SAFE_DELETE_ARRAY(m_pTextures);}//释放网格模型对象SAFE_RELEASE(m_pMesh);
}

浅墨觉得我们这次的封装有够无耻的,就是在“炒现饭”,把之前用过的东西稍微改一下,用一个类把它们“盛”起来,然后就可以利用这个类,无限度地用相同的伎俩,淡淡地写几句代码,来载入无数的X文件模型了。这有点工厂中的加工车间的意思,把经得起推敲的技术弄成一套生产流水线,来批量生产。

五、X文件模型载入类的使用

这个类中,我们简化到只用四句代码就能载入并绘制出一个模型来,不过我们演示的是多个X文件模型的载入,所以就不止4行代码了,而是有几个模型就乘以4的代码量,另外我们还要给每个模型设置不同的世界矩阵,不然一会儿我们绘制的模型就挤到一块儿去了,所以代码量又稍微加了一点。

Ⅰ.首先,定义一个XFileModelClass类的全局指针实例:

XFileModelClass*                                         g_pXFileModel1  = NULL;   //模型类的第一个对象
XFileModelClass*                                         g_pXFileModel2  = NULL;        //模型类的第二个对象
XFileModelClass*                                     g_pXFileModel3  = NULL;        //模型类的第三个对象

Ⅱ.然后,在初始化阶段拿着类指针对象pXFileModel到处“指”,从X文件载入模型:

 HRESULT hr;//载入第一个模型g_pXFileModel1 = new XFileModelClass(g_pd3dDevice);HR( g_pXFileModel1->LoadModelFromXFile(L"knight.X" ));//载入第二个模型g_pXFileModel2 = new XFileModelClass(g_pd3dDevice);HR( g_pXFileModel2->LoadModelFromXFile(L"dragon.X" ));//载入第三个模型g_pXFileModel3 = new XFileModelClass(g_pd3dDevice);HR( g_pXFileModel3->LoadModelFromXFile(L"Demon.X" ));

Ⅲ.最后,就是在Render函数中给他们构建不同的世界矩阵,设置一个世界矩阵绘制一次,这样有顺序地做三次,分别绘制出三个模型来:

   //以下这段代码用于绘制游戏角色D3DXMATRIXmScal,mTrans1,mTrans2,mTrans3,mFinal1,mFinal2,mFinal3;   //定义一些矩阵,准备对模型进行矩阵变换//第一个角色的绘制,首先是“调合”出这个角色合适的世界矩阵,然后再绘制D3DXMatrixTranslation(&mTrans1,50.0f,1200.0f,0.0f);D3DXMatrixScaling(&mScal,3.0f,3.0f,3.0f);mFinal1=mScal*mTrans1*g_matWorld;g_pd3dDevice->SetTransform(D3DTS_WORLD,&mFinal1);//设置模型的世界矩阵,为绘制做准备g_pXFileModel1->RenderModel();//第二个角色的绘制,首先是“调合”出这个角色合适的世界矩阵,然后再绘制D3DXMatrixTranslation(&mTrans2,200.0f, 0.0f, 0.0f);mFinal2=mTrans2*mFinal1;g_pd3dDevice->SetTransform(D3DTS_WORLD,&mFinal2);g_pXFileModel2->RenderModel();//第三个角色的绘制,首先是“调合”出这个角色合适的世界矩阵,然后再绘制D3DXMatrixTranslation(&mTrans3,-200.0f, 0.0f, 0.0f);mFinal3=mTrans3*mFinal1;g_pd3dDevice->SetTransform(D3DTS_WORLD,&mFinal3);g_pXFileModel3->RenderModel();

通过类的封装,其实一个模型的载入与绘制最简化可以到两句代码,把LoadModelFromXFile函数中的内容直接放在构造函数中,然后在定义类对象的同时用构造函数初始化,然后世界矩阵也不设置直接裸奔绘制,不过这样的话,这个类就太怪异了- -。

六、详细注释的源代码欣赏

本篇文章配套的源代码在之前的基础上又增加了两个文件,也就是封装了X文件模型载入类的源文件和头文件。全部文件数量增加到了14个,它们的列表如下:

我们依旧只贴出核心代码main.cpp,其他的众多文件大家下源代码回去看就好了。

//*****************************************************************************************
//
//【Visual C++】游戏开发笔记系列配套源码五十一  浅墨DirectX教程十九 网格模型进阶之路
//       VS2010版
// 2013年 4月6日  Create by 浅墨
//图标素材出处: VAMPIRE_SWEETIE
//背景音乐素材出处:魔兽争霸3 暗夜精灵战斗曲
//人物模型素材出处:英雄无敌6
//
//***************************************************************************************** //*****************************************************************************************
// 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"
#include "XFileModelClass.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;                         //世界矩阵
D3DLIGHT9                               g_Light;   //全局光照
DInputClass*                                g_pDInput = NULL;              //DInputClass类的指针实例
CameraClass*                            g_pCamera = NULL;              //摄像机类的指针实例
TerrainClass*                               g_pTerrain = NULL;             //地形类的指针实例
SkyBoxClass*                                g_pSkyBox=NULL;                    //天空盒类的指针实例
SnowParticleClass*                      g_pSnowParticles = NULL;       //雪花粒子系统的指针实例
XFileModelClass*                        g_pXFileModel1  = NULL;    //模型类的第一个对象
XFileModelClass*                        g_pXFileModel2  = NULL;      //模型类的第二个对象
XFileModelClass*                        g_pXFileModel3  = 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,           //喜闻乐见的创建窗口函数CreateWindowWS_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 };  //初始化msgwhile( 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;   //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的elsevp = 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); // 设置光照  ::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, 1500.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\\SunSetFront2048.png",L"GameMedia\\SunSetBack2048.png",L"GameMedia\\SunSetRight2048.png",L"GameMedia\\SunSetLeft2048.png", L"GameMedia\\SunSetUp2048.png");//从文件加载前、后、左、右、顶面5个面的纹理图g_pSkyBox->InitSkyBox(50000);  //设置天空盒的边长//创建并初始化雪花粒子系统g_pSnowParticles = new SnowParticleClass(g_pd3dDevice);g_pSnowParticles->InitSnowParticle();HRESULT hr;//载入第一个模型g_pXFileModel1 = new XFileModelClass(g_pd3dDevice);HR( g_pXFileModel1->LoadModelFromXFile(L"knight.X" ));//载入第二个模型g_pXFileModel2 = new XFileModelClass(g_pd3dDevice);HR( g_pXFileModel2->LoadModelFromXFile(L"dragon.X" ));//载入第三个模型g_pXFileModel3 = new XFileModelClass(g_pd3dDevice);HR( g_pXFileModel3->LoadModelFromXFile(L"Demon.X" ));return S_OK;
}void               Direct3D_Update( HWND hwnd,FLOAT fTimeDelta)
{//使用DirectInput类读取数据g_pDInput->GetInput();// 沿摄像机各分量移动视角if (g_pDInput->IsKeyDown(DIK_A))  g_pCamera->MoveAlongRightVec(-1.0f);if (g_pDInput->IsKeyDown(DIK_D))  g_pCamera->MoveAlongRightVec( 1.0f);if (g_pDInput->IsKeyDown(DIK_W)) g_pCamera->MoveAlongLookVec( 1.0f);if (g_pDInput->IsKeyDown(DIK_S))  g_pCamera->MoveAlongLookVec(-1.0f);if (g_pDInput->IsKeyDown(DIK_R))  g_pCamera->MoveAlongUpVec( 1.0f);if (g_pDInput->IsKeyDown(DIK_F))  g_pCamera->MoveAlongUpVec(-1.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.0006f);g_pCamera->RotationRightVec(g_pDInput->MouseDY() * 0.0006f);//鼠标滚轮控制观察点收缩操作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,mTrans1,mTrans2,mTrans3,mFinal1,mFinal2,mFinal3;   //定义一些矩阵,准备对模型进行矩阵变换//第一个角色的绘制,首先是“调合”出这个角色合适的世界矩阵,然后再绘制D3DXMatrixTranslation(&mTrans1,50.0f,1200.0f,0.0f);D3DXMatrixScaling(&mScal,3.0f,3.0f,3.0f);mFinal1=mScal*mTrans1*g_matWorld;g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal1);//设置模型的世界矩阵,为绘制做准备g_pXFileModel1->RenderModel( );//第二个角色的绘制,首先是“调合”出这个角色合适的世界矩阵,然后再绘制D3DXMatrixTranslation(&mTrans2, 200.0f, 0.0f, 0.0f);mFinal2=mTrans2*mFinal1;g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal2);g_pXFileModel2->RenderModel( );//第三个角色的绘制,首先是“调合”出这个角色合适的世界矩阵,然后再绘制D3DXMatrixTranslation(&mTrans3, -200.0f, 0.0f, 0.0f);mFinal3=mTrans3*mFinal1;g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal3);g_pXFileModel3->RenderModel( );//绘制地形g_pTerrain->RenderTerrain(&g_matWorld, false);  //渲染地形,且第二个参数设为false,表示不渲染出地形的线框//绘制天空D3DXMATRIX matSky,matTransSky,matRotSky;D3DXMatrixTranslation(&matTransSky,0.0f,-12000.0f,0.0f);D3DXMatrixRotationY(&matRotSky, -0.00005f*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()函数,帧数自增1currentTime = 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接口对象SAFE_DELETE(g_pDInput);SAFE_RELEASE(g_pd3dDevice);SAFE_RELEASE(g_pTextAdaperName)SAFE_RELEASE(g_pTextHelper)SAFE_RELEASE(g_pTextInfor)SAFE_RELEASE(g_pTextFPS)SAFE_RELEASE(g_pd3dDevice)
}

这次的游戏场景有些西方风格的“暗黑”化,天空盒的纹理素材选择的是深色的傍晚,然后3个模型的选择之前已经说了,非常有代表性,来自地狱代表队的地狱恶魔一头,来自人类代表队的人族骑士一个,来自天堂代表队的天堂雷龙一只。

我们来看一下运行截图:

首先是纵览图一张:

然后三张模型的正面图:

然后三张侧面图:

再来一张纵览:

特写镜头一张:划破天际的锋利刀刃

文章最后,依旧是放出本篇文章配套源代码的下载:

本节笔记配套源代码请点击这里下载:

【浅墨DirectX提高班】配套源代码之十九下载

以上就是本节笔记的全部内容,更多精彩内容,且听下回分解。

浅墨在这里,希望喜欢游戏开发系列文章的朋友们能留下你们的评论,每次浅墨登陆博客看到大家的留言的时候都会非常开心,感觉自己正在传递一种信仰,一种精神。

文章最后,依然是【每文一语】栏目,今天的句子是:

成功的人都是从逆境中坚持下来的,我们应感谢生命赐予的不幸和挫折,让我们证明我们的坚强和勇敢。

下周一,让我们离游戏开发的梦想更近一步。

下周一,游戏开发笔记,我们,不见不散。

【Visual C++】游戏开发五十一 浅墨DirectX教程十九 网格模型进阶之路相关推荐

  1. 【Visual C++】游戏开发五十七 浅墨DirectX教程二十四 打造游戏GUI界面(二)

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/16922703 作者:毛星 ...

  2. 【Visual C++】游戏开发笔记四十一 浅墨DirectX教程之九 为三维世界添彩:纹理映射技术(一)...

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8523341 作者:毛星云(浅墨 ...

  3. 【Visual C++】游戏开发笔记四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8586540 作者:毛星云(浅墨 ...

  4. 【Visual C++】游戏开发笔记四十四 浅墨DirectX教程十二 网格模型和X文件使用面面观...

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/zhmxy555/article/details/8586540 作者:毛星云(浅墨) ...

  5. 【Visual C++】游戏开发笔记四十七 浅墨DirectX教程十五 翱翔于三维世界 摄像机的实现

    分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow 本系列文章由zhm ...

  6. 【Visual C++】游戏开发笔记四十七 浅墨DirectX教程十五 翱翔于三维世界:摄像机的实现...

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/zhmxy555/article/details/8657656 作者:毛星云(浅墨) ...

  7. 【Visual C++】游戏开发笔记四十七 浅墨DirectX教程十五 翱翔于三维世界:摄像机的实现

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处.   文章链接: http://blog.csdn.net/zhmxy555/article/details/8657656 作者:毛星云( ...

  8. 【Visual C++】游戏开发五十 浅墨DirectX教程十八 雪花飞扬:实现唯美的粒子系统...

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接:http://blog.csdn.net/zhmxy555/article/details/8744805 作者:毛星云(浅墨) ...

  9. 【Visual C++】游戏开发笔记四十六 浅墨DirectX教程十四 模板测试与镜面特效专场

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处.   文章链接: http://blog.csdn.net/zhmxy555/article/details/8632184 作者:毛星云( ...

最新文章

  1. window 注册表五大类
  2. hdu1505 暴力或dp优化
  3. window.setTimeout() 和window.setInterval() 使用说明
  4. Linux下配置汇编编译器NASM和bochs模拟器
  5. python面向对象(2)——继承(4)
  6. matlab算法用python做_机器学习笔记—朴素贝叶斯算法实现(matlab/python)
  7. hibernate教程笔记9
  8. eclipse一些实用小技巧
  9. 开发者如何在一周从入门级到专家级别的修炼
  10. python写彩票程序30选7_python实现彩票系统
  11. VSCode安装教程(超详细)
  12. kali linux win10桌面,一键伪装成Win 10,Kali Linux 2019年最终版重磅功能预览
  13. java 微博 开源_微博开源框架Motan初体验
  14. HashSet判断重复项原理
  15. 「玩转Python」突破封锁继续爬取百万妹子图
  16. 中国大学慕课mooc毛概考试答案参考
  17. 大型网络中内部网关路由协议(IGP)的选择
  18. tp框架该网页无法正常运作的一个坑
  19. 折腾词库,一个词库互转程序
  20. Linkerd2入门

热门文章

  1. 计算机网络冲刺串讲,计算机应用基础串讲冲刺讲义(二)
  2. 人机交互及界面设计序言
  3. 八大优势能否助JSF统一Web开发
  4. 运算放大器中的正负反馈判断和电压电流反馈判断
  5. 综合素质——作文——规则意识、人品教育、自信、用于探索、开拓前行;
  6. JavaMail详解
  7. Windows10 更新1607版本,系统自带浏览器Microsoft Edge收藏夹消失?
  8. 学习嵌入式linux与ARM开发板入门方法(一)
  9. Jenkins使用6--jenkins git timeout
  10. Ubuntu升级软件和Ubuntu升级系统的几条简单命令