作者:昆山莱盛劳务 | 来源:互联网 | 2023-09-18 17:54
VOXEL 技术其实是一种很简单的技术,但它对于即时渲染地形确实又是一种十分有效的技术。写这篇文字的目的有两个,一方面是为大家介绍这个古老的技术,另一方面更重要的是希望大家看了本文能多有启发,从而发展出更多简单实用的好技术。就我自己的感觉而言,创作游戏程序,更多时候就是在寻找一种更简单而又更有效的方法。让一切不可能变为可能,这便是游戏程序设计之魅力所在。
BTW,本来去年就想写这篇文字的,居然能够拖到了今天,看来我的懒惰毛病是发作得非常厉害的了。算了,闲话休提,还是让我们现在就一起踏上有趣的 VOXEL 技术之旅吧!
VOXEL 技术出现得相当早,据 James Sharman 自称他在 95 年时就想出了这种方法。而 NOVALOGIC 则是这种技术的忠实拥护者,从早期的 COMMANCHE 到最近的 DELTA FORCE 和 DELTA FORCE II,都一直使用 VOXEL 技术来搭建图形引擎的核心。
VOXEL 技术的思想其实很简单,它不是用的现在很流行的多边形方法来描述地形,而是用的一种线性插值的办法来形成自然连续的起伏地形。如果只用一句话来描述就是由近及远地依次画出对应的地形轮廓线,同时记录屏幕投影的高度坐标值,以供画下一条轮廓线时比较,从而只画后面新露出来的部分。
根据这种想法,我们首先必须要考虑的就是如何来表示地形,以及如何去求取并绘制出每条地形轮廓线。
通常,在使用 VOXEL 技术时,我们会建立一个二维数组来记录地面上均匀分布的各点高度。比如 HEIGHT[3,4] = 100 就表示在位置 [3,4] 处的地面高度为 100,习惯上,通常的高度取值在 0 到 255 之间。
有了这个数组以后,下一步我们便依次开始求取并绘制出每条地形轮廓线。
在上图中,V 表示观察者所处的位置,L1 和 L2 分别表示的是前后裁减范围,红色线段所表示的就是我们要绘制的地形轮廓线。
对于任何一条红色线段来说,知道了观察者的位置、观察方向和观察夹角,以及到观察者的距离这几个条件以后,代入三角公式就可以很容易地计算出这条红色线段的两个端点位置。
现在我们有了这条红色线段的两个端点位置,下面就可以开始绘制地形轮廓线了。
首先我们从离观察者最近的一条红色线段开始,假设两个端点的位置是 (X1, Y1) 和 (X2, Y2),而实际的屏幕显示宽度为 W。
接下来我们把这条红色线段按等分取 W 个点,这些点就是我们绘制时用的采样点。
然后我们便该依次求取出每个采样点上的地面高度。在求这些采样点上的地面高度时,我们通常使用线性插值的方法。
|
比如有一点 N,它的位置是 (X, Y),其中 X=Xa+Xb,Y=Ya+Yb,Xa 和 Ya 是 X 和 Y 的整数部分,Xb 和 Yb 是 X 和 Y 的小数部分。按照线性插值,此处的地面高度应该是:
HN=(H(Xa,Ya)*(1-Xb)+H(Xa+1,Ya)*Xb)*(1-Yb)+(H(Xa,Ya+1)*(1-Xb)+H(Xa+1,Ya+1)*Xb)*Yb
如此这样,我们就求出了该采样点上的地面高度。然后,我们把这个地面高度按线段到观察者的距离进行坐标变换,最后得到该点对屏幕投影产生的纵坐标值,同时,该点对屏幕投影产生的横坐标值直接由它在这 W 个点中的排列位置得到,比如说第一个点的横坐标值就是 0,第二个点的横坐标值就是 1,第三个点的横坐标值就是 2 等等……。
为了正确地处理每一点的前后空间关系,我们将为每列保留一个最高纵坐标值。在实际显示地形轮廓线的每一点时,我们需要把每点的纵坐标值同该列的这个最高纵坐标值进行比较,并在超过此值时覆盖这个最高纵坐标值。由于我们目前是画的是最近的一条地形轮廓线,所以不需要考虑是否被更近的地形轮廓线所挡住,直接判断每点纵坐标值是否在屏幕以内就可以了,如果是的话则直接修改该列的最高纵坐标值为此纵坐标值,否则让该列的最高纵坐标值为最低。
接下来在由近及远依次显示除了最近的一条之外的其它地形轮廓线时,我们通过对每列的最高纵坐标值进行这种比较和更新,就可以跳过那些被遮盖住的部分,从而达到正确显示的目的。
一般地,在选取地形轮廓线时,越远的地形轮廓线可以选得越稀疏,由于透视的原因,这样不会产生明显的失真,而计算量可以减少很多。
到了这一步,剩下的工作已经不多了。觉得光是这样的地形轮廓线太简单了不是?想要看到更真实的地形吗?那么就让我们开始关于地表蒙皮的话题吧!
在 VOXEL 技术中,地表蒙皮是一件非常简单的事情,实际上就是用许多短竖线去填充地形轮廓线之间的空缺。
|
图五给出的是一种最简单的情况,只做了一些非常简单的对亮度的处理。在这种情况下,使用的是地形轮廓线之间填充按照亮度插值生成的短竖线的方法。理解了这种方法就不难去理解其它更复杂的方法了。
首先,我们必须求出地形轮廓线上每点(准确说应该是指的像素)的亮度,使用我们前面求每点高度时类似的办法,这个问题将不难解决,但我们还得先建立一个同高度二维数组 HEIGHT[x, y] 对应的亮度二维数组 BRIGHT[x, y]。
亮度的计算有很多种办法,这里介绍一种按坡度来求取亮度的简单方法:
BRIGHT[x][y] = 128+(HEIGHT[x+1][y+1]-HEIGHT[x][y])*4;
if(BRIGHT[x][y]<0) BRIGHT[x][y]&#61;0;
if(BRIGHT[x][y]>255) BRIGHT[x][y]&#61;255;
求出了地形轮廓线上某点的亮度以后&#xff0c;剩下的工作就是填充一条按照亮度插值生成的短竖线。
为了达到这个目的&#xff0c;我们在显示一条地形轮廓线时&#xff0c;除了会记录该列的最高纵坐标值外&#xff0c;还要记录产生最高纵坐标值的那一点的亮度值。这样当我们在画从第二条开始的地形轮廓线时&#xff0c;一旦发现当前采样点的纵坐标值超过了该列的最高纵坐标值&#xff0c;就按照从记录中最高纵坐标值对应的亮度值到当前采样点的亮度的顺序画一条亮度渐变的短竖线。这条竖线的起点就是记录中的最高纵坐标值&#xff0c;终点是当前采样点的纵坐标值。然后把该列的最高纵坐标值置为当前采样点的纵坐标值&#xff0c;并记录下最后的亮度值&#xff0c;即当前采样点的亮度值。
如此一来&#xff0c;亮度值地表就被我们轻松实现了。
下一步&#xff0c;为了获得更真实的效果&#xff0c;我们将开始考虑如何实现对地表的纹理映射。
在此要声明一句&#xff0c;对于纹理映射这个概念&#xff0c;我不想在这里详细解释&#xff0c;否则本文会变得太长且内容过于分散。所以以下便假设读者已经熟悉了 3D 中的纹理映射这个概念。如果你确实不知道的话就说一声&#xff0c;我回头再单独写成文字便是。
James Sharman 在他的文章中曾经提出了一种方法&#xff0c;就是按高度渐变来产生地表纹理映射。他首先为各个不同高度的地面建立一系列的纹理如下&#xff1a;
|
有了这一系列的纹理&#xff0c;我们接着在做地形绘制时&#xff0c;便按照每点的实际高度和以前面提到的 Xb、Yb 作为 U、V 坐标来选取对应的纹理进行映射。得到的结果如下图所示&#xff1a;
其中相邻两条地形轮廓线之间的填充是对 U、V 坐标使用线性插值方法得到的。为了让大家能更好地看清这个过程&#xff0c;请参考下图&#xff1a;
现在对这种先计算出若干地形轮廓线然后再用线性插值方法填充的思想应该理解得非常清楚了吧。
不过&#xff0c;在 DELTA FORCE 这样的游戏中&#xff0c;使用的是另外一种方式。在这种方式中&#xff0c;先把整个地图做成一张巨大的纹理&#xff0c;然后在映射时&#xff0c;是只按照位置来进行纹理映射而不考虑高度。这种方式可以得到更逼真的细节&#xff0c;但同时也要求更多的内存。另外&#xff0c;在 DELTA FORCE 中&#xff0c;阴影是直接做在纹理上的&#xff0c;而不是实时计算得到的&#xff0c;这样有利于提高效率。
说到这里无意抬头望了望天&#xff0c;发现时候已经不早了&#xff0c;太阳都快出来了&#xff0c;是该准备去休息的时辰了。虽然还有千言万语&#xff0c;但瞌睡来了确实挡都挡不住&#xff0c;所以只好溜去睡觉&#xff0c;言有未尽之处&#xff0c;大家莫怪……zzZ