动态八叉树空间索引系统开发小结

来源:互联网 发布:unity3d 离线使用 编辑:程序博客网 时间:2024/05/21 07:47

  一直忙系统开发,真是很久很久没有来了。最近在研究三维场景的管理方法,首选了八叉树方法。在网上可以看到很多文章,但是讲八叉树的书籍却不多,多半是敷衍而过,我就没有舍得花大洋去买了。自己根据网上的文章研究,总算搞明白了,于是自己动手实现之。
  上网搜索八叉树空间索引,文章一大堆,基本的思想也比较明确,就是把要管理的空间当成一个大立方体,按照限定的深度递归分割,每次平均的分成8份,形成了八叉树,每个立方体空间成为一个节点。然后把各种对象按所在位置记录到叶节点中,形成索引表。判断物体所在节点时一般仅用对象的包围盒,这样判断起来比较快,裁剪冗余也不会太高。进行场景剔除时,递归的判断节点是否可见,直到选出所有可见的叶节点,再从其索引表中取出物体,达到提高剔除效率的目的。
  大概就是这样了,但我没有急着开始写代码,希望对这个索引系统进行一些优化的设计。首先,我对八叉树索引系统功能有了一些要求,一般的游戏是在加载场景时建立一次八叉树空间索引,里面的建筑之类的固定物体都给预先分配好,甚至很多是把八叉树索引保存在文件里,创建场景时只是加载出来。这样带来一个问题,运动物体没有被八叉树索引。而要动态的对物体进行索引,就是在物体的包围盒发生变动时,要求立刻加入新进入的节点,并从之前离开的节点中删除,这需要一个高效的实现方法。我从两个方面解决了一些效率的问题,达到了对动态物体实时进行索引的方法。
  首先是避免建立八叉树节点,使用均匀网格。把整个要索引的空间分成一个三维均匀网格(一个大魔方),但网格的尺寸必须是2^N。每一个网格单元就作为一个八叉树的叶节点。系统只在剔除时进行递归操作。从最顶级节点(整个大魔方)开始判断可见性,网格尺寸是2^N,每次递归时只要尺寸除以2即可(实际的实现是使用位移操作优化了),这样可以很快算出每级节点的网格尺寸和空间尺寸。这样的实现相当于把八叉树存储结构给线性化了,好处是所有节点可以放在一个三维数组中,管理变得十分方便,而且节约了每个节点的8个子节点指针。按一个指针4字节算,8个指针需要32字节,也不是个小数目。
  然后是物体索引重复记录问题,要保证重复索引的物体只被选出一次。如果物体的体积过大,或者位于节点空间边界处,就会占用多个节点,这样每个被占用的节点都要记录一次此物体。对于索引的建立和管理,这种冗余是必要的,但是在取出剔除后的保留结果时,必须保证每个对象只取出一次,对于场景渲染,也就是要保证每个模型只渲染一次。我的方法是给每个物体增加一个bool类型的标记变量,在选取之前,将标记全部置为false,对选出的物体再修改为true,这样只要在选出之前判断是否为true就可以了。这样可以使代码变的简洁易懂,避免了使用hash_set和哈希查找。当然不是对所有物体的标记变量重置为true,而是先选出所有可见节点,只把可见节点内的物体标记为false,以此提高效率。
  最后是漫长的实现过程。一个星期用来研究和写代码,一个星期调试和改Bug。做这样一个复杂的东西要注意的细节问题实在是太多了,很多问题都是主动检查代码,自己在大脑中“运行”才发现的,在递归函数里调试经常是跟踪的晕头转向o(╯□╰)o现在,在递归剔除的基础上,实现了高效率的摄像机视口剔除,还实现了很多高效率的空间对象查找函数,SelectObjectByCamera, SelectObjectByRay, SelectObjectBySphere之类,SelectObjectByCamera除了用于视口剔除,还可以用来在游戏中选出NPC的可见物体,SelectObjectByRay可用于从屏幕拾取物体,SelectObjectBySphere可以用于三维GIS中的球形缓冲区分析,实现其它空间查找函数还可以进行更复杂的缓冲区分析。在三维GIS中,八叉树空间索引也是重要的数据。
  附图一张。场景比较简单,除了一个天空盒剩下的都是白盒子。截图的场景使用了基于动态八叉树空间索引的视口剔除。索引范围是1000x1000x1000(m^3)的立方体空间,被分成了16x16x16的均匀网格,一共4096个单元,每个单元的中心位置放了一个1x1x1(m^3)的小立方体Mesh,数量也是4096个。目前的程序在Pentium-D 2.8GHz CPU的电脑上,渲染30FPS,CPU的使用率<5%。