专题 地形绘制基础_第1页
专题 地形绘制基础_第2页
专题 地形绘制基础_第3页
专题 地形绘制基础_第4页
专题 地形绘制基础_第5页
已阅读5页,还剩35页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、专题 地形绘制基础第1页,共40页,2022年,5月20日,1点11分,星期日抛砖引玉我们通常将游戏场面架设在起伏的山坡、绿草、沙滩、雪地、树木、阳光和雾等烘托出的自然环境中,这样的场景可增加游戏的逼真度和吸引力。大家玩过的游戏中是否有非常逼真的场景给你留下了深刻的印象的?Copyright 2008 By Neusoft Group. All rights reserved第2页,共40页,2022年,5月20日,1点11分,星期日麻雀虽小,五脏俱全 地形网格就是一系列三角形栅格(grid),但是方格中的每个顶点都被赋予了一个高度值(高度或海拔),这样该方格就可通过方格对应高度的平滑过渡来模

2、拟自然地形中山脉到山谷的变化。然后,通过纹理来表现沙滩、绿草如茵丘陵和雪山等 。Copyright 2008 By Neusoft Group. All rights reserved三角形栅格高度平滑过渡的三角形栅格具有光照和纹理的地形截图第3页,共40页,2022年,5月20日,1点11分,星期日按部就班我们这个专题的主要内容有:地形高度图的创建和修改创建地形的几何信息对地形进行纹理映射和光照处理实现摄象机在地形中行走的效果Copyright 2008 By Neusoft Group. All rights reserved第4页,共40页,2022年,5月20日,1点11分,星期日地形

3、高度图从几何形态来看,不同的地形外貌,取决于地形表面的每一个点距离地平面的高度值。这些高度值可用二维数组来存储,数组中的每一个元素就与地平面上每一个顶点的高度值一一对应。Copyright 2008 By Neusoft Group. All rights reserved地平面顶点地形网格顶点高度值第5页,共40页,2022年,5月20日,1点11分,星期日我们使用高度图(heightmap)来描述地形中的丘陵和山谷。通常高度图以灰度图(grayscale map)的形式保存起来。灰度图中的亮度越大,表示地形中某一点的海拔越高。Copyright 2008 By Neusoft Group.

4、 All rights reserved第6页,共40页,2022年,5月20日,1点11分,星期日创建高度图生成一个地形高度数据的最直接的方法是,利用图形编辑软件(如photoshop),绘制一个与地平面相同尺寸的灰度图形,用作地形高度图。图形绘制完毕后,将其保存为8位的RAW(原始二进制文件)文件,RAW文件连续存储了图像中以字节为单位的每个像素的灰度值。高度图被保存在磁盘中,通常为其每个元素只分配一个字节的存储空间,这样高度只能在区间0,255内。若0255的高度值不足以表达我们要表达的地形,可以将高度图数据加载到程序中时,对其进行一个比例变换,重新分配一个整型或浮点型数组来存储这些高度

5、值。Copyright 2008 By Neusoft Group. All rights reserved第7页,共40页,2022年,5月20日,1点11分,星期日我们在地形类(Terrain)中定义一个成员函数(readRawFile)用于读取高度图中的高度信息到一个向量里面:代码中的变量_heightmap是Terrain类的一个成员变量。Copyright 2008 By Neusoft Group. All rights reservedstd:vector _heightmap;bool Terrain:readRawFile(std:string fileName) / A h

6、eight for each vertex std:vector in( numVertices ); std:ifstream inFile(fileName.c_str(), std:ios_base:binary); if( inFile = 0 ) return false; inFile.read(char*)&in0, / buffer in.size();/ number of bytes to read into buffer inFile.close(); / copy BYTE vector to int vector _heightmap.resize( _numVert

7、ices ); for(int i = 0; i CreateVertexBuffer( _numVertices * sizeof(TerrainVertex), D3DUSAGE_WRITEONLY, TerrainVertex:FVF, D3DPOOL_MANAGED, &_vb, 0); if(FAILED(hr) return false; / 对应第一个生成的顶点坐标 int startX = -_width / 2; int startZ = _depth / 2; / 对应最后一个生成的顶点坐标 int endX = _width / 2; int endZ = -_depth

8、 / 2; /计算相邻纹理坐标的增量值 float uCoordIncrementSize = 1.0f / (float)_numCellsPerRow; float vCoordIncrementSize = 1.0f / (float)_numCellsPerCol;第16页,共40页,2022年,5月20日,1点11分,星期日Copyright 2008 By Neusoft Group. All rights reserved TerrainVertex* v = 0; _vb-Lock(0, 0, (void*)&v, 0); int i = 0; for(int z = star

9、tZ; z = endZ; z -= _cellSpacing) int j = 0; for(int x = startX; x Unlock(); return true;第17页,共40页,2022年,5月20日,1点11分,星期日Copyright 2008 By Neusoft Group. All rights reserved2、索引的计算为计算三角形栅格各顶点的索引,我们只需自右图的左上角起至右下角,依次遍历每个方格,并计算构成每个方格的三角面片的顶点索引。由右图我们可以推导出一个用于求出构成第i行、第j列的方格的两个面片的顶点索引的通用公式:ABC = i numVertsP

10、erRow + j inumVertsPerRow + j + 1 (i + 1). numVertsPerRow + j CBD = (i + 1) numVertsPerRow + j inumVertsPerRow + j + 1 (i+l) numVertsPerRow + j + 1 第18页,共40页,2022年,5月20日,1点11分,星期日Copyright 2008 By Neusoft Group. All rights reserved下面是计算索引的代码:bool Terrain:computeIndices() HRESULT hr = 0; hr = _device

11、-CreateIndexBuffer( _numTriangles * 3 * sizeof(WORD), / 每个三角形有3个索引 D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_MANAGED, &_ib, 0); if(FAILED(hr) return false; WORD* indices = 0; _ib-Lock(0, 0, (void*)&indices, 0); 第19页,共40页,2022年,5月20日,1点11分,星期日Copyright 2008 By Neusoft Group. All rights reserved / 将

12、组成一个方格的2个三角形的一组6个索引的开始位置编入索引 int baseIndex = 0; / 从头到尾计算每一个格子中的三角形 for(int i = 0; i _numCellsPerCol; i+) /行循环 for(int j = 0; j Unlock(); return true;第20页,共40页,2022年,5月20日,1点11分,星期日纹理映射Terrain类的纹理映射方式是加载一个已经创建好的纹理文件,然后再应用该纹理数据。右边的代码就是将纹理数据自文件加载到了_tex数据成员中,其中_tex是一个指向IDirect3DTexture9接口的指针。Copyright 2

13、008 By Neusoft Group. All rights reservedbool Terrain:loadTexture( std:string fileName) HRESULT hr = 0; hr = D3DXCreateTextureFromFile( _device, fileName.c_str(), &_tex); if(FAILED(hr) return false; return true; 第21页,共40页,2022年,5月20日,1点11分,星期日光照为了给地形场景增强真实感,我们在场景中添加光照的成员函数Terrain:lightTerrain。由于地形是静

14、态的,且光源一般也不发生移动,所以我们可以预先对光照进行计算,这样节省了Direct3D实时照亮地形那部分计算时间。并且这样的计算无需给每个顶点存储顶点的法向量,所以可以节省大量内存Copyright 2008 By Neusoft Group. All rights reserved第22页,共40页,2022年,5月20日,1点11分,星期日我们计算地形的明暗用到最基本的漫反射光的光照。给定一个平行光源,用“到达光源的方向”(该光源发出的平行光的传播方向的反方向)来描述该平行光源。再计算地形中的每个坐标方格计算光向量L和该方格的面法向量N之间的夹角。由右图可知,上述夹角越大, 坐标方格的朝

15、向偏离光源就 越大,其所接收到的光照就 越少。反之,越多。当夹角 超过90度,方格表明接收不 到任何光照。第23页,共40页,2022年,5月20日,1点11分,星期日由光向量和方格的面法向量之间的角度关系,我们可以构造一个位于区间0,1内的明暗因子(shading scalar),来表示方格表明所接收到的光照的量。明暗因子接近于0,则表示这两个向量的夹角很大。当纹理的颜色与其相乘时,颜色值就趋于0,从而呈现出教暗的视觉效果。反之,若该值接近1,则表示两个向量的夹角很小,当纹理的颜色值与其相乘时,该颜色基本保持了原来的亮度。Copyright 2008 By Neusoft Group. Al

16、l rights reserved第24页,共40页,2022年,5月20日,1点11分,星期日Copyright 2008 By Neusoft Group. All rights reserved坐标方格的明暗度计算光源的方向L是已知量,我们要想办法求出方格的法向量N。方格中任意两个向量的叉积可得到N,然后再对N进行规范化。右图中我们找到方格中两个非零 且不平行的向量:u,v。 u = (cellSpacing, by - ay, 0) v = (0, cy -ay, -cellSpacing) Nuv 第25页,共40页,2022年,5月20日,1点11分,星期日Copyright 20

17、08 By Neusoft Group. All rights reservedL和N之间的夹角我们转化为求他们的点积,3D空间中的两个单位向量的点积等于这两个向量之间夹角的余弦。S=LN -1s1 当s0时,取s0。代码:Terrain:computeShade 来计算明暗因子:float cosine = D3DXVec3Dot(&n, directionToLight);if(cosine 0.0f) cosine = 0.0f;float Terrain:computeShade(int cellRow, int cellCol, D3DXVECTOR3* directionToLig

18、ht) / 取得方格中三个顶点的高度(从高度图中) float heightA = getHeightmapEntry(cellRow, cellCol); float heightB = getHeightmapEntry(cellRow, cellCol+1); float heightC = getHeightmapEntry(cellRow+1, cellCol);第26页,共40页,2022年,5月20日,1点11分,星期日Copyright 2008 By Neusoft Group. All rights reserved / 创建方格中的二个顶点 D3DXVECTOR3 u(

19、cellSpacing, heightB - heightA, 0.0f); D3DXVECTOR3 v(0.0f, heightC - heightA, - cellSpacing); /用方格中的二个向量的叉积找到面法线 D3DXVECTOR3 n; D3DXVec3Cross(&n, &u, &v); D3DXVec3Normalize(&n, &n); float cosine = D3DXVec3Dot(&n, directionToLight); if(cosine 0.0f) cosine = 0.0f; return cosine;第27页,共40页,2022年,5月20日,1

20、点11分,星期日Copyright 2008 By Neusoft Group. All rights reserved对地形进行着色我们已经可以计算出每个方格的明暗因子,现在,只需遍历地形中的每个方格,计算其明暗因子,然后将该方格对应的纹理元的颜色与该因子相乘。下面一段代码展示了Terrain:lightTerrain方法的重要部分。DWORD* imageData = (DWORD*)lockedRect.pBits;for(int i = 0; i textureDesc.Height; i+) for(int j = 0; j textureDesc.Width; j+) int in

21、dex = i * lockedRect.Pitch / 4 + j; / 取得当前单元的颜色值 D3DXCOLOR c( imageDataindex ); / 给当前单元着色 c *= computeShade(i, j, lightDirection); / 保存着色结果 imageDataindex = (D3DCOLOR)c; 第28页,共40页,2022年,5月20日,1点11分,星期日在地形中“行走”地形的绘制以及纹理映射、光照的处理我们都完成了,现在我们来实现移动摄像机模拟在场景中行走的过程。摄像机的实现我们已经在前面的专题中实现了,可以直接使用封装好的摄像机类来使用摄像机。我

22、们要让摄像机在地形中行走,不能穿地形到下面,或者悬浮到地形上面。最主要的就是要根据摄像机的位置来确定它的高度(y坐标)。过程:依据给定的x和z坐标找到我们所处的坐标方格。Terrain:getHeight函数实现了该功能,它以x和z坐标为参数,并返回摄像机应处在的高度(海拔)。Copyright 2008 By Neusoft Group. All rights reserved第29页,共40页,2022年,5月20日,1点11分,星期日Copyright 2008 By Neusoft Group. All rights reservedfloat Terrain:getHeight(fl

23、oat x, float z) /在xz平面上把地形的START点移动到坐标原点的位置 x = (float) width / 2.0f) + x; z = (float) depth / 2.0f) - z; / 把cellspacing进行比例变换让其等于1 x /= (float) cellSpacing; z /= (float)_cellSpacing;首先,进行平移变换,将顶点start平移至坐标原点。然后,通过缩放因子为单元间隔的负倒数的比例变换将坐标方格的单元间隔归一化。现在,我们就转换到了一个新的参考系中,其中z轴方向向“下”(理解上)。上述过程如右图所示第30页,共40页,

24、2022年,5月20日,1点11分,星期日Copyright 2008 By Neusoft Group. All rights reserved经过这样的变换以后,坐标系与矩阵的顺序保持了一致。即左上角为原点,列索引和行索引分别沿着向右方向和向下方向递增。目前单元间距为1,所以,我们可以迅速求出当前我们所处的坐标方格的行列索引。知道了坐标方格的行列索引,我们就知道了当前所处的坐标方格,就可求出构成该方格的4个顶点的高度。float col = :floorf(x); /小于等于x的最大整数值float row = :floorf(z);/ A B/ *-*/ |/|/ *-*/ C D fl

25、oat A = getHeightmapEntry(row, col);float B = getHeightmapEntry(row, col+1);float C = getHeightmapEntry(row+1, col);float D = getHeightmapEntry(row+1, col+1);第31页,共40页,2022年,5月20日,1点11分,星期日如右图所示,当前所处的方格位置以及构成该方格的4个顶点的高度均已知。我们要求摄像机位于任意的位置(x,z)时,坐标方格单元的高度。因为方格可能朝多个方向发生倾斜,所以我们要求出这个y值相对比较困难。第32页,共40页,20

26、22年,5月20日,1点11分,星期日为了求出摄像机所在位置的高度,我们首先需要判断摄像机当前处于哪个坐标方格内。而每个坐标方格都是由两个三角面片的组合来绘制的。为了求出摄像机当前所处的三角形面片,我们需要对当前所处的坐标方格进行变换,使其左上角的顶点与坐标原点重合。由于col和row描述了当前坐标方格的左上角顶点的位置,我们必须沿x轴平移-col个单位,并沿z轴平移-row个单位。 float dx = x - col; float dz = z - row;第33页,共40页,2022年,5月20日,1点11分,星期日如果dz1.0-dx,我们当前就位于上三角形面片v0v1v2中,否则就在

27、下三角形面片v0v2v3中。当摄像机处于上三角面片中时,我们沿着该三角形的AB、AC两个边,以向量q=(qx,A,qz)为始端分别构造两个向量u = (cellSpacing, B -A, 0),v = (0, C - A, - cellSpacing) 。我们沿着u轴对点dx进行线性插值,再沿着v轴对dz点进行线性插值。若给定x坐标和z坐标,向量(q+dxu+dzv)的y分量就是该点所处的高度值。第34页,共40页,2022年,5月20日,1点11分,星期日下面是Terrain:getHeight函数的最后一部分代码:if(dz B float vy = C - A; / A-C height = A + d3d:Lerp(0.0f, uy, dx) + d3d:Lerp(0.0f, vy, dz) ; else / 下三角形 DCB float uy = C - D; / D-C float vy = B - D; / D-B height = D + d3d:Lerp(0.0f, uy, 1.0f - dx) +d3d:Lerp(0.0f, vy, 1.0f - dz); return height;Lerp函数是一个沿着一维直线的基本

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论