OSG 八叉树 与 Kd Tree

2018-09-01

   在游戏或者CAD程序中,我们经常面对的一个需求:已知空间(多数是二维、三维,也有少数高维空间)内的一个物体,求解距离它最近的k个物体是哪些。当然,这个问题可能很简单,假设空间内只有几百几千、甚至几万个个物体,我们只需要遍历一下即可。也花费不了几毫秒时间。但是,当空间内的物体有几百万、几千万个的时候呢,或者几亿几十亿呢?你个简单的查找就耗费几十分钟、几个小时,这绝不能忍受。所以,我们需要加速的优化手段。
   一个朴素的方法就是,先求解所有物体所占的空间的BoundingBox,名为bb,以后所有的操作只考虑这个bb空间,不会超过这个范围。把空间平均分割一下,如一维空间分成两份,二维空间就平均分成四份,三维空间就平均分成八份,n维空间就平均分成n^2份,切割完成后,把整体当作父节点,把小的一份当作子节点,那这就是二进制空间分割树,以上分别对应着二叉树、四叉树、八叉树。当把空间平分了之后,我们就可以向周围的子空间询问:是否有包含物体;包含了几个物体?假设物体在空间内均匀分布,那么就意味着只需要向邻居子空间询问即可完成搜索工作。
   但是,假如物体在空间中的分布不均匀呢?假想操场上只有一棵树,对这棵树建立包围盒bb,我们包围盒空间进行八叉树划分,递归的多进行m次,且假设m比较大,比如10。若地面上一无所有,一片叶子掉下来落在地面上,问:距离这片叶子最近的其他一片叶子在哪里?那得问多少次才能问到目标。八叉树的构建非常简单。但是,如果我们只偏重于查询,这样子就很浪费了。一个简单的优化就是:不再平分空间,而是稀疏的地方一个子空间很大,稠密的地方子空间就很小,这样就能通过向身边的子空间询问而很快获知最近的物体在哪个子空间。
   Kd Tree的构造方式和八叉树不一样。因为Kd Tree是二分树,而八叉树 是八分的。八叉树的leaf node把元素包含在内部,而Kd Tree以元素所在平面来划分空间,形成不同的节点,元素不单独属于任何一个节点。
   上面讨论的用途是邻近搜索。3D程序中还有一个更重要的需求是划分场景并优化,可做遮挡查询、LOD。
   CAD程序中,一般会使用到这两种数据结构。用八叉树来减轻渲染负载,用Kd Tree来优化业务相关的算法。

   OSG并没有直接提供八叉树的类型或者对外接口,但是,OSG内部的确是有八叉树实现的。osgUtil::Optimizer::SpatializeGroupsVisitor 类型就提供了四叉树、八叉树的构建功能。被优化的osg::Node,本身就是一个隐性的八叉,每一个branch节点都有8个子节点,它们都是新建的。optimizer会根据 osg::Node对象的center 来判断该对象应该放在哪个子节点中。这样就有一个问题,可能对象比较稀疏,其center在一个leaf node中,但是它横跨了其他的节点,碰撞检测的时候,即使找到center所在的leaf node,也可能浪费了多次的八叉树遍历计算量。

   OSG 内部只提供了Kd Tree。但是这个Kd Tree有些特点,它二分空间的时候,尽量平均分,这样就具有了八叉树特性。它可用于加速内置的碰撞检测(点、线段、三角形),也可用于邻近查找。LineSegmentIntersector、PolytopeIntersector 两个内置的 intersector 会使用到KdTree,当然了,自己实现的intersector也可以利用OSG Kd Tree。
   OSG 的Kd Tree 一般以vertex所在的平面分割空间来构建的。它还支持 KdTree::addPoint(), addLine(), addTriangle(), addQuad(), 那么对这些元素应该如何计算呢?其实很简单,把这些类型都当作一个基础的类型,叫做primitive。取它们的center来代替它们。 这里存在一个问题,假设两个triangle 的center 最为接近,但是它们大小不同,处于不同平面,两者交叉,Kd Tree应该如何处理呢?
   int BuildKdTree::divide(KdTree::BuildOptions& options, osg::BoundingBox& bb, int nodeIndex, unsigned int level) 是构建Tree的核心,这是一个递归函数。我们需要对一个primitive数组进行空间划分,首先把这个数组排序。找到当前Node boundingbox 的中心 mid,以距离mid最近的一个primitive所在的平面来分割空间,把长方体分成两部分,每一个node保存两个指针first、second,当node为branch类型时,分别指向二分的左右子空间,当node为leaf类型时,指向的是primitive。所以,这种方式在空间上比较平均。默认的二分层次是32层,能处理21亿个元素,这对于大多数应用来说是够了。我们现在在做的点云CAD,单片点云数量也只有500w,全部点云才15亿顶点,肯定是够用的,一般的应用需要处理的元素肯定比这个小三四个数量级。
   因为OSG Kd Tree并没有保存 parent node指针,所以,每次查询的时候,只能从根节点开始。
 

  1. https://en.wikipedia.org/wiki/K-d_tree
  2. https://en.wikipedia.org/wiki/Octree
  3. http://bbs.osgchina.org/forum.php?mod=viewthread&tid=3156&_dsign=35333951
  4. http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.727.5415&rep=rep1&type=pdf
  5. https://en.wikipedia.org/wiki/Bounding_volume_hierarchy BVH
  6. https://pushmind.org/2014/09/15/octree-note/ 不能工作,表明OSG的内部设计已经改变了
  7. http://www.openscenegraph.org/index.php/documentation/user-guides/107-kdtrees
  8. http://public.vrac.iastate.edu/vancegroup/docs/OpenSceneGraphReferenceDocs-3.0/a00423.html
如果有任何意见,欢迎留言讨论。


[ 主页 ]
COMMENTS
POST A COMMENT

(optional)



(optional)