《清华大学DirectX游戏编程第14章(全20章)》由会员分享,可在线阅读,更多相关《清华大学DirectX游戏编程第14章(全20章)(23页珍藏版)》请在金锄头文库上搜索。
1、第14章 基本地形渲染 本章将讲述如何实现一个简单的地形类。本章将讲述如何实现一个简单的地形类。 主要目标:学习如何生成渲染地形需要的高度信息,高度信息将用于在程序中模拟山峰、峡谷等自然地形。理解怎样生成代表地形的顶点和三角形数据。学习地形纹理设置和为地形打光的方法。学习如何在地形渲染中控制摄像机位置,模拟人在场景中行走和奔跑的效果。214.1 高 度 图高度图用来描述地形的高度高度图用来描述地形的高度信息。高度图实际上是一个信息。高度图实际上是一个数组,数组中的每个元素都数组,数组中的每个元素都存储着地形网格中对应顶点存储着地形网格中对应顶点的高度数据的高度数据 将高度图数据读入内存时,将高
2、度图数据读入内存时,通常为图中的每个元素分配通常为图中的每个元素分配一个字节的内存,这样高度一个字节的内存,这样高度的范围就是的范围就是0255。 图图14.2展示了用作高度图的展示了用作高度图的灰度图。灰度图。314.1.1 高度图的创建高度图可以通过程序生成,或者通过高度图可以通过程序生成,或者通过Adobe Photoshop这样的图像处理软件来制作。这样的图像处理软件来制作。 图图14.3展示了用展示了用Adobe Photoshop创建的金字创建的金字塔地形。塔地形。414.1.2 读取RAW文件由于由于RAW文件中的数据仅仅是连续的字节块文件中的数据仅仅是连续的字节块的集合,可以用
3、下面的方法方便地得到其中的集合,可以用下面的方法方便地得到其中的数据。这里变量的数据。这里变量_heightmap是是Terrain类的类的成员,其声明如下:成员,其声明如下:参见教材P209注意这里把存储注意这里把存储BYTE数据的数据的vector里的数据里的数据拷贝到存储拷贝到存储int数据的数据的vector里,这样就可以里,这样就可以扩大高度数据,使其超出扩大高度数据,使其超出0255的限制。的限制。514.1.3 高度图的访问和修改Terrain类提供下面两个方法来访问和修改高度图中的元类提供下面两个方法来访问和修改高度图中的元素:素:int Terrain:getHeightma
4、pEntry(int row, int col)return _heightmaprow * _numVertsPerRow + col;void Terrain:setHeightmapEntry(int row, int col, int value)_heightmaprow * _numVertsPerRow + col = value;614.2 生成地形几何数据图图14.4展示了地形的一些属性、展示了地形的一些属性、术语和需要使用到的特殊顶点。术语和需要使用到的特殊顶点。 Terrain类的定义如下:类的定义如下:参见教材P211通过构造函数传入的值,可以通过构造函数传入的值,可以
5、计算出地形需要的其他变量:计算出地形需要的其他变量:参见教材P212顶点结构声明如下:顶点结构声明如下:参见教材P212714.2.1 计算顶点下面需要计算的是纹理坐标,参照图下面需要计算的是纹理坐标,参照图14.5,通,通过它可以看出过它可以看出(u, v)纹理坐标和地形的顶点纹理坐标和地形的顶点(i, j)的对应关系:的对应关系:生成顶点的代码如下:生成顶点的代码如下:参见教材P213814.2.2 计算索引定义三角形为计算三角网格中的顶点索引,只需从左上角为计算三角网格中的顶点索引,只需从左上角到右下角遍历每个方格,然后计算组成这个方到右下角遍历每个方格,然后计算组成这个方格的两个三角形
6、的顶点索引值,参照图格的两个三角形的顶点索引值,参照图14.6。914.2.2 计算索引定义三角形通过图通过图14.6发现在第发现在第i行第行第j列列的方格具有以下性质:的方格具有以下性质:参见教材P215 生成索引值的代码:生成索引值的代码:参见教材P2151014.3 纹 理Terrain类提供了两种方法来为地形加上纹理,类提供了两种方法来为地形加上纹理,较简单的方法是载入并使用一个先前做好的纹较简单的方法是载入并使用一个先前做好的纹理图。理图。Terrain类中实现了下面的方法,把图片文件中类中实现了下面的方法,把图片文件中的纹理数据载入到的纹理数据载入到IDirect3DTexture
7、9对象中,对象中,并使用并使用_tex指针指向它。指针指向它。Terrain:draw方法在方法在渲染地形之前将会设置渲染地形之前将会设置_tex的值。的值。这里给出该函数的具体实现,其过程非常简单:这里给出该函数的具体实现,其过程非常简单:参见教材P2161114.3.1 用程序生成纹理数据另一种给地形贴上纹理的方法是用程序计算纹理数据,另一种给地形贴上纹理的方法是用程序计算纹理数据,也就是说先创建一个也就是说先创建一个“空空”的纹理,然后在代码中按的纹理,然后在代码中按照一些预定义的参数来计算每一个纹素的颜色值。照一些预定义的参数来计算每一个纹素的颜色值。 通过用通过用Terrain:ge
8、nTexture方法创建纹理:首先调用方法创建纹理:首先调用D3DXCreateTexture创建一个空的纹理,然后锁定最创建一个空的纹理,然后锁定最高级别的数据区,遍历每个纹素并设置它的颜色值。高级别的数据区,遍历每个纹素并设置它的颜色值。 Terrain:genTexture方法也同时会计算方法也同时会计算mipmap纹理。纹理。用用D3DXFilterTexture函数可以完成这个任务,函数可以完成这个任务,genTexture方法的实现代码如下:方法的实现代码如下:参见教材P2161214.4 光 照Terrain:genTexture方法里会调用方法里会调用Terrain:light
9、Terrain方法,这个方法为地形增方法,这个方法为地形增加了光照效果,从而使渲染的真实感更强。加了光照效果,从而使渲染的真实感更强。 为什么要照亮所渲染的地形呢,而且为什么不为什么要照亮所渲染的地形呢,而且为什么不让让Direct3D来处理光照呢?我们自己来进行这来处理光照呢?我们自己来进行这种计算会有如下的三个好处:种计算会有如下的三个好处:不用保存顶点法线的信息,节省内存。由于地形一般是静态的,光源的位置也不会改变,因此预先计算好光照,就可以省下Direct3D实时为地形计算光照的时间。顺便做一些数学练习,熟悉基本的光照理论,练习使用Direct3D的函数。1314.4.1 概述这项用来
10、计算地形阴影的光照技术是最基础的技术之这项用来计算地形阴影的光照技术是最基础的技术之一,被称为散射光照技术。设置一个平行光源,并指一,被称为散射光照技术。设置一个平行光源,并指定它的方向与从光源发出的光线的方向相反。定它的方向与从光源发出的光线的方向相反。从图从图14.7可以看到这个角度越大,方格就越背离光源,可以看到这个角度越大,方格就越背离光源,得到的光照就越少。得到的光照就越少。 通过光照向量和表面法线向量之间的角度关系,可以通过光照向量和表面法线向量之间的角度关系,可以构建一个阴影因子,它的取值范围为构建一个阴影因子,它的取值范围为0, 1,并决定,并决定表面获得的光照量。表面获得的光
11、照量。 1414.4.2 计算方格的阴影首先需要在方格表面中找到两个向首先需要在方格表面中找到两个向量,它们非零而且互相不平行,这量,它们非零而且互相不平行,这就是图就是图14.8中的中的u和和v:通过通过Terrain:computeShade方法方法来计算指定方格的阴影因子。它的来计算指定方格的阴影因子。它的3个参数分别是指定方格的行数、个参数分别是指定方格的行数、列数和光源的方向:列数和光源的方向:参见教材P2201514.4.3 计算地形阴影只需简单地遍历每一个方格,为每一个方只需简单地遍历每一个方格,为每一个方格计算对应的纹理元素的阴影因子,并用格计算对应的纹理元素的阴影因子,并用纹
12、素中的颜色值乘以这个因子,这样就能纹素中的颜色值乘以这个因子,这样就能使光照少的方格变得暗一些。使光照少的方格变得暗一些。下面的代码片段展示了下面的代码片段展示了Terrain:lightTerrain方法的重要部分:方法的重要部分:参见教材P2211614.5 在地形上“行走”为了得到为了得到Y轴高度,首先根据摄像机的轴高度,首先根据摄像机的X、Z坐标找出坐标找出当前所在的方格。通过当前所在的方格。通过Terrain:getHeight函数可以函数可以实现这个功能,它以摄像机的实现这个功能,它以摄像机的X、Z坐标为参数,返回坐标为参数,返回摄像机所在地面的高度值。具体的实现如下:摄像机所在地
13、面的高度值。具体的实现如下:float Terrain:getHeight(float x, float z)/ 通过XZ平面上的平移,使地形的起始位置回到原点x = (float)_width / 2.0f) + x;z = (float)_depth / 2.0f) - z;/ 通过把X,Z坐标除以_cellSpacing,使每个方格的宽度变成“单位1”x /= (float)_cellSpacing;z /= (float)_cellSpacing;1714.5 在地形上“行走”首先,通过首先,通过xz平面上的平移,使地形的起始位平面上的平移,使地形的起始位置回到原点。图置回到原点。图1
14、4.9为转换前后的地形对比:为转换前后的地形对比:地形起点回到原点,方格宽度变为地形起点回到原点,方格宽度变为1,+Z轴指轴指向下方。向下方。行数就是行数就是x的整数部分,列数就是的整数部分,列数就是z的整数部分。的整数部分。函数函数floor(T)将返回不大于将返回不大于t的最大整数。的最大整数。1814.5 在地形上“行走”下面的任务就是找到摄像机下面的任务就是找到摄像机X、Z坐标所在方格坐标所在方格的高度的高度(Y轴的高度轴的高度)。这就需要一些技巧了,因。这就需要一些技巧了,因为方格可能在两个方向上都是倾斜的,请参看为方格可能在两个方向上都是倾斜的,请参看图图14.10。图图14.11
15、展示了转化后的方格。展示了转化后的方格。1914.5 在地形上“行走”图图14.12(b)展示了这个插值点,那么向量展示了这个插值点,那么向量(q + dxu + dzv)的的y分量就是分量就是X、Z坐标处的高度值坐标处的高度值 Terrian:getHeight最后一部分代码如下:最后一部分代码如下:参见教材P224这里面这里面Lerp函数实现的是基本的一维线性插值运算:函数实现的是基本的一维线性插值运算:float d3d:Lerp(float a, float b, float t)return a - (a*t) + (b*t);2014.6 Terrain示例程序本章的示例程序通过给
16、定的包含高度数据的本章的示例程序通过给定的包含高度数据的RAW文件创建了一个地形,并计算其纹理和光文件创建了一个地形,并计算其纹理和光照。照。 首先,加入了下面的全局变量来描述地形、摄首先,加入了下面的全局变量来描述地形、摄像机以及帧速计数器:像机以及帧速计数器:Terrain *TheTerrain = 0;Camera TheCamera(Camera:LANDOBJECT);FPSCounter *FPS = 0;下面是主要框架的代码:下面是主要框架的代码:参见教材P2252114.7 一些改进措施你可以通过把地形分成一个矩阵,这种矩阵被称为你可以通过把地形分成一个矩阵,这种矩阵被称为“
17、Blocks”。每一个。每一个Block代表了地形的一个矩形区代表了地形的一个矩形区域。此外,每一个域。此外,每一个Block也包含了这一个也包含了这一个Block所代表所代表地形区域的几何信息地形区域的几何信息(这些数据存储在这个这些数据存储在这个Block自己自己的顶点的顶点/索引缓冲中索引缓冲中)。每一块。每一块Block都负责渲染它自都负责渲染它自身身也就是整个也就是整个Terrain的一个部分。的一个部分。还有一种方法就是用还有一种方法就是用ID3DXMesh接口来装载地形几接口来装载地形几何信息,然后用何信息,然后用D3DX库的函数库的函数D3DXSplitMesh把地把地形网格分
18、成各个小的网格。形网格分成各个小的网格。D3DXSplitMesh的原型如下:的原型如下:参见教材P2272214.8 小 结可以用具有不同高度、不同颜色的三角形网格来创建高山和低可以用具有不同高度、不同颜色的三角形网格来创建高山和低谷,以此模拟一个真实的地形。谷,以此模拟一个真实的地形。高度图是一个储存了地形各个顶点高度数据的数据集合。高度图是一个储存了地形各个顶点高度数据的数据集合。可以用硬盘上的图片来为地形加上纹理,也可以用程序生成地可以用硬盘上的图片来为地形加上纹理,也可以用程序生成地形纹理。形纹理。通过计算每个方格的阴影因子来标识这个方格究竟会有多亮通过计算每个方格的阴影因子来标识这个方格究竟会有多亮/多暗。阴影因素由光线向量与方格的法线向量之间的角度来计多暗。阴影因素由光线向量与方格的法线向量之间的角度来计算。算。让摄像机在场景中移动时,需要寻找当前所处的三角形。然后,让摄像机在场景中移动时,需要寻找当前所处的三角形。然后,计算三角形上的两个向量,这两个向量尾部相接,且与三角形计算三角形上的两个向量,这两个向量尾部相接,且与三角形的两边重合。通过一个左上角在原点的单位化方位的的两边重合。通过一个左上角在原点的单位化方位的x坐标与坐标与z坐标为参数,在这些向量上进行线性内插,从而求出高度值。坐标为参数,在这些向量上进行线性内插,从而求出高度值。23