对于一个有不少物体的3D场景来讲,渲染这个场景最简单的方式就是用一个List将这些物体进行存储,并送入GPU进行渲染。固然,这种作法在效率上来讲是至关低下的,由于真正须要渲染的物体应该是视椎体内的物体。除此以外,从裁剪算法和碰撞检测等算法的效率来讲,使用这种数据结构也是至关低效的。比较好的方式是使用具备层次结构的空间数据结构存储待渲染的物体,如BVH(包围体层次结构)、BSP(二叉空间分割)树、四叉树、八叉树和模糊K-D树等,在进行空间查找的时候将时间复杂度从O(n)下降到O(logn)。固然,对应的代价是每帧更新的时候,须要更新对应的空间数据结构。本文主要对BVH、BSP和八叉树进行介绍。算法
一、BVH(bounding volume hierarchies,包围体层次结构)数据结构
BV(bounding volume,包围体)是包含一组物体的空间体,它要比所包含的几何物体形状简单得多,因此在使用包围体进行碰撞检测等操做的时候比使用物体自己更快。常见的包围体有AABB(axis-aligned bounding boxes,轴对齐包围盒),OBB(oriented bounding boxes,有向包围盒),以及k-DOP(discrete oriented polytope,离散定向多面体),详细解释见维基百科。性能
对于三维场景的实时渲染来讲,BVH是最常使用的数据结构,例如,BVH常常用于视椎体裁剪。场景以层次树形结构进行组织,包含一个根节点、内部节点和叶子节点。最高节点是根,没有父节点;叶子节点保存着须要绘制的实际几何体(BVH只能存储几何体,实际渲染的物体除了几何属性还有其余属性,通常使用场景图表示,参看维基百科)。树中的每一个节点,包括叶子节点,都有一个包围体,能够将其子树的全部几何体包围起来,这就是BVH(包围体层次结构)名字的来源,图1为BVH的示例。大数据
图1 BVH示例,左图为6个物体的简单场景,右图为对应的BVHspa
在场景中的物体移动的时候,须要对BVH进行更新。若是物体移动之后仍然在原先的包围体内,则不须要更新;若不在,则删除这个物体所在节点,并从新计算其父节点的包围体,而后,从根节点开始,以递归形式将这个节点插入这棵树中。另外一种方法则是以递归形式增大父节点的包围体,直到这个包围体能够包含这个子节点为止。不管使用哪一种方法,随着编辑操做的增多,这棵树会变得愈来愈不平衡,效率也会愈来愈低。3d
二、BSP(binary space partitioning,二叉空间分割)树指针
BSP树在1969年由Shumacher首次提出,当时并未想到能成为开发娱乐产品的算法,但从90年代初BSP树就已经被用于游戏行业来改善性能,并使利用地图中更多细节成为可能。第一个使用该技术的游戏是Doom,由游戏行业中的两位传奇人物JohnCarmack和John Romero创立。blog
在计算机图形学中,BSP树有两种不一样的形式:分别为轴对齐(axis-aligned)和多边形对齐(polygon-aligned)。要建立BSP树,首先用一个平面将空间一分为二,而后将几何体按类别划分到这两个空间中,随后以递归形式反复进行这个过程。这种树有一个很是有趣的特性,若是按照必定的方式对树进行遍历,那么会从某个视点将这棵树包含的几何体进行排序(对于轴对齐的方式来讲,它是粗略的排序;对于多边形对齐方式来讲,它的准确的排序)。在Z-Buffer问世以前,基于多边形对齐的BSP树成为3D游戏进行场景排序的最佳方案。排序
2.一、轴对齐BSP树递归
轴对齐BSP树的建立方式以下:首先,将整个场景包围在一个AABB中,选取xyz其中一个轴,生成一个与之垂直的平面,将该AABB分为两个小AABB,而后以递归的方式继续对生成的AABB进行分割,直到树达到最大深度或AABB中包含的几何图元数量低于用户定义的某个阈值。轴对齐BSP树的结构如图2所示。
图2 BSP树结构
在分割的时候,有些物体会与分割平面相交,对这些物体有如下几种处理方法:1)存储在分割平面所在的节点中,如图2中的三角形也能够存储在1b中(图2采用的是第二种方法);2)存储在多个叶子节点,如图2所示;3)将这个物体由这个平面分为两个物体。第一种方法效率比较低,好比物体比较小,可是刚好被平面分割,不得不将其存在较高的节点中,因此实际中不多采用这种作法;第二种方法会致使相同的物体被绘制屡次,通常使用timestamping方法对其进行改进,即对绘制过的几何体作一个标记,绘制前对这个标记进行检查,若已经绘制过,则再也不绘制,这种方法使用的比较多;第三种方法因为要对图元进行分割,因此计算量比较大,并且对于动态物体的渲染不太方便。
分割平面的选取也有多种方法:1)按照xyz-xyz……的顺序进行循环分割,若是每次都选择中间位置,则结果和八叉树同样。固然,也能够随意选定位置,BSP树相对于八叉树的优点之一就是平面的位置能够随意选择。2)对AABB的最长边进行分割。3)经过相关算法(geometric probablity theory)计算出近似最优的分割平面,该分割平面将尽量少的与几何体相交。
轴对齐BSP树具备粗略排序的特征,对遮挡裁剪(occlusion culling)等算法具备较好的加速做用。在遮挡裁剪中,若是可以按照从前日后的顺序进行渲染,则能够减小像素着色器的负担。使用轴对齐BSP树,从根节点开始,先遍历靠近视点一侧的全部物体,再遍历远离视点的全部物体,对于每一个子节点采用这种方式递归,则能够作到近似的从前日后进行渲染。因为它没有对叶子节点内的几何体进行排序,并且一个物体可能在多个叶子节点中,因此轴对齐BSP树只是粗略的排序。
2.二、多边形对齐BSP树
多边形对齐的BSP树采用多边形所在的平面进行空间分割,具体方法以下:在根节点处选取一个多边形,用这个多边形所在平面将场景中剩余的多边形分为两组。对于与分割平面相交的多边形,沿着相交线将这个多边形分为两个部分。接下来采用一样的方式对这些多边形进行递归分割,直到全部的多边形都在这棵BSP树中,如图3所示。
图3 多边形对齐BSP树示意图
多边形对齐BSP树的建立很是耗时,适合静态场景,它的优势是对于给定视点,能够按照严格排序的顺序进行遍历,在没有Z-Buffer的DOS时代,这种方法是3D游戏制做中一种很好的方法,著名的FPS游戏Doom和Quake都使用了这种方法。固然,如今已经再也不使用了这种方法(它的好处被Z-Buffer取代,其缺点是只适用于静态场景,使用不便)。
三、八叉树
八叉树相似轴对齐BSP树。沿着轴对齐包围盒的三条轴对其进行分割,分割点必须位于包围盒的中心点,以这种方式生成8个新的包围盒。八叉树经过将整个场景包含在一个最小的轴对齐包围盒中进行构造,递归分割,直到达到最大递归层次或包围盒中包含的图元小于某个阈值,其分割过程如图4所示。八叉树的使用方式与轴对齐BSP树同样,能够处理同类型的查询,也能够用于遮挡裁剪算法中。因为八叉树是具备规则的结构,因此有些查询会比BSP树高效。
图4 八叉树分割过程
在八叉树的结构中,一般将物体保存在叶子节点中,其中有些物体必须保存在多个叶子节点中(2.1节第二段的第二种方法)。这种作法的一个最大弊端是增大数据量,而若是使用指针,则会致使八叉树的编辑变得更加困难。“松散的八叉树”算法对这个问题进行了改进。
四、空间结构的选取
目前为止,没有一种空间结构绝对的最优,根据不一样场合选择不一样的空间结构是一个明智的作法。八叉树和BSP树相比,因为其不须要存储分割平面的信息(由于每次都是在包围盒的中心点进行分割),因此要比BSP效率更高。可是这不是绝对的,例如在一个细长的走廊场景中,用BSP明显更合适,由于对沿着走廊的轴分割次数确定要比另外两个轴来得多,若是使用八叉树,要么浪费另外两个轴的分割空间,要么沿着走廊的轴分割不够细。
通常来讲,对于室内场景使用BSP树,由于1)室内场景遮挡比较严重,使用BSP树在特定的位置分割有助于提高效率;2)室内极可能朝某个方向延伸比较多,好比一条细长的走廊。对于大规模室外场景,使用八叉树比较好,由于场景中的物体比较分散,并且不会出现太多的遮挡,因为不用存储分割平面位置,使用八叉树这种规则的空间结构可以提升效率。固然,若是这个大规模室外场景的物体主要集中在地面,使用四叉树进行空间管理会比较好。