上个星期浅墨写的介绍三维摄像机的文章和示例程序放出以后,大家似乎都表现出了很高涨的热情,不少朋友评论或者给浅墨发邮件问什么时候讲地形和天空顶。本来浅墨是准备这个星期就开始讲可编程渲染流水线的,看大家这么强烈的要求,浅墨决定干脆把准备在后面讲的地形天空一气呵成,跟在摄像机后面一起讲了得了。所以,这篇文章就诞生了。
先上一张配套示例程序的截图:
修改顶点间距和缩放比例可以得到更广阔,更陡峭的山峰:
想创造出极具真实感的三维游戏世界,三维地形的模拟是必不可少,至关重要的。
三维地形模拟其实是一个很广阔的课题,它其实不仅仅局限于我们的游戏开发领域,在三维仿真,虚拟现实等领域都涉及。说起三维地形模拟,似乎有那么一丝神秘,其实,只要了解其实现原理了这所谓的地形系统模拟也就是纸老虎一只。这篇文章里我们就来揭开三维地形模拟的面纱,看看到底怎样利用一个C++类的书写,实现我们专属的三维地形系统,然后就只需几句代码,两幅图片,一个“活生生”的三维地形就跃然纸上了。
一、三维地形绘制思路分析
关于地形绘制的大体思路,其实非常简单,让我们先来看三幅图。
我们可以发现,以上的三幅图就概括了三维地形模拟的大体走向与思路。
首先是第一幅图,我们在图中可以看到,图中描绘的就是在同一平面上的三角形网格组成的一个大的矩形区域。在这里我们把他看做是一张大的均匀的同一平面上的“渔网”,显然它是一个二维的平面。图中的每一个顶点都可以用一个二维的坐标(x,y)来唯一表示。
然后第二幅图,我们就像“揠苗助长”一样,拉着第一幅图中的“渔网”的某些顶点往上提(或者往下压)。这里往上提一点,那里提一点,这样,我们就为每一个顶点都赋予了一个高度(就算有的顶点没有移动,它的高度就为0),第一幅图中的渔网就变形了,成了三维图形了。每个顶点就都有了一个高度值。用z坐标来表示这个高度值的话,那么现在三维空间中这个变形的“渔网”中的每个顶点都可以用(x,y,z)来唯一表示。
最后第三幅图,在第二幅图中的三维“渔网”的表面我们“镀上”纹理不尽相同的“薄膜”,也就是进行了一个纹理包装的过程。这样奇迹就发生了,逼真的雪原山川,奇峰怪石展现在了我们眼前。
所以,绘制三维地形的玄机,就被这三幅图联手一语道破了。
其中,第二幅图中的那个“揠苗助长”的过程可谓三维地形绘制的一招“妙棋”。
这招“妙棋”我们常常是借助高度图来完成。下面我们就来讲一讲什么是高度图。
二、关于高度图
高度图在三维地形模拟中扮演着非常重要的角色。下面让我们来一起探讨一下高度图的方方面面。
1.高度图的概念
高度图说白了其实就是一组连续的数组,这个数组中的元素与地形网格中的顶点一一对应,且每一个元素都指定了地形网格的某个顶点的高度值。当然,高度图至少还有一种实现方案,就是用数值中的每一个元素来指定每个三角形栅格的高度值,而不是顶点的高度值。
高度图有多种可能的图形表示,其中最常用的一种是灰度图(grayscale map)。地形中某一点的海拔越高的话,相应地该点对应的灰度图中的亮度就越大。下面就是一幅灰度图:
我们通常只为每一个元素分配一个字节的存储空间,这样高度也就只能在0~255之间取值。
因此,地形中最低点将用0表示,而最高点使用255表示(当然,这样做可能会 出现一些问题,比如地形中大部分区域的高度差别都不大,但是有少数地方高度差特别大时,不过大多数情况下这个系统都能运行的很好)
这个范围大体上来反应地形中的高度变化完全没问题,但是在实际运用中,为了匹配3D世界的尺寸,可能需要对高度值进行比例变换,然而一进行比例变换,往往就可能超出上面的0~255这个区间。所以我们把高度数据加载到应用程序中时,我们重新分配一个整型或者浮点型的数组来存储这些高度值,这样我们就不必拘泥于0~255这个范围,这样就可以随心所欲地构建出我们心仪的三维世界了。
对于灰度图中的每个像素来说,同样使用0~~255之间的值来表示一个灰度。这样,我们就能把不同的灰度映射为高度,并且用像素索引表示不同网格。
要从高度图创建一个地形,我们需要创建一个与高度图相同大小的顶点网格,并使用高度图上每个像素的高度值作为顶点的高度。例如,我们可以使用一张6×6像素分辨率的高度图生成一个6×6大小的顶点网格。
网格上的顶点不仅包含位置,还包含诸如法线和纹理坐标的信息。下图就是一个在XZ平面中的6×6大小的顶点网格,其中每个顶点的高度对应在Y坐标上。
另外我们在设计三维地形模拟系统的时候,会指定一下相邻顶点的距离(水平距离和垂直距离一样)。这个距离在上图中用“Block Scale”表示。这个距离如果取小一点的话,会使顶点间的高度过渡平滑,但是会减少网格也就是三维地形的整体大小;反之,相邻间顶点的距离取大一点的话,顶点间的过渡会变得陡峭,同时网格也就是三维地形的整体尺寸会相对来说变大。在上图中,如果两个顶点间的距离我们设为1米的话,那么所生产地形的大小就是25平方米,很好理解吧。
最常用的灰度图格式是后缀名为RAW,我们在这里使用的高度图文件格式就是RAW,这个格式不包含诸如图像类型和大小信息的文件头,所以易于被读取。RAW文件只是简单的二进制文件,只包含地形的高度数据。在一个8位高度图中,每个字节都表示顶点的高度。
2.高度图的制作
高度图的制作一般有两种方式。
1、以某种算法为基础,写个程序生成。比较有名的是Fault Formation和Midpoint Displacement这两种算法。
2、通过图像编辑软件,三维建模软件,或者专业制作地形的软件来制作。
图像编辑软件首当其冲的当然是Photoshop,这个就是我们今天准备教大家的高度图生成方式。(先把后面两种介绍完,稍后就教大家怎么做高度图。)
三维建模软件就如我们之前介绍过的3DS Max和Maya了,地形制作也是三维建模界的一个分支。
然后专业制作地形的软件,比如一款叫Terragen。这款软件用起来也很方便,大家不妨google一下去下一个玩玩。
3、用Photoshop制作高度图
接下来,浅墨来教大家使用Photoshop生成高度图。
1.打开Photoshop(浅墨用的是Photoshop CS6),【Ctrl+N】或者依次点击菜单栏上的【文件】->【新建】,新建一个画布。如下图,我们的画布的大小取64x64像素。
2.创建完画布,接下来就是最关键的一步。依次点击菜单栏上的【滤镜】->【渲染】->【云彩】。
这时候,我们就可以发现,我们创建的空白画布上有了随机的灰度颜色值,如果你对这次生成的随机灰度图不满意的话,大可再次点击【滤镜】->【渲染】->【云彩】(或者【Ctrl+F】)来重新生成一次随机的灰度效果图,直到颜色分布满意为止。我也也可以用画笔来在图上涂抹,自己来设定高度。这是浅墨通过处理后得到的一张灰度图,这样后面如果我们用这张图作为高度图,得到的就是一个凹下去型的爱心地形图:
记得在用【云彩】滤镜的时候,最好把调色板的颜色前景色设为纯黑色,不然可能得到的随机灰度图效果出不来。即调色板中的颜色设置成如下图:
另外,我们可以通过对图片色阶的调整,来对生成的灰度图的整体颜色进行调节。比如想让地形整体来说高一些,就把灰度图整体调亮一些,反之,地形整体来说要显得低一些的话,就把绘图图整体调按。色阶对话框通过【图像】->【调整】->【色阶】打开,或者直接按快捷键【Ctrl+F】。
另外在点击【图像】->【调整】后弹出的对话框中还有曲线、色相、饱和度等等选项,大家不妨也试试。
制作完成,我们点击【文件】->【储存为…】或者直接按快捷键【Shift+Ctrl+S】来制作好的高度图进行保存。保存的格式随意,因为我们稍后写的一个地形类原则上支持几乎所有的图片格式高度图的导入,只不过对有些图片格式得到的效果图比较奇葩而已。
这里我们选择8位的raw格式:
点击确定后,会弹出如下导出raw的对话框,记得要把【通道储存在】这个选项改成非隔行顺序,如图:
其实,大家不想用Photshop的话,可以直接google一下“heighmap”,搜索结果中随便找就是一张现成的,然后改成raw格式就好了。原则上我们可以直接随便拿一张任意格式的图片来做高度图使用,只是可能做出来的地形显得怪异一点而已。
4.在程序中读取高度图
让我们针对使用最广泛的raw类型的高度图进行讲解。由于raw格式文件是按字节为单位保存图像中的每个像素的灰度值的,那么我们可以容易地读取保存在该文件中的高度信息。在这次的地形类的实现中,我们用到了C++中模板以及文件流的知识,如果对下面这段代码不太熟悉的话就去看看《C++Primer》的相应章节吧。好了,下面贴出详细注释的代码:
-
- std::ifstream inFile;
- inFile.open(pRawFileName,std::ios::binary);
-
- inFile.seekg(0,std::ios::end);
- std::vector<BYTE>inData(inFile.tellg());
-
- inFile.seekg(std::ios::beg);
- inFile.read((char*)&inData[0],inData.size());
- inFile.close();
且由于保存在raw文件中的每个灰度数据只是用一个字节存储的,那么这样所表示的地形高度只能在[0,255]之间取值。我们显然不高兴这样。所以,我们继续将读取的高度信息重新保存到一个浮点型的模板类型中,这样就能舒心地取到任何范围的高度值了。注意下面这段代码中vHeightInfo的定义是在类头文件中的:
- std::vector<FLOAT> m_vHeightInfo;
- …
- m_vHeightInfo.resize(inData.size());
-
- for (unsigned int i=0; i<inData.size();i++)
- m_vHeightInfo[i] = inData[i];
三、地形类轮廓的书写
在继续展开讲解之前,让我们先来把这个地形类的整体轮廓给勾勒出来。这个类我们取名为TerrainClass,它能通过载入二进制类型的文件(以raw格式为首)来得到地形的高度信息,通过载入图片得到地形所采用的纹理。载入文件的过程我们封装在一个名为LoadTerrainFromFile的函数中。
在上文中讲高度图的概念相关知识的时候我们就提到过,需要把高度图所传达的信息转化到顶点网格中去,这样才好绘制出来。所以在类中既是重点也是难点的就是这个“转化”的过程,这个过程我们放到一个名为InitTerrain的函数中。高度图到顶点的“转化”完成后,接下来当然需要把这些顶点配合着纹理都绘制出来,绘制的过程我们放在一个名为RenderTerrain的函数中。加上构造函数和析构函数,FVF顶点格式的定义以及若干必须的成员变量,我们就可以勾勒出TerrainClass类的轮廓如下,即下面贴出来的是Terrain.h头文件的全部代码:
-
-
-
-
-
-
- #pragma once
-
- #include <d3d9.h>
- #include <d3dx9.h>
- #include <vector>
- #include <fstream>
- #include "D3DUtil.h"
-
- class TerrainClass
- {
- private:
- LPDIRECT3DDEVICE9 m_pd3dDevice;
- LPDIRECT3DTEXTURE9 m_pTexture;
- LPDIRECT3DINDEXBUFFER9 m_pIndexBuffer;
- LPDIRECT3DVERTEXBUFFER9 m_pVertexBuffer;
-
- int m_nCellsPerRow;
- int m_nCellsPerCol;
- int m_nVertsPerRow;
- int m_nVertsPerCol;
- int m_nNumVertices;
- FLOAT m_fTerrainWidth;
- FLOAT m_fTerrainDepth;
- FLOAT m_fCellSpacing;
- FLOAT m_fHeightScale;
- std::vector<FLOAT> m_vHeightInfo;
-
-
- struct TERRAINVERTEX
- {
- FLOAT _x, _y, _z;
- FLOAT _u, _v;
- TERRAINVERTEX(FLOAT x, FLOAT y, FLOAT z, FLOAT u, FLOAT v)
- :_x(x), _y(y), _z(z), _u(u), _v(v) {}
- static const DWORD FVF = D3DFVF_XYZ | D3DFVF_TEX1;
- };
-
- public:
- TerrainClass(IDirect3DDevice9 *pd3dDevice);
- virtual ~TerrainClass(void);
-
- public:
- BOOL LoadTerrainFromFile(wchar_t *pRawFileName, wchar_t *pTextureFile);
- BOOL InitTerrain(INT nRows, INT nCols, FLOAT fSpace, FLOAT fScale);
- BOOL RenderTerrain(D3DXMATRIX *pMatWorld, BOOL bDrawFrame=FALSE);
- };
四、地形顶点的计算
下面我们来看看如何计算出地形中的每个顶点。
在计算顶点之前,还需要做一些准备工作。在创建地形时,需要通过指定地形的行数、列数以及顶点间的距离来指定地形的大小。上面我们在给类写轮廓的时候刚贴出来过,封装着地形顶点计算的InitTerrain函数的原型是这样的:
- BOOLInitTerrain(INT nRows, INT nCols, FLOAT fSpace, FLOAT fScale);
其中前两个参数分别为地形的行数和列数,需要我们在初始化时指定。也就是说在计算地形的时候行数和列数是已知的,那么,地形在x方向和z方向上的顶点数也就明了了,也就是z方向上顶点数为地形的行数加1,而在x方向上的顶点数为地形列数加上1.
需要注意的是地形在x方向和z方向上的顶点数都不能大于与高度图对应的分辨率分量。因为高度图中的每个元素都描述地形型中某个顶点的高度值。如果高度图中只描述了128x128分辨率的地形信息,我们在初始化时InitTerrain函数的前两个参数都不能取超过128的值。以高度图的角度来想一下,既然我只跟你准备了128x128的高度信息,那么你就得在我规定的范围之内取顶点数,如果你取多了,我可管不了你这么多,等着内存溢出吧。
接着,第三个fSpace为顶点间的间隔,第四个参数fScale为缩放的系数。
关于顶点的计算思路,我们通过下面这幅图,就可以写出来:
对每行的单元格数目、每列的单元格数目、单元格间的间距、高度缩放系数、地形的宽度、地形的深度、每行的顶点数、每列的顶点数、顶点总数各个击破,就写出了下面这几句代码:
- m_nCellsPerRow = nRows;
- m_nCellsPerCol = nCols;
- m_fCellSpacing = fSpace;
- m_fHeightScale = fScale;
- m_fTerrainWidth = nRows * fSpace;
- m_fTerrainDepth = nCols * fSpace;
- m_nVertsPerRow = m_nCellsPerCol + 1;
- m_nVertsPerCol = m_nCellsPerRow + 1;
- m_nNumVertices = m_nVertsPerRow * m_nVertsPerCol;
另外,我们在计算地形顶点前,还需要将地形的高度值乘以一个缩放系数,以便能够调整高度的整体变化幅度,就是下面这两句代码:
-
- for(unsigned int i=0;i<m_vHeightInfo.size(); i++)
- m_vHeightInfo[i] *= m_fHeightScale;
接着,就是顶点的正式计算时刻,我们按照着之前专门讲解顶点缓存时用的四步曲,以及对着上面的这幅图,下面的这些实现代码就很好理解了:
-
-
-
-
- if(FAILED(m_pd3dDevice->CreateVertexBuffer(m_nNumVertices * sizeof(TERRAINVERTEX),
- D3DUSAGE_WRITEONLY, TERRAINVERTEX::FVF,D3DPOOL_MANAGED, &m_pVertexBuffer, 0)))
- return FALSE;
-
- TERRAINVERTEX *pVertices = NULL;
- m_pVertexBuffer->Lock(0, 0,(void**)&pVertices, 0);
-
- FLOAT fStartX = -m_fTerrainWidth / 2.0f,fEndX = m_fTerrainWidth / 2.0f;
- FLOAT fStartZ = m_fTerrainDepth / 2.0f, fEndZ =-m_fTerrainDepth / 2.0f;
- FLOAT fCoordU = 3.0f /(FLOAT)m_nCellsPerRow;
- FLOAT fCoordV = 3.0f /(FLOAT)m_nCellsPerCol;
-
- int nIndex = 0, i = 0, j = 0;
- for (float z = fStartZ; z > fEndZ; z -=m_fCellSpacing, i++)
- {
- j = 0;
- for (float x = fStartX; x < fEndX; x+= m_fCellSpacing, j++)
- {
- nIndex = i * m_nCellsPerRow + j;
- pVertices[nIndex] =TERRAINVERTEX(x, m_vHeightInfo[nIndex], z, j*fCoordU, i*fCoordV);
- nIndex++;
- }
- }
-
- m_pVertexBuffer->Unlock();
已经逐行注释了,理解起来应该是没问题的吧。
五、地形索引的计算
顶点值算完了,当然还需要接着计算顶点的索引。顶点索引的计算关键是推导出一个用于求构成第i行,第j列的顶点处右下方两个三角形的顶点索引的通用公式。下面我们来看看这个公式如何推导,下图依旧解释得很清楚了:
对顶点缓存中的任意一点A,如果该点位于地形中的第i行、第j列的话,那么该点在顶点缓存中所对应的位置应该就是i*m+j(m为每行的顶点数)。如果A点在索引缓存中的位置为k的话,那么A点为起始点构成的三角形ABC中,B、C顶点在顶点缓存中的位置就为(i+1)x m+j和i x m+(j+1)。且B点索引值为k+1,C点索引值为k+2.这样。这样,公式就可以推导为如下:
三角形ABC=【i*每行顶点数+j,i*每行顶点数+(j+1),(i+1)*行顶点数+j】
三角形CBD=【(i+1)*每行顶点数+j,i*每行顶点数+(j+1),(i+1)*行顶点数+(j+1)】
通过上面我们推导出的这个公式,就可以写出下面计算索引缓存的相关代码:
-
-
-
-
- if (FAILED(m_pd3dDevice->CreateIndexBuffer(m_nNumVertices * 6 *sizeof(WORD),
- D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &m_pIndexBuffer, 0)))
- return FALSE;
-
- WORD* pIndices = NULL;
- m_pIndexBuffer->Lock(0, 0, (void**)&pIndices, 0);
-
- nIndex = 0;
- for(int row = 0; row < m_nCellsPerRow-1; row++)
- {
- for(int col = 0; col < m_nCellsPerCol-1; col++)
- {
-
- pIndices[nIndex] = row * m_nCellsPerRow + col;
- pIndices[nIndex+1] = row * m_nCellsPerRow + col + 1;
- pIndices[nIndex+2] = (row+1) * m_nCellsPerRow + col;
-
- pIndices[nIndex+3] = (row+1) * m_nCellsPerRow + col;
- pIndices[nIndex+4] = row * m_nCellsPerRow + col + 1;
- pIndices[nIndex+5] = (row+1) * m_nCellsPerRow + col + 1;
-
- nIndex += 6;
- }
- }
-
- m_pIndexBuffer->Unlock();
六、渲染出地形
没有渲染我们前面就算白忙活了。
地形的渲染我们封装在了一个名为RenderTerrain的函数中,注释很详细,我们直接上代码:
-
-
-
-
- BOOLTerrainClass::RenderTerrain(D3DXMATRIX *pMatWorld, BOOL bRenderFrame)
- {
- m_pd3dDevice->SetStreamSource(0,m_pVertexBuffer, 0, sizeof(TERRAINVERTEX));
- m_pd3dDevice->SetFVF(TERRAINVERTEX::FVF);
- m_pd3dDevice->SetIndices(m_pIndexBuffer);
- m_pd3dDevice->SetTexture(0,m_pTexture);
-
- m_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
- m_pd3dDevice->SetTransform(D3DTS_WORLD,pMatWorld);
- m_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0,
- m_nNumVertices, 0, m_nNumVertices * 2);
-
- m_pd3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
- m_pd3dDevice->SetTexture(0, 0);
-
- if (bRenderFrame)
- {
- m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
- m_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0,
- m_nNumVertices, 0, m_nNumVertices *2);
- m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
- }
- return TRUE;
- }
七、完成地形类的设计
上面都是零零散散地说出了我们地形类的某些实现细节,下面我们把他们整合在一起,构成一个整体,完成地形类的设计,即贴出TerrainClass.cpp的全部代码:
-
-
-
-
-
-
- #include"TerrainClass.h"
-
-
-
-
- TerrainClass::TerrainClass(IDirect3DDevice9*pd3dDevice)
- {
-
- m_pd3dDevice = pd3dDevice;
- m_pTexture = NULL;
- m_pIndexBuffer = NULL;
- m_pVertexBuffer = NULL;
- m_nCellsPerRow = 0;
- m_nCellsPerCol = 0;
- m_nVertsPerRow = 0;
- m_nVertsPerCol = 0;
- m_nNumVertices = 0;
- m_fTerrainWidth = 0.0f;
- m_fTerrainDepth = 0.0f;
- m_fCellSpacing = 0.0f;
- m_fHeightScale = 0.0f;
- }
-
-
-
-
-
-
- BOOLTerrainClass::LoadTerrainFromFile(wchar_t *pRawFileName, wchar_t *pTextureFile)
- {
-
- std::ifstream inFile;
- inFile.open(pRawFileName,std::ios::binary);
-
- inFile.seekg(0,std::ios::end);
- std::vector<BYTE>inData(inFile.tellg());
-
- inFile.seekg(std::ios::beg);
- inFile.read((char*)&inData[0],inData.size());
- inFile.close();
-
- m_vHeightInfo.resize(inData.size());
-
- for (unsigned int i=0; i<inData.size();i++)
- m_vHeightInfo[i] = inData[i];
-
-
- if (FAILED(D3DXCreateTextureFromFile(m_pd3dDevice,pTextureFile, &m_pTexture)))
- return FALSE;
-
- return TRUE;
- }
-
-
-
-
-
- BOOLTerrainClass::InitTerrain(INT nRows, INT nCols, FLOAT fSpace, FLOAT fScale)
- {
- m_nCellsPerRow = nRows;
- m_nCellsPerCol = nCols;
- m_fCellSpacing = fSpace;
- m_fHeightScale = fScale;
- m_fTerrainWidth = nRows * fSpace;
- m_fTerrainDepth = nCols * fSpace;
- m_nVertsPerRow = m_nCellsPerCol + 1;
- m_nVertsPerCol = m_nCellsPerRow + 1;
- m_nNumVertices = m_nVertsPerRow * m_nVertsPerCol;
-
-
- for(unsigned int i=0;i<m_vHeightInfo.size(); i++)
- m_vHeightInfo[i] *= m_fHeightScale;
-
-
-
-
- if(FAILED(m_pd3dDevice->CreateVertexBuffer(m_nNumVertices *sizeof(TERRAINVERTEX),
- D3DUSAGE_WRITEONLY, TERRAINVERTEX::FVF,D3DPOOL_MANAGED, &m_pVertexBuffer, 0)))
- return FALSE;
-
- TERRAINVERTEX *pVertices = NULL;
- m_pVertexBuffer->Lock(0, 0,(void**)&pVertices, 0);
-
- FLOAT fStartX = -m_fTerrainWidth / 2.0f,fEndX = m_fTerrainWidth / 2.0f;
- FLOAT fStartZ = m_fTerrainDepth / 2.0f, fEndZ =-m_fTerrainDepth / 2.0f;
- FLOAT fCoordU = 3.0f /(FLOAT)m_nCellsPerRow;
- FLOAT fCoordV = 3.0f /(FLOAT)m_nCellsPerCol;
-
- int nIndex = 0, i = 0, j = 0;
- for (float z = fStartZ; z > fEndZ; z -=m_fCellSpacing, i++)
- {
- j = 0;
- for (float x = fStartX; x < fEndX; x+= m_fCellSpacing, j++)
- {
- nIndex = i * m_nCellsPerRow + j;
- pVertices[nIndex] =TERRAINVERTEX(x, m_vHeightInfo[nIndex], z, j*fCoordU, i*fCoordV);
- nIndex++;
- }
- }
-
- m_pVertexBuffer->Unlock();
-
-
-
-
-
- if(FAILED(m_pd3dDevice->CreateIndexBuffer(m_nNumVertices * 6 *sizeof(WORD),
- D3DUSAGE_WRITEONLY, D3DFMT_INDEX16,D3DPOOL_MANAGED, &m_pIndexBuffer, 0)))
- return FALSE;
-
- WORD* pIndices = NULL;
- m_pIndexBuffer->Lock(0, 0, (void**)&pIndices,0);
-
- nIndex = 0;
- for(int row = 0; row < m_nCellsPerRow-1;row++)
- {
- for(int col = 0; col <m_nCellsPerCol-1; col++)
- {
-
- pIndices[nIndex] = row* m_nCellsPerRow + col;
- pIndices[nIndex+1] = row * m_nCellsPerRow + col + 1;
- pIndices[nIndex+2] = (row+1) *m_nCellsPerRow + col;
-
- pIndices[nIndex+3] = (row+1) *m_nCellsPerRow + col;
- pIndices[nIndex+4] = row * m_nCellsPerRow + col + 1;
- pIndices[nIndex+5] = (row+1) *m_nCellsPerRow + col + 1;
-
- nIndex += 6;
- }
- }
-
- m_pIndexBuffer->Unlock();
-
- return TRUE;
- }
-
-
-
-
-
- BOOLTerrainClass::RenderTerrain(D3DXMATRIX *pMatWorld, BOOL bRenderFrame)
- {
- m_pd3dDevice->SetStreamSource(0,m_pVertexBuffer, 0, sizeof(TERRAINVERTEX));
- m_pd3dDevice->SetFVF(TERRAINVERTEX::FVF);
- m_pd3dDevice->SetIndices(m_pIndexBuffer);
- m_pd3dDevice->SetTexture(0,m_pTexture);
-
- m_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
- m_pd3dDevice->SetTransform(D3DTS_WORLD,pMatWorld);
- m_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0,
- m_nNumVertices, 0, m_nNumVertices * 2);
-
- m_pd3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
- m_pd3dDevice->SetTexture(0, 0);
-
- if (bRenderFrame)
- {
- m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
- m_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0,
- m_nNumVertices, 0, m_nNumVertices *2);
- m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
- }
- return TRUE;
- }
-
-
-
-
- TerrainClass::~TerrainClass(void)
- {
- SAFE_RELEASE(m_pTexture);
- SAFE_RELEASE(m_pIndexBuffer);
- SAFE_RELEASE(m_pVertexBuffer);
- }
一个地形类就这样被我们一步一步设计出来了。下面我们来看一下,这个类到底如何用。
八、详细注释的源代码欣赏
本次的配套程序有点科幻的味道,我们地形渲染得像水晶宝石山,然后载入了一个变形金刚中大黄蜂的模型,非常帅气。
源代码包含了8个文件,主要用于公共辅助宏定义的D3DUtil.h,用于封装了DirectInput输入控制API的DirectInputClass.h和DirectInputClass.cpp,以及封装了虚拟摄像机类的CameraClass.h和CameraClass.cpp,封装了地形系统的TerrainClass.h和TerrainClass.cpp,当还还有核心代码main.cpp。
DirectInputClass.h和DirectInputClass.cpp较之前的文章中依然没有任何修改,依然不再贴出,TerrainClass.cpp和TerrainClass.h在上面讲解的过程中以及贴出来了,这里也不贴出,我们依然只贴核心代码main.cpp,大家要看得爽的话,源代码在文章末尾有下载链接,下回去用VisualStuido看就行了。下面就是main.cpp的全部代码:
-
-
-
-
-
-
-
-
-
-
-
- #define SCREEN_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度
- #define SCREEN_HEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度
- #define WINDOW_TITLE _T("【Visual C++】游戏开发笔记系列配套示例程序四十八 浅墨DirectX教程十六 三维地形系统的实现") //为窗口标题定义的宏
-
-
-
-
-
-
- #include <d3d9.h>
- #include <d3dx9.h>
- #include <tchar.h>
- #include <time.h>
- #include "DirectInputClass.h"
- #include "CameraClass.h"
- #include "TerrainClass.h"
-
-
-
-
- #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")
-
-
-
-
-
-
- LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
- LPD3DXFONT g_pTextFPS =NULL;
- LPD3DXFONT g_pTextAdaperName = NULL;
- LPD3DXFONT g_pTextHelper = NULL;
- LPD3DXFONT g_pTextInfor= NULL;
- float g_FPS= 0.0f;
- wchar_t g_strFPS[50] ={0};
- wchar_t g_strAdapterName[60] ={0};
- D3DXMATRIX g_matWorld;
- LPD3DXMESH g_pMesh = NULL;
- D3DMATERIAL9* g_pMaterials= NULL;
- LPDIRECT3DTEXTURE9* g_pTextures = NULL;
- DWORD g_dwNumMtrls = 0;
- LPD3DXMESH g_cylinder = NULL;
- D3DMATERIAL9 g_MaterialCylinder;
- DInputClass* g_pDInput = NULL;
- CameraClass* g_pCamera = NULL;
- TerrainClass* g_pTerrain = NULL;
-
-
-
-
-
-
-
- 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);
- void Direct3D_Update( HWND hwnd);
- void Direct3D_CleanUp( );
- float Get_FPS();
- void HelpText_Render(HWND hwnd);
-
-
-
-
-
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
- {
-
-
- WNDCLASSEX wndClass = { 0 };
- 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("icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE);
- wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );
- wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);
- wndClass.lpszMenuName = NULL;
- wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop");
-
- if( !RegisterClassEx( &wndClass ) )
- return -1;
-
- HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE,
- WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, SCREEN_WIDTH,
- SCREEN_HEIGHT, NULL, NULL, hInstance, NULL );
-
-
-
- if (!(S_OK==Direct3D_Init (hwnd,hInstance)))
- {
- MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0);
- }
- PlaySound(L"雅尼 - 兰花.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP);
-
-
-
- MoveWindow(hwnd,200,50,SCREEN_WIDTH,SCREEN_HEIGHT,true);
- ShowWindow( hwnd, nShowCmd );
- UpdateWindow(hwnd);
-
-
- g_pDInput = new DInputClass();
- g_pDInput->Init(hwnd,hInstance,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
-
-
- MSG msg = { 0 };
- while( msg.message != WM_QUIT )
- {
- if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
- {
- TranslateMessage( &msg );
- DispatchMessage( &msg );
- }
- else
- {
- Direct3D_Update(hwnd);
- Direct3D_Render(hwnd);
- }
- }
-
- UnregisterClass(_T("ForTheDreamOfGameDevelop"), wndClass.hInstance);
- return 0;
- }
-
-
-
-
-
-
-
- LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
- {
- switch( message )
- {
- case WM_PAINT:
- Direct3D_Render(hwnd);
- ValidateRect(hwnd, NULL);
- break;
-
- case WM_KEYDOWN:
- if (wParam == VK_ESCAPE)
- DestroyWindow(hwnd);
- break;
- case WM_DESTROY:
- Direct3D_CleanUp();
- PostQuitMessage( 0 );
- break;
-
- default:
- return DefWindowProc( hwnd, message, wParam, lParam );
- }
-
- return 0;
- }
-
-
-
-
-
-
-
-
-
-
-
-
- HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance)
- {
-
-
-
-
- LPDIRECT3D9 pD3D = NULL;
- if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
- return E_FAIL;
-
-
-
-
- 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;
-
-
-
-
- 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;
-
-
-
-
- if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
- hwnd, vp, &d3dpp, &g_pd3dDevice)))
- return E_FAIL;
-
-
-
- wchar_t TempName[60]=L"当前显卡型号:";
- D3DADAPTER_IDENTIFIER9 Adapter;
- pD3D->GetAdapterIdentifier(0,0,&Adapter);
- int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);
- MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);
- wcscat_s(TempName,g_strAdapterName);
- wcscpy_s(g_strAdapterName,TempName);
-
- if(!(S_OK==Objects_Init())) return E_FAIL;
-
- SAFE_RELEASE(pD3D)
-
- 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);
-
-
-
-
- LPD3DXBUFFER pAdjBuffer = NULL;
- LPD3DXBUFFER pMtrlBuffer = NULL;
-
- D3DXLoadMeshFromX(L"bee.X", D3DXMESH_MANAGED, g_pd3dDevice,
- &pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh);
-
- D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer();
- 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, 8000.0f, 100.0f, 50000.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, 12000.0f, -30000.0f));
- g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 6000.0f, 0.0f));
- g_pCamera->SetViewMatrix();
- g_pCamera->SetProjMatrix();
-
-
- g_pTerrain = new TerrainClass(g_pd3dDevice);
- g_pTerrain->LoadTerrainFromFile(L"heighmap.raw", L"green.jpg");
- g_pTerrain->InitTerrain(200, 200, 500.0f, 60.0f);
-
- return S_OK;
- }
-
- void Direct3D_Update( HWND hwnd)
- {
-
- g_pDInput->GetInput();
-
-
- if (g_pDInput->IsKeyDown(DIK_A)) g_pCamera->MoveAlongRightVec(-10.0f);
- if (g_pDInput->IsKeyDown(DIK_D)) g_pCamera->MoveAlongRightVec( 10.0f);
- if (g_pDInput->IsKeyDown(DIK_W)) g_pCamera->MoveAlongLookVec( 10.0f);
- if (g_pDInput->IsKeyDown(DIK_S)) g_pCamera->MoveAlongLookVec(-10.0f);
- if (g_pDInput->IsKeyDown(DIK_I)) g_pCamera->MoveAlongUpVec( 10.0f);
- if (g_pDInput->IsKeyDown(DIK_K)) g_pCamera->MoveAlongUpVec(-10.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_J)) g_pCamera->RotationLookVec(-0.001f);
- if (g_pDInput->IsKeyDown(DIK_L)) g_pCamera->RotationLookVec( 0.001f);
-
-
- g_pCamera->RotationUpVec(g_pDInput->MouseDX()* 0.001f);
- g_pCamera->RotationRightVec(g_pDInput->MouseDY() * 0.001f);
-
-
- static FLOAT fPosZ=0.0f;
- fPosZ += g_pDInput->MouseDZ()*0.03f;
-
-
- D3DXMATRIX matView;
- g_pCamera->CalculateViewMatrix(&matView);
- g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);
-
-
- D3DXMatrixTranslation(&g_matWorld, 0.0f, 0.0f, fPosZ);
-
-
- POINT lt,rb;
- RECT rect;
- GetClientRect(hwnd,&rect);
-
- lt.x = rect.left;
- lt.y = rect.top;
-
- rb.x = rect.right;
- rb.y = rect.bottom;
-
- ClientToScreen(hwnd,<);
- ClientToScreen(hwnd,&rb);
-
- rect.left = lt.x;
- rect.top = lt.y;
- rect.right = rb.x;
- rect.bottom = rb.y;
-
- ClipCursor(&rect);
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- void Direct3D_Render(HWND hwnd)
- {
-
-
-
- g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(0, 108, 255), 1.0f, 0);
-
-
-
-
- g_pd3dDevice->BeginScene();
-
-
-
-
-
-
- D3DXMATRIX mScal,mRot1,mRot2,mTrans,mFinal;
- D3DXMatrixScaling(&mScal,20.0f,20.0f,20.0f);
- D3DXMatrixTranslation(&mTrans,0,8000,0);
- D3DXMatrixRotationX(&mRot1, D3DX_PI/2);
- D3DXMatrixRotationY(&mRot2, D3DX_PI/2);
- mFinal=mScal*mRot1*mRot2*mTrans*g_matWorld;
- g_pd3dDevice->SetTransform(D3DTS_WORLD, &mFinal);
-
- 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);
-
-
- D3DXMATRIX TransMatrix, RotMatrix, FinalMatrix;
- D3DXMatrixRotationX(&RotMatrix, -D3DX_PI * 0.5f);
- g_pd3dDevice->SetMaterial(&g_MaterialCylinder);
- for(int i = 0; i < 4; i++)
- {
- D3DXMatrixTranslation(&TransMatrix, -10000.0f, 0.0f, -15000.0f + (i * 20000.0f));
- FinalMatrix = RotMatrix * TransMatrix ;
- g_pd3dDevice->SetTransform(D3DTS_WORLD, &FinalMatrix);
- g_cylinder->DrawSubset(0);
-
- D3DXMatrixTranslation(&TransMatrix, 10000.0f, 0.0f, -15000.0f + (i * 20000.0f));
- FinalMatrix = RotMatrix * TransMatrix ;
- g_pd3dDevice->SetTransform(D3DTS_WORLD, &FinalMatrix);
- g_cylinder->DrawSubset(0);
- }
-
-
- HelpText_Render(hwnd);
-
-
-
-
-
- g_pd3dDevice->EndScene();
-
-
-
- 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" I:垂直向上飞翔 K:垂直向下飞翔", -1, &formatRect,
- DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
- formatRect.top += 25;
- g_pTextHelper->DrawText(NULL, L" J:向左倾斜 L:向右倾斜", -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));
- }
-
-
-
-
-
- float Get_FPS()
- {
-
-
- static float fps = 0;
- static int frameCount = 0;
- static float currentTime =0.0f;
- static float lastTime = 0.0f;
-
- frameCount++;
- currentTime = timeGetTime()*0.001f;
-
-
- if(currentTime - lastTime > 1.0f)
- {
- fps = (float)frameCount /(currentTime - lastTime);
- lastTime = currentTime;
- frameCount = 0;
- }
-
- return fps;
- }
-
-
-
-
-
-
-
- void Direct3D_CleanUp()
- {
-
-
- for (DWORD i = 0; i<g_dwNumMtrls; i++)
- SAFE_RELEASE(g_pTextures[i]);
- SAFE_DELETE(g_pTextures);
- SAFE_DELETE(g_pMaterials);
- SAFE_DELETE(g_pDInput);
- SAFE_RELEASE(g_cylinder);
- SAFE_RELEASE(g_pMesh);
- SAFE_RELEASE(g_pd3dDevice);
- SAFE_RELEASE(g_pTextAdaperName)
- SAFE_RELEASE(g_pTextHelper)
- SAFE_RELEASE(g_pTextInfor)
- SAFE_RELEASE(g_pTextFPS)
- SAFE_RELEASE(g_pd3dDevice)
- }
然后是几张程序截图:
如果是嫌这个地形还不过瘾,不够大或者不够陡峭,我们可以修改顶点间的间距以及缩放系数,来得到陡峭而一望无际的山峰。下图对应的是在初始化地形时将顶点间的间距以及缩放系数调大一些的情况:
- g_pTerrain->InitTerrain(200, 200, 2000.0f, 600.0f);
按我们的飞行速度,达到这山峰的地图边界得飞几分钟。。。。不过这时候大黄蜂不见了,在地形整体下方了,此时摄像机初始位置也最好调一下,不然一出来也是在地形整体的下方。
文章最后,依旧是放出本篇文章配套源代码的下载:
本节笔记配套源代码请点击这里下载:
【浅墨DirectX提高班】配套源代码之十六下载
以上就是本节笔记的全部内容,更多精彩内容,且听下回分解。
浅墨在这里,希望喜欢游戏开发系列文章的朋友们能留下你们的评论,每次浅墨登陆博客看到大家的留言的时候都会非常开心,感觉自己正在传递一种信仰,一种精神。
文章最后,依然是【每文一语】栏目,今天的句子是:
这世上有两样东西是别人抢不走的:一是藏在心中的梦想,二是读进大脑的书。
下周一,让我们离游戏开发的梦想更近一步。
下周一,游戏开发笔记,我们,不见不散。