6DOF Voxel地形渲染技术
作者:deathbravo from chinaunix
voxel 据说是Volumetric Pixel的缩写,传说中的“体素”。这是相对于二维图像的像素概念产生的用于描述空间点特征的概念。
许多文章说James Sharman在1995年提出的这种空间描述方法,俺暂时没去验证,但俺找到一个mailinglist里面一封发自1994年2月28日的信里面提到mars这个程序的效果非常好,希望了解它的技术细节。mars是Tim Clarke 在386上编写的用voxel渲染地形的demo.
voxel其实包含了两方面的意义,其一是空间描述方法,其实就是数据的记录方式,一般情况下就是记录x、y、z坐标处有个什么样的像素(体素);其二是渲染方法,也就是如何把那些体素画在屏幕上去,算是投影变换过程吧。
voxel技术之所以让俺着迷,那是因为10年前NovaLogic带来的Delta Force,在quake那让人头晕目眩的室内场景和伪室外场景之外,俺在计算机屏幕上看到了蓝天白云、青山绿水,而且用鼠标和键盘在其间任意游荡。在那个年代,voxel确实体现了威力,在性能相当低的机器上实时渲染大型的室外场景。mars这个程序是在386机器上运行的飞快的,只是现在看起来感觉相当粗糙。
voxel除了渲染地形,在医学影像方面应用似乎较多,好像也看到过用在机械设计方面的论文。
但是俺感兴趣的其实只是用它来渲染地形,因为简单。使用voxel方法,渲染地形时的运算量只与视野范围和分辨率相关,如果使用多边形渲染,就要考虑LOD的问题,而LOD是voxel的天然特性。
本来写这篇东西是为了写点实质的内容,却写了这么多行废话。下面开始实质。
voxel地形渲染所使用的数据是heightmap,有人称作高地图,实际上是DEM数据,俺这里用了一个1024x1024的图(Terrain.rar),每个点一个字节,也就是高度分辨率是256.内容大概的样子是这样。
黑色的是沟,白色的是山。
实际的地形是下面这个文件
|
文件: |
Terrain.rar |
大小: |
136KB |
下载: |
下载 | |
经典的voxel地形渲染可以控制视点在4个自由度上活动,就是传说中的4dof.
如上图可以上下左右移动,可以Z周为轴心旋转。就是可以跳可以蹲,可以走可以退,可以左移可以右移,就是不能抬头低头,也不能侧身翻跟头。视线只能水平的盯着前方。
地形渲染的方式有些像极度简化的光线投影。
光线投影对屏幕上每个像素投射一条光线,而voxel地形渲染时是对屏幕上每一列投射一条光线,从左边开始还是从右边开始无所谓,上图所示的是从左边开始投射n条光线。下图所示的是投出去的每条光线所面临的状况。
投出去的光线并不像光线投影时精确的计算角度和方向,由于现在描述的这个过程是针对4dof的,所以角度的问题已经完全被忽略了,可以关心的只有方向。那么为了简化计算,就可以根据方向角计算一个水平x方向和一个水平y方向的增量,这样来让光线一步一步的前进,每前进一步,就可以获得那一个地点的水平坐标,也就对应到前面那个地形文件里的某个像素,进而得到那个点的高程值。
这个高程值乘上一个比例系数就可以获得这个点在屏幕上应该画在什么位置了。因为每条投射线对应的屏幕x坐标是确定的,那么投射过程就是逐点来求地面各个点映射到屏幕上的y坐标。每次求出一个新点高度,就与前面所获得的最高的点比较,如果新点更高,那就画到屏幕上,如果没有前面的点高,那就直接丢弃,这样在没有深度缓冲区的情况下获得了深度缓冲区才具有的好处。
当屏幕上n条线都投射完,渲染也就完成了。
不过上面的叙述少描述了一个过程,也就是地面纹理映射,如果没有纹理,渲染出来的东西是没法看的。纹理的映射仅仅是在获得高程值的同时,从纹理缓冲区读取相应的颜色值来画到屏幕。如果纹理的尺寸和地形文件的尺寸一样,那就完全可以使用一样的坐标来操作。
下图是实验时使用的地表面纹理:
前面写的关于使用x,y方向增量来推进光线当然会使渲染结果比较不精确,如果想要获得精确的结果,当然也可以使用角度来计算,而且如果目标点没有精确落在某个高程点上时,可以通过临近点之间插值求得,见过许多人用双线性插值,其实效果也没好到哪去,有兴趣的人可以试试看样条插值。
明白了4个DOF的渲染方法,那就很容易前进到5个DOF中来。通常第五个DOF是指水平横轴,也就是控制抬头看低头看的这个轴。没有这个轴的状况下,渲染出来的地形几乎没有什么意义,谁会愿意一直盯着水平线四处瞎转。实际上,有了这个轴,也几乎就够用了,例子就是delta force,没有什么必要让人侧着翻跟头,即使视点在直升飞机上,也可以让人不跟着侧向摆动,除非一定要把视点放在战斗机这种极其喜欢侧翻的机器上。
废话又多了。5个dof时,控制参数多了一个垂直角度,控制视线上下移动。
上面这个图表明的增加第5个DOF后,光线投射的情况。从图中可以清楚的看出,对于视点和投影屏幕来说(也就是我把头向左偏一些,使投影屏幕看起来仍然是竖直的),整个地表被撬起来了,倾斜的角度就是视线和水平线的夹角。那么光线投射的方式不变,需要调整的仅仅是求地表高程到屏幕的投影算法,因为地面被撬起来了。这个调整其实也很简单,从图中看出地面被撬起的程度与夹角相关,根据夹角和距离就能求出每个点被抬高的数值,也就是图中红线表示的那一段。
下图是5DOF产生的效果
增加第6个DOF,手段非常简单,但是很不经济。
需要另外开辟一个更大的正方形缓冲区,用来代替屏幕,使用前面正常的流程做投影,然后以侧翻角度倾斜真正的屏幕缓冲区,从正方形缓冲区里面把需要的像素拷贝走。
这个方法实现起来很简单,但开销很大,所以我发现novalogic在AF3里面,仅仅使用坦克内部小范围视野的时候才开启第6个DOF。不过也许别人有更好的办法实现第6个DOF。
下图是6DOF的效果
拥有了6个自由度的地形渲染能力,接下来如果只面对空空荡荡的原野就没意思了,所以还需要增加其他物体的渲染能力。voxel有能力渲染除了地形以外的复杂物体,但是没有渲染地形这么简单有效,所以我这里的思路是使用多边形方法来构造和渲染其他物体。我这个思路的核心内容就是偷懒,因为多边形技术实在太成熟了,已经完全没必要再去费心思了。所以实现方法就是使用OpenGL来渲染多边形物体,再与voxel渲染的地形合成最终图像。
voxel渲染地形没有问题了,OpenGL渲染多边形也不在这里多叙述了,实际上主要的问题就是在如何合成。
合成时唯一需要考虑的就是物体和地形的遮挡关系,我不能看到山后面的物体,要实现这个目标,直接想到的就是zbuffer.我没什么办法去干扰OpenGL的渲染过程,所以只能从voxel下手。这样在地形的渲染过程中计算每个屏幕像素的颜色值的同时,也把他的z值计算出来,在OpenGL开始渲染之前,把这些Z值告诉OpenGL,这样它在渲染多边形物体的时候就知道要避开哪些地形了。
OpenGL的光栅操作中有glDrawPixels这样一个函数;
void glDrawPixels( GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
const GLvoid *pixels )
这个函数会把pixels指向的缓冲区的内容写入OpenGL内部的缓冲区,根据format类型不同,会写入不同的缓冲区。
比如format为GL_RGB时,pixels指向的内容作为图像内容被拷贝到颜色缓冲区。
当format为GL_DEPTH_COMPONENT时,pixels指向的内容作为深度值被拷贝到深度缓冲区。
有这两个参数,我就解决问题了,先把地形渲染生成的图像写入颜色缓冲区,再把地形深度值写入深度缓冲区,然后再执行正常的多边形物体渲染,二者就完美结合了。
阅读(2028) | 评论(0) | 转发(0) |