第25章 造物主的降临——多游戏模型的载入

章节导读
本章我们介绍的知识点是网格模型的优化以及克隆,再把之前讲解网格模型那章中讲到的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 来,已经非常了不起了,不是吗?
下一步的学习怎么进行呢?也请大家放心,在书本的附录中为大家整理了进阶学习的参考书籍和资料。没有这本书的陪伴,后续的路途,愿大家能坚持最初的梦想, 无所畏惧地走下去。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值