二元空间分割树
作者:Paul Frazee (The Rainmaker),译者:Cater.Chen
在渲染3D游戏的室内场景时,BSP树是往往是一个比较复杂但非常有效的方法。它可以用来进行高效的深度检测。深度检测通常
用来决定哪个物体离视点更远,然后再根据从远到近进行多边形绘制,这样出来的结果才是正确的。
首先给出一个图,我们可以看出在Removal B区域,白色的多边形覆盖了黄色多边形,而在Removal A区域,黄色多边形覆盖了白色区域,如果没有深度检测,我们可能看到在Removal A和Removal B两个区域都是黄色多边形挡住了白色多边形,这和实际情况相违背。所以深度检测非常重要。
OpenGL直接提供了深度检测,但是它的效率很低,因此我们还是要考虑使用BSP树来解决问题。BSP树将多边形快速排序,并以树的形式保存,它的读取非常有效,可以在运行时快速地找到哪个多边形离视点最远。
在这个场景中,黄色字母代表墙壁,粉红色圆代表视点位置,虚线代表视线范围。
这个场景显然需要深度检测,否则A和F可能会挡住D, B和E, 这是不可接受的。我们需要创建一棵BSP树,其过程如下:
1) 编写一个函数,在所有多边形中选择一个多边形(所有多边形作为函数参数)
2) 将多边形转化为平面
3) 根据函数判断并分类出多边形位于平面的前面、后面、重合、穿越
4) 对穿越平面的多边形进行分割
5) 如果存在前向多边形,跳转到步骤1),传递这些前向多边形作为函数参数
6) 如果存在后向多边形,跳转到步骤1),传递这些后向多边形作为函数参数
7) 将重合的多边形加入到树的顶点列表中
你或许对次算法比较疑惑,那么让我们一步步来看,注意函数的参数是一个多边形列表,如果你要选择一个多边形,需要将所的多边形传递给函数
在第一步中,我们选择一个多边形(基于某种算法或者随机选择)并执行分割。
在本例中,我们选择多边形B.第二步,我们将多边形转化为一个平面,注意平面是无限大的。
现在我们有了一个平面B. 第三步,我们将所有的多边形进行分类,在下图中,一个黄色的后向多边形,一个紫色的前向多边形,一个绿色的穿越多边形,还有一个蓝色的重合多边形。
在分类上述多边形时,我们对每个多边形通过平面方程计算其到平面B的距离,并根据此距离对多边形进行分类。
第四步,需要我们对穿越的多边形进行分割,经过分割后,结果看起来是这样子的:
后面的步骤很简单了,第五步和六步需要我们对前向和后向多边形进行递归执行,但是递归总得有个结束的过程,因此最后一步需要添加退出递归的条件,即当多边形为重合时,将它加入到当前的多边形列表。
OK,现在执行了第一次分割,看看结果:
Node 1 (Partition B): B
|
Back: Cb, A, Fb---------Front: Ca, G, D, E, Fa
好,我们再来看看对前向多边形进行的第二遍分割。
这一次我们使用多边形D进行分割,经过分类与分割得到:
这时,BSP树变成了:
Node 1 (Partition B): B
|
Back: Cb, A, Fb---------Front (Partition D): D
|
Back: Ca,Gb---Front: Ga,E,Fa
不断的分割,最后得到:
Node 1: B
|
Back: A----------------Front: D
| |
Front: Cb Back: Ca--------Front: E
| | |
Front: Fb Front: Gb Back: Gaa---Front:
Gab
|
Front: Fa
分割完毕!我们得到了11个结点,每个结点都是父结点分割而来。现在我们比较疑惑的是它到底有啥好处呢?事实上,看起来
这里所作的工作仅仅是分割了一些多边形而已。
但这恰恰是渲染时所用到的,当渲染时,我们根据视点和平面的相对位置,并根据距离渲染BSP树中的结点。从结点B开始,执
行了以下几个步骤:
1) 求解视点到平面的距离
2) 如果距离大于0:
a. 遍历后向结点,跳转到步骤1)
b. 绘制当前多边形结点
c. 遍历前向结点,跳转到步骤1)
3) 如果2)结果为false:
a. 遍历前向结点,跳转到步骤1)
b. 绘制当前多边形结点
c. 遍历后向结点,跳转到步骤1)
这就是全部工作,根据视点位置进行渲染时是按如下顺序遍历的:
A, Cb, Fb, B, Fa, Gab, Gaa, E, Gb, Ca, D