BSP tree

Quake 3用的主要是BSP算法,这种算法主要用来做室内游戏引擎(我指的是实时方面,其实BSP的概念很广),BSP技术很快,而且很成熟,包括游戏中的碰撞检测和预处理中的PVS计算和辐射度计算都可以利用到BSP,缺点是,只能处理静态物体,动态物体需要我们另外处理,你可能已经听说BSP正在越来越不受欢迎,无论如何,作为一种经典,这里先介绍一下。Quake 3的发展过程中用了很多不同的消隐算法,包括Beam Tree,Span Sorting,Edge Sorting等,有兴趣可以去看gamedev里面Abrash那篇Ramblings in Realtime


1. 什么是BSP树


BSP算法的初始数据是一个多边形集,可能大多数情况是三角形,至少是凸多边形,但是Q3允许凹多边形,因为这可以减少多边形的数量。BSP在预处理的时候先在多边形集中选取一个多边形作为支持平面,然后根据这个平面将集合划分成两个部分,每个部分是一个新的子节点,递归进行该过程,直到每个子节点中的多边形都构成一个凸区域,每个区域是一个叶节点,或成为cell,然后算法预计算在每个区域中可以见到哪些区域,得到PVS(潜在可见集)。上述过程很复杂,我们后面会更具体的介绍,下面先假设你已经得到了这样一颗BSP树,你可以做什么呢?


2. 如何利用BSP


Q3的渲染器先渲染世界,然后再渲染精灵。在渲染世界的时候,它先检查PVS有没有发生变化,有才重新计算。它会先遍历BSP,确定当前视点所在的cell,这很简单,直接通过与支持平面的位置测试就可以了,如果视点没有变化,则直接用上次的PVS,否则它会递归遍历整颗树,先将节点和视四棱域做包含测试,显然,如果父节点不可见,则其下的整颗子树可以被快速排除。Q3的测试方法很有效率,因为会预先计算视四棱域的裁减平面,并将平面按八个象限以及平行于坐标平面的情况分成11类,这样就可以利用平面的特殊性。然后Q3会做一个递归(实际上大多数递归包括上面提到的,Q3都用循环来代替了),把叶子节点的PVS信息逐级传递到父节点,这样的好处是当树自根向下遍历时可以在达到叶子节点前快速排除父节点。然后Q3就可以利用PVS排除不可见的节点,把可见节点中的表面添加到待绘制列表(PS:PVS的查找是通过位向量实现的,所以可以大大压缩PVS数据量),添加的时候会把表面的各种属性按照一定优先级“或”到8字节中,就是高位存放优先级高的属性编号,比如shader,地位存放优先级低的,然后Q3就可以通过一次快排,决定表面绘制的顺序,这种渲染状态管理方法看上去虽然很简单,但还是不失巧妙。BSP树还可以用来做相对于视点距离的排序,但是Q3没有这么做,可能是因为并不见得有效率。



3. 构建BSP树



BSP树绘制起来很简单,但是构建起来就不那么容易了。


BSP从当前表面集中选取一个作为分割平面,这个平面一般用启发式选取,保证树的两个子节点包含的表面几乎相等,或者位于尽量中心的位置,选择的标准视具体情况而定。利用这个平面,将空间划分成左右两个部分,再对左右两部分分别调用上述过程,直至构造出一颗树,每个节点中的多边形都构成凸包,即任一平面都不穿透其它平面,或说其它平面都位于该平面的同一侧,凸包有很好的性质,你可以按任意顺序绘制其中的表面,它们不会互相重叠,这避免了重绘,当然光凭这点还不足以满足实时需求,我们还要引入PVS,BSP中的每个叶节点都包含一个凸区,我们可以预先计算在该凸区可见的所有叶节点,在游戏运行时,将视点与BSP的分割平面递归做方位测试,即可决定玩家所在节点,然后只需绘制在该节点可见的节点,这很好,但是如何获得PVS查找表?先引入Portal的概念,Portal将世界看作互相连接的扇区,所谓扇区就是从其中任一点出发到另一点都不穿过其边界,凸包就有这样的性质,Portal本身是连接扇区的门户,是一种特殊的表面,一节点相对于另一节点的门户可见,该节点便可添加到另一节点的PVS中,所以先讨论怎么通过BSP自动构建Portal。



我们在每一节点,在分割面上构建一个大于世界包围盒的多边形作为初始多边形,挖掉分割面上的多边形,然后分别送到左右子树中去,在左右子树中,再次与分割面求交,并对新遇到的分割面也做上述处理,但还要和父节点的分割面求交,继续该过程,直到当前节点为叶节点,与cell中的多边形求交减去重复的部分和位于凸包外的部分,然后将得到的多边形作为叶结点的Portal。可以看出,如果不允许凹多边形出现,实现起来会很麻烦,当然,最后的多边形还应该做优化,否则会有大量多余的顶点和Portal。



得到每个叶节点的Portal列表后,我们可以进行PVS的构建了,因为相邻的连通节点一定相互可见,所以他们之间的测试可以免去,同样,不连通的节点一定相互不可见,所以也可以免去测试。我们可以用光线追踪方法做这个测试,只要采样足够,正确性还是可以接受的,可以均匀采样,也可以自适应采样,但要注意二者都不能保证不遗漏多边形。测试两个节点时,只要从一个节点的任一点可以看见另一节点,二者就相互可见,实际上,我们只要测试门户上的点的关系,这为什么是正确的呢?因为只要有光线能从A的门户到达B的门户,则光线延长线上必有点分别位于AB中,反之,若没有光线能从A的门户到达B的门户,由于AB的点想要达到对方必然经过门户,所以一定到达不了彼此。我们这里假设门户多边形上的点位于同一平面,同时门户多边形一定是凸的,不是的话可以分割成几个凸的,可以随机的在平面上选取采样点,抛弃位于多边形边界外的采样点,对任意两门户的采样点连线进行光线追踪,若均无交,则AB互不可见,易知这种计算量是及其巨大的,还好,这一切都发生在预处理阶段,而且我们有很多方法加速光线追踪,BSP本身就是一种,但据说kd-tree是最快的,还可以用shaft culling等。

你可能感兴趣的:(游戏,算法,tree,测试,shader,sorting)