第23章 向碧蓝的苍穹致敬——三维天空的构建

章节导读

本章准备跟大家一起探讨一下三维天空的几种实现方式,然后在几种方式之中选择最常用的一种进行重点突破,用一个C++类把这种三维天空的实现方式封装起来。这样以后要使用三维天空来辅助绘制某个游戏场景的话, 准备好天空的纹理图,然后简单地敲几行代码, 调用一下这个天空类中我们亲手写出来的函数就可以搞定了。

23.1 三维天空技术阐述

游戏世界中的天空好像是无边无际的,其实我们都被骗了。
在计算机的三维世界中, 三维天空的绘制肯定不可能像现实生活中的天空一样, 一望无际绵延 无尽, 往往是通过一种假象来实现的。这种假象与古代人所说的“ 天圆地方” 有着异曲同工之妙。 反正就是一个足够大的容器一样的东西把我们罩在里面,让我们像井底之蛙一样以为这就是整个世 界, 世界就这么大,天空就这么大。而这个足以罩住我们视线的游戏世界的容器, 可以是一个立方 体,也可以是半球,甚至是一个足够大的平面。
目前描述三维天空的技术主要包括三种类型:
(1)平面型天空( Sky Plane ),仅用一个平面放到玩家头顶。这种方案太弱了,太容易被玩 家们看穿, 真实感太低,技术含量也太低。但是对于并不太注意远景的场景,用天空平面也不失为 一种办法。这种情况下,用纯色的雾来覆盖整个远景,使得远处充满神秘,遮一下羞效果也凑合。
( 2 )天空穹庐( Sky Dome ) , 放到玩家头顶上的是一个曲面,通常都会为一个半球。就像这样:


这种方案其实真实性最强,但是不是目前使用最广泛的方案,它涉及到天空无缝衔接的素材匮乏等的问题。
(3) 天空盒( Sky Box ),即放到场景的是一个立方体。它是目前使用最广泛的三维天空模拟技术,网络上素材丰富,所以本节就教大家用天空盒来模拟三维天空。天空盒经常是由24 个顶点、六个面组成的立方体(或者直接从做好的X 模型文件载入天空盒),并经常会随着视点的移动而移动, 来刻画极远处玩家无法达到位置的天空。


生成天空盒对于我们来说并不是困难的事情, 但是真正要在游戏中使得天空“好看”, 还需要有着漂亮的天空纹理素材图,可以在网上搜罗(下文有讲如何搜索〉,也可以拜托给美工师们。
另外, 在高级一些的应用中, 天空盒的纹理可能同时会用来生成Cube Map , 并用之来做水面倒影、云影、反光等很眩的特效, 大家先有一个这方面的概念就好。

23.2  天空盒的设计

1 . 准备天空盒纹理素材
天空盒的纹理自然就是我们这个天空盒子立方体每个面的纹理了, 至少5 个面, 最多6 个面, 因为底面处是我们所在的土地,是地形,也就不用渲染为天空了。
这5个面可以分别单独成文件,像这样:



这5 张纹理需要满足的条件是: 按照规定的几个面拼接起来, 能构成一幅360 度并包含顶部的无缝衔接的全景图;另外,有些游戏引擎设定了需要把5 个面按某种方式连起来合成一幅图来使用, 就像下面的天空盒素材:


互联网上关于天空盒的纹理素材资源很丰富,大家Google/百度就可以找到很多资源的下载点。
建议用关键字skybox texture 或者skybox download 来搜索。
另外,如果想原创天空盒纹理的话,可以用DirectX SDK 中自带的DirectX Texture tool 工具完成。


2. 天空盒类的设计
好了, 开始我们的本职工作,写代码吧。
今天的任务是写一个封装了天空盒渲染功能的类,我们给这个类取名为SkyBoxClass 。我们来看下这个类中有哪些内容。
最开始国际惯例, LPDIRECT3DDEVICE9 类型的设备接口指针m_pd3dDevice 自然不能少。
然后这个类中需要处理24 个带纹理坐标的顶点来构成一个立方体盒子, 自然少不了FVF 灵活顶点格式和一个

DIRECT3DVERTEXBUFFER 接口的指针。
接着还要有5 个纹理对象,分别储存5个面上的纹理图,所以一个LPDIRECT3DTEXTURE9类型的m_pTexture[5] 自然也少不了。最后,还需要定义一个float 类型的m_Length 表示天空盒的边长。

结构体和成员变量就是这些了,我们再来看一下需要哪些成员函数。
首先构造函数析构函数已经写出来了,接着再写3 个函数就够了,它们分别是初始化天空盒顶点的InitSkyBox 函数,加载纹理的LoadSkyTextureFrornFile 函数, 渲染天空盒的RenderSkyBox函数。
SkyBoxClass 类的轮廓就是这样了,把上面我们的思路实变成代码就是如下SkyBoxClass.h 中全部代码:

//=============================================================================
// Name: SkyBoxClass.h
//	Des: 一个封装了三维天空盒系统的类的头文件
//=============================================================================
#pragma once
#include "D3DUtil.h"

//为天空盒类定义一个FVF灵活顶点格式
struct SKYBOXVERTEX
{
	float	x,y,z;
	float	u,v;
};
#define D3DFVF_SKYBOX D3DFVF_XYZ|D3DFVF_TEX1


class SkyBoxClass
{
private:
	LPDIRECT3DDEVICE9				m_pd3dDevice;			//D3D设备对象
	LPDIRECT3DVERTEXBUFFER9	m_pVertexBuffer;		//顶点缓存对象
	LPDIRECT3DTEXTURE9			m_pTexture[5];			//5个纹理接口对象
	float										m_Length;					//天空盒边长

public:
	SkyBoxClass( LPDIRECT3DDEVICE9  pDevice );		//构造函数
	virtual ~SkyBoxClass(void);								//析构函数

public:
	BOOL	InitSkyBox( float Length );   //初始化天空盒函数
	BOOL	LoadSkyTextureFromFile(wchar_t *pFrontTextureFile, wchar_t *pBackTextureFile,wchar_t *pLeftTextureFile, wchar_t *pRightTextureFile,wchar_t *pTopTextureFile);  //从文件加载天空盒五个方向上的纹理
	VOID		RenderSkyBox( D3DXMATRIX *pMatWorld, BOOL bRenderFrame ); //渲染天空盒,根据第一个参数设定天空盒世界矩阵,第二个参数选择是否渲染出线框

};

23.3 天空盒类的实现

类的框架勾勒出来了,接下来就很简单,分别在类的cpp 文件中实现类成员函数就好了。
1 . 构造函数的实现
首先是类构造函数, 蛮简单,直接对着看类定义中有哪些变量,分别赋初值就行。除了Direct3D设备对象赋值成通过函数形参传进来的设备对象指针pDevice 之外,其他的参数根据类型统统取NULL 或者 0.0f:

//-------------------------------------------------------------------------------------------------
// Desc: 构造函数
//-------------------------------------------------------------------------------------------------
SkyBoxClass::SkyBoxClass( LPDIRECT3DDEVICE9  pDevice )
{
	//给各个参数赋初值
	m_pVertexBuffer=NULL;
	m_pd3dDevice=pDevice;
	for(int i=0; i<5; i++)
		m_pTexture[i] = NULL; 
	m_Length = 0.0f;
}
2. 顶点初始化函数lnitSkyBox 的实现

接下来要实现的就是最关键的顶点初始化函数InitSkyBox。首先,通过形参把天空盒的边长传给代表边长的成员函数m_Length。接着就是我们熟悉的顶点缓存使用四步曲的二、三两步一一创建顶点缓存、访问顶点缓存了。

我们在讲解纹理映射时就给出了立方体表面贴纹理的24 个顶点实现方法,这里的思路基本和之前讲的相同,而与D3D 实现的普通立方体贴图不同的一点是, 大部分情况下视点都包容在天空盒内部,因此,天空盒的顶点顺序应当是正好与我们之前讲的普通立方体纹理映射的顶点顺序相反。
所以, InitSkyBox 函数的实现代码就是这样:

//-------------------------------------------------------------------------------------------------
// Name:  SkyBoxClass::InitSkyBox( )
// Desc: 天空盒初始化函数,顶点缓冲区的赋值
//-------------------------------------------------------------------------------------------------
BOOL SkyBoxClass::InitSkyBox( float Length )
{
	m_Length=Length;

	//1.创建。创建顶点缓存
	 m_pd3dDevice->CreateVertexBuffer( 20 * sizeof(SKYBOXVERTEX), 0, 
		D3DFVF_SKYBOX, D3DPOOL_MANAGED, &m_pVertexBuffer, 0 );

	//用一个结构体把顶点数据先准备好
	SKYBOXVERTEX vertices[] =
    {
		//前面的四个顶点
		{ -m_Length/2, 0.0f,    m_Length/2, 0.0f, 1.0f, },
		{ -m_Length/2, m_Length/2,   m_Length/2, 0.0f, 0.0f, },
		{  m_Length/2, 0.0f,    m_Length/2, 1.0f, 1.0f, },
		{  m_Length/2, m_Length/2,   m_Length/2, 1.0f, 0.0f, },

		//背面的四个顶点
		{  m_Length/2, 0.0f,   -m_Length/2, 0.0f, 1.0f, },
		{  m_Length/2, m_Length/2,  -m_Length/2, 0.0f, 0.0f, },
		{ -m_Length/2, 0.0f,   -m_Length/2, 1.0f, 1.0f, },
		{ -m_Length/2, m_Length/2,  -m_Length/2, 1.0f, 0.0f, },

		//左面的四个顶点
		{ -m_Length/2, 0.0f,   -m_Length/2, 0.0f, 1.0f, },
		{ -m_Length/2, m_Length/2,  -m_Length/2, 0.0f, 0.0f, },
		{ -m_Length/2, 0.0f,    m_Length/2, 1.0f, 1.0f, },
		{ -m_Length/2, m_Length/2,   m_Length/2, 1.0f, 0.0f, },

		//右面的四个顶点
		{ m_Length/2, 0.0f,   m_Length/2, 0.0f, 1.0f, },
		{ m_Length/2, m_Length/2,  m_Length/2, 0.0f, 0.0f, },
		{ m_Length/2, 0.0f,  -m_Length/2, 1.0f, 1.0f, },
		{ m_Length/2, m_Length/2, -m_Length/2, 1.0f, 0.0f, },

		//上面的四个顶点
		{  m_Length/2, m_Length/2, -m_Length/2, 1.0f, 0.0f, },
		{  m_Length/2, m_Length/2,  m_Length/2, 1.0f, 1.0f, },
		{ -m_Length/2, m_Length/2, -m_Length/2, 0.0f, 0.0f, },
		{ -m_Length/2, m_Length/2,  m_Length/2, 0.0f, 1.0f, },

    };

	//准备填充顶点数据
    void* pVertices;
	//2.加锁
    m_pVertexBuffer->Lock( 0, 0, (void**)&pVertices, 0 );
	//3.访问。把结构体中的数据直接拷到顶点缓冲区中
    memcpy( pVertices, vertices, sizeof(vertices) );
	//4.解锁
	m_pVertexBuffer->Unlock();

	 return TRUE;
}
3. 纹理载入函数LoadSkyTextureFromFile 的写法

接下来看看纹理载入函数LoadSkyTextureFrornFile的写法,实在是非常的简单。
给出5 个文件路径,传进来调用5次D3DXCreateTextureFrornFile 函数载入纹理到m_pTexture[]数组中就好了:

//-------------------------------------------------------------------------------------------------
// Name:  SkyBoxClass::LoadSkyTextureFromFile( )
// Desc: 天空盒纹理加载函数
//-------------------------------------------------------------------------------------------------
BOOL SkyBoxClass::LoadSkyTextureFromFile(wchar_t *pFrontTextureFile, wchar_t *pBackTextureFile,wchar_t *pLeftTextureFile, wchar_t *pRightTextureFile,wchar_t *pTopTextureFile)
{
	//从文件加载五张纹理
	D3DXCreateTextureFromFile( m_pd3dDevice , pFrontTextureFile, &m_pTexture[0] );  //前面
	D3DXCreateTextureFromFile( m_pd3dDevice , pBackTextureFile,  &m_pTexture[1] );  //后面
	D3DXCreateTextureFromFile( m_pd3dDevice , pLeftTextureFile,  &m_pTexture[2] );  //左面
	D3DXCreateTextureFromFile( m_pd3dDevice , pRightTextureFile, &m_pTexture[3] );  //右面
	D3DXCreateTextureFromFile( m_pd3dDevice , pTopTextureFile,   &m_pTexture[4] );  //上面	
	return TRUE;
}

4 . 渲染函数RendeSkyBox
再看看用于渲染天空盒的RenderSkyBox 函数。其中我们用到了讲解纹理映射的时候没有讲到的纹理阶段混合操作,这里我们顺便讲一下。
纹理映射的本质实际上就是从纹理中获取颜色值,然后应用到物体表面上。后而我们会接触到的多次纹理映射就是混合多层纹理的颜色,然后应用到物体表面。为了处理上的方便, Direct3D 将颜色的RGB 通道和Alpha 通道分开来进行处理, 具体的操作方法就是通过纹理阶段状态( Texture Stage State ) 的设置。
其实也就是一个函数IDirect3DDevice9::SetTextureStageState 的用法,在MSDN 中查到这个函数原型如下:

HRESULT SetTextureStageState(
  [in]  DWORD Stage,
  [in]  D3DTEXTURESTAGESTATETYPE Type,
  [in]  DWORD Value
);


  • 第一个参数, DWORD 类型的Stage,指定当前设置的纹理层为第几层(有效值0~7 ) 。
  • 第二个参数, D3DTEXTURESTAGESTATETYPE 类型的Type,填将要设置的纹理渲染状态,在枚举类型D3DTEXTURESTAGESTATETYPE 中任意取值。先看完第三个参数,然后一起看一下这个D3DTEXTURESTAGESTATETYPE 枚举类型。
  • 第三个参数, DWORD 类型的Value , 表示所设置的状态值,它是根据第二个参数来决定具体取什么值的。
下面一起看一下D3DTEXTURESTAGESTATETYPE 枚举类型的定义:

typedef enum D3DTEXTURESTAGESTATETYPE {
  D3DTSS_COLOROP                 = 1,
  D3DTSS_COLORARG1               = 2,
  D3DTSS_COLORARG2               = 3,
  D3DTSS_ALPHAOP                 = 4,
  D3DTSS_ALPHAARG1               = 5,
  D3DTSS_ALPHAARG2               = 6,
  D3DTSS_BUMPENVMAT00            = 7,
  D3DTSS_BUMPENVMAT01            = 8,
  D3DTSS_BUMPENVMAT10            = 9,
  D3DTSS_BUMPENVMAT11            = 10,
  D3DTSS_TEXCOORDINDEX           = 11,
  D3DTSS_BUMPENVLSCALE           = 22,
  D3DTSS_BUMPENVLOFFSET          = 23,
  D3DTSS_TEXTURETRANSFORMFLAGS   = 24,
  D3DTSS_COLORARG0               = 26,
  D3DTSS_ALPHAARG0               = 27,
  D3DTSS_RESULTARG               = 28,
  D3DTSS_CONSTANT                = 32,
  D3DTSS_FORCE_DWORD             = 0x7fffffff 
} D3DTEXTURESTAGESTATETYPE, *LPD3DTEXTURESTAGESTATETYPE;
大家可以看到这个枚举中的参数非常多,我们重点看一下前两个参数。

  • D3DTSS_COLOROP :指定纹理颜色的混合方法,对应的Value 值( SetTextureStageState第三个参数)在D3DTEXTUREOP 枚举类型中取值。我们把几种常用的列出来就好了。Value 值取D3DTOP_DISABLE 表示禁用当前纹理层颜色输出: Value 值取D3DTOP_SELECTARG1 或者D3DTOP_SELECTARG2 ,分别表示将颜色混合阶段的第一个或者第二个参数的颜色值或者Alpha值输出。Value 值取D3DTOP_MODULATE 表示将颜色混合阶层的第一个和第二个颜色相乘并输出。
  • D3DTSS_COLORAG1 :取这个值的话表示对纹理颜色混合阶段的第一个参数进行操作,而它的Value 值在D3DTA 常量中取值,默认值为D3DTA_TEXTURE , 表示这个纹理阶段的参数就取纹理的颜色。
然后我们看一看RenderSkyBox 函数中用到的两句关于纹理阶段状态的代码:

m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1);  //将纹理颜色混合的第一个参数的颜色值用于输出
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );   //纹理颜色混合的第一个参数的值就取纹理颜色值
第一句SetTextureStageState中我们表示要将纹理颜色混合的第一个参数的颜色值用于输出,然后第二句马上就把第一个参数的颜色值取为纹理颜色值了,这样我们颜色混合后的值就是纹理的颜色值。
解决了纹理Alpha 混合的问题,后面就好解决了,设置镜像寻址模式防止纹理衔接间的缝隙,设置世界矩阵,关联顶点和渲染流水线,设置顶点格式,接着一个for 循环设置纹理并渲染,最后再判断一下是否要绘制出线框, 一气呵成。实现代码就是这样:

//--------------------------------------------------------------------------------------
// Name: SkyBoxClass::RenderSkyBox()
// Desc: 绘制出天空盒,可以通过第二个参数选择是否绘制出线框
//--------------------------------------------------------------------------------------
void SkyBoxClass::RenderSkyBox( D3DXMATRIX *pMatWorld, BOOL bRenderFrame )
{
	m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1);  //将纹理颜色混合的第一个参数的颜色值用于输出
	m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );   //纹理颜色混合的第一个参数的值就取纹理颜色值
	m_pd3dDevice->SetTransform( D3DTS_WORLD, pMatWorld );  //设置世界矩阵
	m_pd3dDevice->SetStreamSource(0,m_pVertexBuffer, 0, sizeof(SKYBOXVERTEX));    //把包含的几何体信息的顶点缓存和渲染流水线相关联  
	m_pd3dDevice->SetFVF(D3DFVF_SKYBOX);  //设置FVF灵活顶点格式

	//一个for循环,将5个面绘制出来
	for(int i =0; i<5; i++)
	{
		m_pd3dDevice->SetTexture(0, m_pTexture[i]);
		m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, i*4, 2);
	}

	//对是否渲染线框的处理代码
	if (bRenderFrame)  //如果要渲染出线框的话
	{
		m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME); //把填充模式设为线框填充
		//一个for循环,将5个面的线框绘制出来
		for(int i =0; i<5; i++)
		{
			m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, i*4, 2);	//绘制顶点 
		}

		m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);	//把填充模式调回实体填充
	}
}
5 . 析构函数

最后再实现一下析构函数, 看有什么COM 接口对象、SAFE_RELEASE 就行了:

//-------------------------------------------------------------------------------------------------
// Desc: 析构函数
//-------------------------------------------------------------------------------------------------
SkyBoxClass::~SkyBoxClass(void)
{
	SAFE_RELEASE( m_pVertexBuffer );
	for(int i=0; i<5; i++)
	{
		SAFE_RELEASE( m_pTexture[i] );
	}
}
这样, 一个封装了天空盒的SkyBoxClass 类就被我们实现出来了,可以看到它非常简单,只需 要填写好6个面的24 个顶点,最后为每个面贴上纹理就可以了。

23.4 天空盒类的使用

别看这个SkyBoxClass 天空盒类写起来有些小麻烦,但是用起来非常方便。
首先,定义一个SkyBoxClass 类的指针:

SkyBoxClass*	g_pSkyBox=NULL;				   //天空盒类的指针实例
然后,在初始化阶段拿着天空类的指针对象pSkyBox 到处“指”,创建并初始化天空:

	//创建并初始化天空对象
	g_pSkyBox = new SkyBoxClass( g_pd3dDevice );
	g_pSkyBox->LoadSkyTextureFromFile(L"GameMedia\\frontsnow1.jpg",L"GameMedia\\backsnow1.jpg",L"GameMedia\\leftsnow1.jpg",L"GameMedia\\rightsnow1.jpg", L"GameMedia\\topsnow1.jpg");//从文件加载前、后、左、右、顶面5个面的纹理图
	g_pSkyBox->InitSkyBox(20000);  //设置天空盒的边长

这里的GameMedia\\topsnow1.jpg 表示在工程文件夹下的GameMedia 文件夹中的topsnow1.jpg图片。另外,天空盒的边长最好设大一些,制造出无边无际的假象。
最后,就是在Render 函数中依然是使用天空类的指针对象pSkyBox 指一下RenderSkyBox 函数,进行渲染。
不过在渲染之前需要给RenderSkyBox 函数准备一个合适的世界矩阵,这里为了把天空盒调到适当的地方先是创建了一个平移矩阵matTransSky,然后让天空盒可以不停地缓慢移动,创建了一个随系统时间对Y 轴旋转的matRotSky 矩阵。接着把这两个矩阵相乘,结果等于最终的matSky 矩阵,然后就可以把matSky 作为参数,调用RenderSkyBox 函数了。

	//绘制天空
	D3DXMATRIX matSky,matTransSky,matRotSky;
	D3DXMatrixTranslation(&matTransSky,0.0f,-3500.0f,0.0f);
	D3DXMatrixRotationY(&matRotSky, -0.000005f*timeGetTime());   //旋转天空网格, 简单模拟云彩运动效果
	matSky=matTransSky*matRotSky;
	g_pSkyBox->RenderSkyBox(&matSky, false);

23.5 示例程序D3Ddemo18

本节示例程序源代码在之前的基础上又增加了两个文件,也就是实现天空类的源文件和头文 件。

我们依旧只贴出详细注释的main.cpp 中的核心代码,其他的众多文件大家找到本书代码包中的示例源代码查看就好了,首先是渲染资源初始化函数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); 


	// 从X文件中加载网格数据
	LPD3DXBUFFER pAdjBuffer  = NULL;
	LPD3DXBUFFER pMtrlBuffer = NULL;

	D3DXLoadMeshFromX(L"95.X", D3DXMESH_MANAGED, g_pd3dDevice, 
		&pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh);
	// 读取材质和纹理数据
	D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息
	g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls];
	g_pTextures  = new LPDIRECT3DTEXTURE9[g_dwNumMtrls];
	for (DWORD i=0; i<g_dwNumMtrls; i++) 
	{
		//获取材质,并设置一下环境光的颜色值
		g_pMaterials[i] = pMtrls[i].MatD3D;
		//g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse;

		//创建一下纹理对象
		g_pTextures[i]  = NULL;
		D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]);
	}
	SAFE_RELEASE(pAdjBuffer)
	SAFE_RELEASE(pMtrlBuffer)


	//创建柱子
	D3DXCreateCylinder(g_pd3dDevice, 280.0f, 10.0f, 3000.0f, 60, 60,  &g_cylinder, 0);
	g_MaterialCylinder.Ambient  = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f);  
	g_MaterialCylinder.Diffuse  = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f);  
	g_MaterialCylinder.Specular = D3DXCOLOR(0.5f, 0.0f, 0.3f, 0.3f);  
	g_MaterialCylinder.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f);

	// 设置光照  
	D3DLIGHT9 light;  
	::ZeroMemory(&light, sizeof(light));  
	light.Type          = D3DLIGHT_DIRECTIONAL;  
	light.Ambient       = D3DXCOLOR(0.7f, 0.7f, 0.7f, 1.0f);  
	light.Diffuse       = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);  
	light.Specular      = D3DXCOLOR(0.9f, 0.9f, 0.9f, 1.0f);  
	light.Direction     = D3DXVECTOR3(1.0f, 1.0f, 1.0f);  
	g_pd3dDevice->SetLight(0, &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, 1000.0f, -1200.0f));  //设置摄像机所在的位置
	g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 1200.0f, 0.0f));  //设置目标观察点所在的位置
	g_pCamera->SetViewMatrix();  //设置取景变换矩阵
	g_pCamera->SetProjMatrix();  //设置投影变换矩阵

	// 创建并初始化地形
	g_pTerrain = new TerrainClass(g_pd3dDevice);   
	g_pTerrain->LoadTerrainFromFile(L"GameMedia\\heighmap.raw", L"GameMedia\\terrainstone.jpg");		//从文件加载高度图和纹理
	g_pTerrain->InitTerrain(200, 200, 30.0f, 6.0f);  //四个值分别是顶点行数,顶点列数,顶点间间距,缩放系数

	//创建并初始化天空对象
	g_pSkyBox = new SkyBoxClass( g_pd3dDevice );
	g_pSkyBox->LoadSkyTextureFromFile(L"GameMedia\\frontsnow1.jpg",L"GameMedia\\backsnow1.jpg",L"GameMedia\\leftsnow1.jpg",L"GameMedia\\rightsnow1.jpg", L"GameMedia\\topsnow1.jpg");//从文件加载前、后、左、右、顶面5个面的纹理图
	g_pSkyBox->InitSkyBox(20000);  //设置天空盒的边长

	return S_OK;
}
然后是渲染相关的代码, Direct3D_Render()函数的实现代码:

//-----------------------------------【Direct3D_Render( )函数】-------------------------------
//	描述:使用Direct3D进行渲染
//--------------------------------------------------------------------------------------------------
void Direct3D_Render(HWND hwnd)
{
	//--------------------------------------------------------------------------------------
	// 【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 TransMatrix, RotMatrix, FinalMatrix;
	D3DXMatrixRotationX(&RotMatrix, -D3DX_PI * 0.5f);
	g_pd3dDevice->SetMaterial(&g_MaterialCylinder);
	for(int i = 0; i < 4; i++)
	{
		D3DXMatrixTranslation(&TransMatrix, -300.0f, 0.0f, -350.0f + (i * 500.0f));
		FinalMatrix = RotMatrix * TransMatrix ;
		g_pd3dDevice->SetTransform(D3DTS_WORLD, &FinalMatrix);
		g_cylinder->DrawSubset(0);

		D3DXMatrixTranslation(&TransMatrix, 300.0f, 0.0f, -350.0f + (i * 500.0f));
		FinalMatrix = RotMatrix * TransMatrix ;
		g_pd3dDevice->SetTransform(D3DTS_WORLD, &FinalMatrix);
		g_cylinder->DrawSubset(0);
	}

	//绘制人物
	D3DXMATRIX mScal,mRot2,mTrans,mFinal;   //定义一些矩阵,准备对大黄蜂进行矩阵变换
	D3DXMatrixTranslation(&mTrans,0.0f,600.0f,200.0f);
	D3DXMatrixScaling(&mScal,3.0f,3.0f,3.0f);
	mFinal=mScal*mTrans*g_matWorld;
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal);//设置模型的世界矩阵,为绘制做准备
	// 用一个for循环,进行模型的网格各个部分的绘制
	for (DWORD i = 0; i < g_dwNumMtrls; i++)
	{
		g_pd3dDevice->SetMaterial(&g_pMaterials[i]);  //设置此部分的材质
		g_pd3dDevice->SetTexture(0, g_pTextures[i]);//设置此部分的纹理
		g_pMesh->DrawSubset(i);  //绘制此部分
	}

	//绘制地形
	g_pTerrain->RenderTerrain(&g_matWorld, false);  //渲染地形,且第二个参数设为false,表示不渲染出地形的线框

	//绘制天空
	D3DXMATRIX matSky,matTransSky,matRotSky;
	D3DXMatrixTranslation(&matTransSky,0.0f,-3500.0f,0.0f);
	D3DXMatrixRotationY(&matRotSky, -0.000005f*timeGetTime());   //旋转天空网格, 简单模拟云彩运动效果
	matSky=matTransSky*matRotSky;
	g_pSkyBox->RenderSkyBox(&matSky, false);

	//绘制文字信息
	HelpText_Render(hwnd);


	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之四】:结束绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->EndScene();                       // 结束绘制
	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之五】:显示翻转
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);  // 翻转与显示
	 
}
接着我们看一下运行的截图:

    

最后贴上揭露“天机”的镜头一张,大家其实就是在这个盒子中乐不思蜀的:


23.6 章节小憩

天空,总是让我联想到宁静、优雅等形容词。明丽的蔚蓝色,流动的彩云, 这样的天空总是让 人赏心悦目。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值