渲染3D地形
关键字:高度图、四叉树、二元三角树

概述:

基于高度图的3D地形渲染,其最基本的原理就是,地形上各个点的高度值存于一个文件中(高度图),渲染时,把高度图中的高度数据设为顶点缓冲区中对应顶点的Y坐标值,那么自然就会产生高低起伏的地形了。

一、空间划分

为了提高帧率,不可能在每帧都渲染所有的顶点,如果只渲染看得见的范围内的顶点,那将大大提高渲染速度。为此,有必要对地形进行空间划分。

我在这里用的是四叉树的数据结构来划分地形。这个四叉树又被称为地形树(TerrainTree或者QuadTree)。四叉树的每个接点(除叶接点)都有4个子结点。这4个子结点平均地划分他们的父结点所占据的区域。依次类推,直到叶结点。

叶结点是非常特殊的,首先它没有子结点,其次它还是渲染贴图的最小单位。它对应4个顶点,通过顶点索引,这4个顶点对应2个等腰直角三角形,这2个等腰直角三角形的斜边拼在一起,成为一个正方形,贴图便对应这个正方形。

叶结点又被称为BlockNodeLeaf(简称BNL)

二、LOD

二元三角树是一个很奇特的数据结构。每一个等腰直角三角形都可以被中垂线平均化为两个新的等腰直角三角形。而且二元三角树可以很好的解决T裂痕的问题。我就用这个数据结构来细分BNL。

根据BNL距离照相机的远近,确定此BNL的二元三角树的层次。实际还有Variant的概念,就是直角三角形的斜边中点的当前高度与高度图上对应点的高度相差不超过Variant的话,就不必细分此直角三角形了。
二元三角树是生长在四叉树的叶结点BNL上的,每一个BNL不是由2个直角三角形拼成的么?这2个直角三角形就是此BNL上的二元三角树的根结点。

三、可视判断

3D游戏中,常把场景组织在一个树结构中,为得就是可以快速判断出可视区域。其思想是:如果当前结点完全不可见,那么它的所有子结点也必然完全不可见;如果当前结点完全可见,那么它的所有子结点也必然完全可见;如果当前结点部分可见,那必须依次判断它的子结点。这是一个递归的算法。

我的地形树,由于是四叉树,我理所当然的用包围盒来做可视判断。为了提高速度,还在包围盒的外面又加了一个包围球(包围球的碰撞检测是最快的,其次是包围盒)。与地形树结点相对应,照相机的视域(View Frustum,被削去了尖顶的金子塔就是这样。照相机的位置就刚好位于尖顶)最外层首先是一个包围球,然后,里面是一个包围锥,在里面就是真正与视域的六个面贴身的6个包围面(每个面的法向量都是指向视域内部的)。

四、渲染

找出可视BNL后,就可以依次渲染每个BNL了。VertexBuffer只有一个,IndexBuffer却会有很多,是按贴图来划分的,并且每个IndexBuffer都被单独保存在一个TerrainGroup里。使用相同贴图的BNL都共用相同的IndexBuffer。

首先把找出来的可视BNL按其使用的贴图,将其投入到相应的TerrainGroup里。渲染时,只需设置一遍VertexBuffer,再依次对每一个TerrainGroup设置它的IndexBuffer、Texture,并做绘制操作。