您的位置:首页 > 编程语言 > C语言/C++

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

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

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

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

骨骼动画的前置知识是网格模型(把“前置”这两个字在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中我们可以查到它的原型如下:

[cpp]
view plaincopyprint?

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就行了,就像这样:

[cpp]
view plaincopyprint?

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

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

[cpp]
view plaincopyprint?

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方法,我们来讲一下:

[cpp]
view plaincopyprint?

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函数的时候填在这里,调用完之后,这个指针就指向新克隆好的网格模型了。

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

[cpp]
view plaincopyprint?

//其中的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模型载入有关的全局变量有这些:

[cpp]
view plaincopyprint?

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的全部代码:

[cpp]
view plaincopyprint?

//=============================================================================
// 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; <span style="white-space:pre"> </span> //网格模型对象
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文件模型载入三步曲的核心代码如下:

[cpp]
view plaincopyprint?

// 三步曲之一,从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函数的实现就是这样:

[cpp]
view plaincopyprint?

//--------------------------------------------------------------------------------------
// 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函数就这样被我们瞬间无耻地写完了:

[cpp]
view plaincopyprint?

//--------------------------------------------------------------------------------------
// 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设备赋值了进来,析构函数就是在释放一些对象,实现代码如下:

[cpp]
view plaincopyprint?

//-----------------------------------------------------------------------------
// 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类的全局指针实例:

[cpp]
view plaincopyprint?

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

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

[cpp]
view plaincopyprint?

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函数中给他们构建不同的世界矩阵,设置一个世界矩阵绘制一次,这样有顺序地做三次,分别绘制出三个模型来:

[cpp]
view plaincopyprint?

//以下这段代码用于绘制游戏角色
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,其他的众多文件大家下源代码回去看就好了。

[cpp]
view plaincopyprint?

//*****************************************************************************************
//
//【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, //喜闻乐见的创建窗口函数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);

// 设置光照
::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()函数,帧数自增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接口对象
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提高班】配套源代码之十九下载

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

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

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

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



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

下周一,游戏开发笔记,我们,不见不散。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐