上面是我绘制的一张图。
关于八叉树场景管理器主要需要关注两个类,其一是松散八叉树的数据结构Ogre::Octree,其二是八叉树场景管理器Ogre::OctreeSceneManager。
下面摘录图片中的文字:
松散八叉树的数据结构。
属性:
其中mBox为其包围盒,mHalfSize定义为包围盒大小的一半。
mChildren是一个大小为8的静态数组,里面保存了8个Octree指针,由八叉树场景管理器创建,由本类管理。
mNodes为挂接到当前八叉树上面的八叉树场景节点,
mNumNodes保存了挂接到当前八叉树及其子树上面的节点数量总和。mParent为当前八叉树的父树。
方法:
_isTwiceSize 返回当前树的包围盒是否两倍大于参数中的包围盒。
_getChildIndexes 八叉树在3维空间中被等分成8个子立方体,这个方法的作用就是返回参数给定的包围盒位于
这8个子树当中的哪一个当中。
_getCullBounds 这个方法决定了这是一颗松散八叉树,它返回的是当前树包围盒两倍大的包围盒。
可以参看《实时计算机图形学》。这个返回的盒子将用于裁剪。
八叉树场景管理器。
使用松散八叉树数据结构来管理场景节点,通过八叉树数据结构来实现可见性裁剪。这个场景管理器只使用了
OctreeCamera的视锥体来判断目标节点的包围盒是否在其中,如果在则加入渲染队列,否则忽略;而没有对视锥
内部的节点与节点之间做遮挡裁剪。
属性:
mVisible保存可见的场景节点,好像没什么用处目前。
mOctree 当前场景管理器使用的松散八叉树数据结构,目前默认深度为8,默认包围盒大小为正负10000。
mBoxes 保存需要渲染的WireBoundingBox.
mNumObjects 保存需要渲染的场景节点的数量总和
mBox 八叉树的大小
mLoose 应该是用于标识是否是松散的,不过现在没用到
其它还有一些用来绘制场景包围盒的颜色和索引缓冲。
方法:
init 初始化
destroySceneNode 先将其从八叉树数据结构中删除,然后再调用父类的函数做释放内存等等事情。
_updateSceneGraph 每次渲染都会调用 跟基类一样
_addOctreeNode 添加一个新的八叉树场景节点。 如果八叉树深度没有到达最大深度且当前聚焦的八叉树包围盒两倍大于目标节点的包围盒,那么则继续递归的向本树的子树进行添加,否则添加到当前聚焦的八叉树中。
_findVisibleObjects 每次渲染前都会调用,用于查找可见的场景节点(实际上是查找全部或部分位于摄像机视锥内的节点),通过调用walkOctree来实现。
walkOctree 递归遍历八叉树,查询每个场景节点是否位于摄像机视锥内。
_findNodes 这是提供给射线查询、包围盒相交查询、球形相交查询使用的函数。使用八叉树进行有层次的相交查询会明显快于逐节点的查询。
提问:为何要做场景管理器,硬件不是可以自动的裁剪么?
回答:硬件裁剪的局限性在于只能逐图元的裁剪,即判断某个顶点是否是在视锥之外。这个通常是在发生在光栅化之前。举个例子,如果在视锥外有一个由300万个面组成的模型,若是让硬件去裁剪的话,要多久?如果这个模型恰好位于一个八叉树的节点当中,我们首先判断就可以得到这个节点的包围盒不在视锥内,那么这个包围盒内部的图元是不是也肯定不在视锥内了呢?相比较这个速度吧。
至于相交检测也是同样的道理。以FPS游戏中的狙击枪为例,我们可以将弹道视为一条射线,如果这条射线与整个包围盒都不相交,那么你怎么可能打中位于包围盒中的敌人呢?由于包围盒通常是规则的图形,而敌人是由不规则的模型构成的,因此测试效率天差地别。把这条设想放到八叉树场景管理器中,通过对八叉树及其子树的包围盒的相交检查就可以一片一片的排除,这岂不是要快很多?
当然,这只是一个加速的方法而非替代硬件裁剪的过程,当使用场景管理器的时候可以显著减少传递到图形硬件的数据量。