【Visual C++】游戏开发笔记四十八 浅墨DirectX教程十六 三维地形系统的实现
本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
文章链接: http://blog.csdn.net/zhmxy555/article/details/8685546
作者:毛星云(浅墨) 邮箱: happylifemxy@163.com
上个星期浅墨写的介绍三维摄像机的文章和示例程序放出以后,大家似乎都表现出了很高涨的热情,不少朋友评论或者给浅墨发邮件问什么时候讲地形和天空顶,本来浅墨是准备这个星期就开始讲可编程渲染流水线的,看大家这么强烈的要求,浅墨决定干脆把准备在后面讲的地形天空一气呵成,跟在摄像机后面一起讲了。所以,这篇文章就诞生了。
想创造出极具真实感的三维游戏世界,三维地形的模拟是必不可少,至关重要的。
三维地形模拟其实是一个很广阔的课题,它其实不仅仅局限于我们的游戏开发领域,在三维仿真,虚拟现实等领域都涉及。说起三维地形模拟,似乎有那么一丝神秘,其实,只要了解其实现原理了这所谓的地形系统模拟也就是纸老虎一只。这篇文章里我们就来揭开三维地形模拟的面纱,看看到底怎样利用一个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》的相应章节吧。好了,下面贴出详细注释的代码:
对每行的单元格数目、每列的单元格数目、单元格间的间距、高度缩放系数、地形的宽度、地形的深度、每行的顶点数、每列的顶点数、顶点总数各个击破,就写出了下面这几句代码:
呼,困死了。。。睡觉去了,明天再来修改这篇文章,现在欧洲时间凌晨两点半了= =
本节笔记配套源代码请点击这里下载:
【浅墨DirectX提高班】配套源代码之十六下载