本章我们介绍的知识点是网格模型的优化以及克隆,再把之前讲解网格模型那章中讲到的X文件的载入方法封装到了一个类中。
最后就可以用几行代码,来载入多个3D 模型到我们的游戏场景中了。
25.1 网格模型的优化
我们在第17 章讲过一次网格模型,如果大家还不熟练, 请移步到第17 章把之前的知识复习一 下吧。本章我们将接触到网格模型的优化相关知识。无论是用手编(如果你真有这个毅力这么无聊的 话)或者使用三维建模软件(3DS Max, Maya )所创建出的网格模型,都可能包含一些 无用的顶点和l索引,况且网格中属性缓存中子集的顺序也不一定是有序的。所以,如果想要 提高游戏程序的运行效率的话, 网格模型的优化是可以做出卓越贡献的一块大头。
Direct3D 的ID3DMesh 接口为我们提供了ID3DXMesh :: Optimizelnplace 和 ID3DXMesh: :Optimize 这两种方法来对网格进行优化。大家都知道Optimize, 是优化的意思。两者 的区别就在这个多出来的Inplace 之上, in place 是在原位的意思,那么Optimizelnplace 函数就是在 原来的位置上进行优化,直接对原网格进行优化, 而不带Inplace 的Optimize 方法就生成一个优化 后的新的网格对象。即调用Optimizelnplace 函数进行优化,优化的是原网格,而调用Optimize 函 数进行优化,原网格不变, 会返回一个优化后的新网格。
我们先对Optimizelnplace 方法进行剖析, MSDN 中可以查到它的原型如下:
HRESULT OptimizeInplace(
[in] DWORD Flags,
[in] const DWORD *pAdjacencyIn,
[out] DWORD *pAdjacencyOut,
[out] DWORD *pFaceRemap,
[out] LPD3DXBUFFER *ppVertexRemap
);
- 第一个参数, DWORD 类型的Flags ,表示执行什么类型的优化方法。它在D3DXMESHOPT枚举中取一个或者多个的值,常用的一些值如下表所示:
需要注意的是, D3DXMESHOPT_VERTEXCACHE 和D3DXMESHOPT_STRIPREORDER这两者的领域重合了,不能同时使用。
- 第二个参数, const DWORD 类型的*pAdjacencyln ,指向优化前的邻接数组的指针, 一般我们这样填(DWORD* )pAdjacencyBuffer->GetBufferPointer(), 其中的pAdjacencyBuffer 我们在D3DXLoadMeshFromX 中给它附上了被载入网格的邻接信息,然后GetBufferPointer 一下就是指向待优化的邻接数组的指针了。
- 第三个参数,DWORD 类型的*pAdjacencyOut,指向优化后的邻接数组的指针。如果不需要优化后的邻接信息,设为NULL
- 第四个参数, DWORD 类型的*pFaceRemap , 用来填充面重映射信息。该数组必须不小于lD3DXMesh :: 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
);
这个方法的前5 个参数和Optimizelnplace 方法的完全一模一样,大家照着Optimizelnplace 看就可以了。因为Optimize 方法不修改原网格,所以需要额外准备一个参数来存放指向新网格的指针,这就有了它的第六个参数, LPD3DXMESH 类型的*ppOptMesh 。
另外, 关于模型的优化问题需要注意,有些模型在制作过程中已经做了优化,所以有可能再优化也优化不出什么内容了。如果是载入一些没有经过优化或者是粗制滥造的3D 模型,优化的效果就体现出来了,可以大大增加每秒渲染的帧数。
25.2 网格模型的克隆
我们也可能用到网格的克隆技术。网格的克隆是重新生成网格数据的一种方式。通过这种方式, 我们可以创建出一个与原网格相同的网格模型,而且还能通过克隆选项重新指定网格数据的某些特 殊的特性。网格的克隆是三维模型常用的一个概念,3DS Max与Maya 中就有专门的功能,而在 Direct3D 中,也有专门的函数来对应,它就是ID3DXBaseMesh: :CloneMeshFVF 方法,我们来讲一下:
HRESULT CloneMeshFVF(
[in] DWORD Options,
[in] DWORD FVF,
[in] LPDIRECT3DDEVICE9 pDevice,
[out, retval] LPD3DXMESH *ppCloneMesh
);
- 第一个参数, DWORD 类型的Options, 表示创建克隆网格时的一些选项,在一个庞大的D3DXMESH 枚举体中取值, 完整的介绍在这里就省略了,意义不大。我们介绍几个常用的就行了,如下表。其他的很少用到,就算用到了,我们可以到MSDN 查阅相关信息。
- 第二个参数, DWORD 类型的FVF , 很好理解,就是为克隆出的新网格指定FVF 灵活顶点格式,这就说明了克隆出的新网格是完全可以和原网格有不同的FVF 灵活顶点格式的。
- 第三个参数, LPDIRECT3DDEVICE9 类型的pDevice,我们的金钥匙Direct3D设备指针。
- 第四个参数, LPD3DXMESH 类型的*ppCloneMesb , 指向克隆出的网格模型的指针。我们事先新建一个网格模型指针,然后调用CloneMesbFVF 函数的时候填在这里, 调用完之后,这个指针就指向新克隆好的网格模型了。
// 其中的m_pMesh为需要被克隆的原模型指针,为LPD3DXMESH 类型
LPD3DXMESH pNewMesh;
m_pMesh->CloneMeshFVF(D3DXMESH_MANAGED, m_pMesh->GetFVF() | D3DFVF _NORMAL, g_pd3dDevice, &pNewMesh);
25.3 文件模型载入类的设计
本章的任务是把之前的X 文 件的载入与绘制功能封装在一个类中,这样我们就可以在游戏程序中使用这个类, 随便几行代码就 可以载入井绘制多个好看的3D 模型来。从某种意义上来说, 类的出现就是为了避免重复的劳动。在游戏程序编写过程中,多个3D 模 型的载入必然是需要一个或者多个专门的类来管理的, 如果像我们之前的那样, 每载入一个3D 模 型就要定义一堆全局变量, 然后重复写一堆代码,完全就是乱来了。等到后面需要绘制的东西越来 越多, 到最后也许我们自己写出来的程序源码,都看不懂了。
我们在上一章的工程基础上新建一个C++类,命名为XFileModelClass 。依然是老规矩,看看 需要哪些成员变量。
首先理一下思路, 我们注意到之前的示例程序中与X 模型载入有关的全局变量有这些:
LPD3DXMESH m_pMesh; //网格模型对象
DWORD m_dwNumMaterials; //材质的数量
D3DMATERIAL9* m_pMaterials; //模型材质结构体的实例
LPDIRECT3DTEXTURE9 * m_pTextures; //模型纹理结构体的实例
那么类成员变量的书写思路就出来了。首先需要的是D3D 设备对象m_pd3dDevice ,然后网格模型的指针LPD3DXMESH 类型的m_pMesh 自然要有,接着表示材质数量的m_dwNumMaterials 要有,模型材质的结构体实例m_pMaterials 要有,最后,就是模型纹理的结构体的实例m_pTextures 了。
成员函数方面, 构造函数析构函数依旧显式地写出来,接下来就是比较关键的成员函数了。不过这次的成员函数两个就可以了, 一个用于载入, 一个用于绘制。模型的载入函数取名为LoadModelFromXFile,模型的绘制函数取名为RenderModel,好了, 类轮廓就被我们勾勒出来了,即XFileModelClass.h 的全部代码:
//=============================================================================
// Name: XFileModelClass.h
// Des: 一个封装了X文件载入与渲染功能的类头文件
//=============================================================================
#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( ); //渲染三维网格模型
};
25.4 文件模型载入类的实现
类轮廓的框架搭好了,实现这个类就是手到擒来的事了。X 文件模型的载入核心方法当然是已经被我们用了无数次的D3DXLoadMeshFromX 方法。先复习一下之前讲解过的示例程序是如何载入X 文件的,当时,我们总结了一个模型载入三步曲:
- X文件模型载入三步曲之一,加载网格;
- X文件模型载入三步曲之二,加载材质纹理;
- X 文件模型载入三步曲之三,绘制。
之前的4 个全局变量已经定义为类成员变量,只要把剩下的封装起来就可以了。说白了就是把X 文件模型载入三步曲的前两步的代码copy 到我们自定义的X 文件模型载入函数LoadModelFrornXFile 的函数体中,把文件路径,即D3DXLoadMeshFrornX 函数的第一
个参数用一个参数来表示,并且把全局的变量名改成类中定义的对应的成员变量名就好了。最后再用一下我们刚学的网格优化知识,调用一下Optimizelnplace 函数, 于是LoadModelFromXFile 函数的实现就是这样:
//--------------------------------------------------------------------------------------
// Name: XFileModelClass::LoadModelFromXFile()
// Desc: 从.X文件读取三维模型到内存中
//--------------------------------------------------------------------------------------
HRESULT XFileModelClass::LoadModelFromXFile( WCHAR* strFilename )
{
LPD3DXBUFFER pAdjacencyBuffer = NULL; //网格模型邻接信息
LPD3DXBUFFER pD3DXMtrlBuffer = 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 );
return S_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 );
}
return S_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);
}
25.5 文件模型载入类的使用
这个类中,我们简化到只用4 行代码就能载入并绘制出一个模型来,不过我们演示的是多个X 文件模型的载入,所以就不止4 行代码了,而是有几个模型就乘以4 的代码量。另外我们还要给每 个模型设置不同的世界矩阵,否则一会儿我们绘制的模型就挤到一块儿去了,所以代码量又稍微加 了一点。本节我们演示载入3 个模型的步骤。1:首先,定义一个XFileModelClass 类的全局指针实例:
XFileModelClass* g_pXFileModel1 = NULL; //模型类的第一个对象
XFileModelClass* g_pXFileModel2 = NULL; //模型类的第二个对象
XFileModelClass* g_pXFileModel3 = NULL; //模型类的第三个对象
2:然后,在初始化阶段拿着类指针对象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" ));
3:最后,就是在Render 函数中给它们构建不同的世界矩阵,设置一个世界矩阵绘制一次,
这样有顺序地做三次,分别绘制出三个模型来:
//-----------------------------【绘制游戏模型】-----------------------------
//定义一些矩阵,准备对模型进行矩阵变换
D3DXMATRIX mScal1,mScal2,mTrans1,mTrans2,mTrans3,mTrans4;
D3DXMATRIX mFinal1,mFinal2,mFinal3,mFinal4;
//第一个模型的绘制,首先是“调合”出这个模型合适的世界矩阵,然后再绘制
D3DXMatrixTranslation(&mTrans1,50.0f,400.0f,0.0f);
D3DXMatrixScaling(&mScal1,3.0f,3.0f,3.0f);
mFinal1=mTrans1*mScal1*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( );
25.6 示例程序D3Ddemo20
本节示例程序在之前示例的基础上又增加了两个文件,就是封装了X 文件模型载入类的源文 件和头文件。全部文件数量增加到了14 个,它们的列表如下图所示。我们依旧只贴出经过详细注释了的main.cpp 中的具有代表性的核心代码,其他文件大家在本书代码包中找到工程用Visual Studio 运行然后查看即可。
首先还是用于载入游戏资源的Object_Init()函数:
//-----------------------------------【Object_Init( )函数】--------------------------------------
// 描述:渲染资源初始化函数,在此函数中进行要被渲染的物体的资源的初始化
//--------------------------------------------------------------------------------------------------
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, 1200.0f, -2100.0f)); //设置摄像机所在的位置
g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 700.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" ));
//载入第四个模型
g_pXFileModel4 = new XFileModelClass(g_pd3dDevice);
HR( g_pXFileModel4->LoadModelFromXFile(L"angle.X" ));
return S_OK;
}
接着,依然是负责渲染代码书写的Direct3D_Render()函数的实现代码:
//-----------------------------------【Direct3D_Render( )函数】-------------------------------
// 描述:使用Direct3D进行渲染
//--------------------------------------------------------------------------------------------------
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 mScal1,mScal2,mTrans1,mTrans2,mTrans3,mTrans4;
D3DXMATRIX mFinal1,mFinal2,mFinal3,mFinal4;
//第一个模型的绘制,首先是“调合”出这个模型合适的世界矩阵,然后再绘制
D3DXMatrixTranslation(&mTrans1,50.0f,400.0f,0.0f);
D3DXMatrixScaling(&mScal1,3.0f,3.0f,3.0f);
mFinal1=mTrans1*mScal1*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( );
//第四个模型的绘制,首先是“调合”出这个模型合适的世界矩阵,然后再绘制
D3DXMatrixScaling(&mScal2,10.0f,10.0f,10.0f);
D3DXMatrixTranslation(&mTrans4, 10.0f, 20.0f, 250.0f);
mFinal4=mTrans4*mScal2*mFinal1;
g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal4);
g_pXFileModel4->RenderModel( );
//-----------------------------【绘制地形】-----------------------------
g_pTerrain->RenderTerrain(&g_matWorld, false); //渲染地形,且第二个参数设为false,表示不渲染出地形的线框
//-----------------------------【绘制天空】-----------------------------
D3DXMATRIX matSky,matTransSky,matRotSky;
D3DXMatrixTranslation(&matTransSky,0.0f,-12000.0f,0.0f);
D3DXMatrixRotationY(&matRotSky, -0.00002f*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); // 翻转与显示
}
上面的43~48 行代码中,我们通过矩阵的缩放,把大天使的模型在其他三个人物模型的基础
上放大了很多倍, 来获得不凡的视觉效果。依依不舍地放出最后一个游戏场景示例程序的截图吧:
大天使特效镜头
25.7 章节小憩
美好的时光总是短暂的,Direct3D 这个博大精深的图形API 在本书中就介绍这么多了。我们学到这里,从对游戏编程一无所知到可以写出酷而真实的三维游戏场景demo 来,已经非常了不起了,不是吗?
下一步的学习怎么进行呢?也请大家放心,在书本的附录中为大家整理了进阶学习的参考书籍和资料。没有这本书的陪伴,后续的路途,愿大家能坚持最初的梦想, 无所畏惧地走下去。