zoukankan      html  css  js  c++  java
  • (转)Ogre场景管理之Octree源代码分析

    由于本人的引擎ProjectGaia服务于08年创新杯的游戏项目 – 3D太空游戏,所以理所应当加入Octree(八叉树 – 已经周宁学长发帖介绍过)场景管理器.参考了无数Octree的代码,发现还是我们可爱的Ogre写的最好,于是狂看n千行代码,把精髓提取出来给大家共享.

    鉴于我们游戏版教程又n久没有更新了,今天发一篇我对Ogre场景管理器之Octree源代码分析的笔记.

    所有代码采用伪代码.

    首先回顾一下Ogre场景管理的架构

    clip_image002

    Ogre以插件形式提供了多种场景管理器

    1. BSP管理用于支持Quake系列

    2. Terrain管理,优化大型的地形场景

    3. Octree管理,用处多多,很适合太空游戏

    4. 等等… (我只了解这几个:P)

    以上所有的管理器都继承与SceneManager类,实现其提供的接口

    并把与SceneManager挂接的SceneNode根据自身的特性进行管理.

    进入正题: OctreeSceneManager – 名字好长啊,打字都麻烦

    对八叉树的简介在附件中,如果不了解的请先看完附件,我就不废话了.

    注意: 实际代码中的成员,方法数量是我下面提供的10倍以上..我只讲关键过程用到的成员和方法.

    OctreeSceneManager : SceneManager

    主要的方法:

    FindVisiableObjs

    WalkTree

    UpdateOctreeNode

    FindNodeIn(BoundingXXX)

    主要的成员:

    Root : Octree

    WorldBox : BoundingBox

    Octree

    主要的方法:

    GetChildIndex(BoundingBox)

    GetCullBox()

    主要的成员:

    nodeList : List<OctreeNode>

    box : BoundingBox

    child : Octree[2][2][2]

    parent : Octree

    OctreeNode : SceneNode

    主要的方法:

    GetOctree

    AddToRenderQueue

    UpdateBound

    主要的成员

    LocalBoundingBox

    这三者之间的关系如下:

    OctreeSceneMgr包含一棵Octree的root, 一个Octree对象则包含了八个方向的子树(见上面的成员介绍), 一个Octree用链表管理了n多个OctreeNode. (补充: 由于OctreeNode继承与SceneNode, 看过我前面介绍的兄弟们都知道,SceneNode也是以树状结构组成的.但是此树非我们Octree中的树,因此在接下来的讲解中无视, 但要注意一点:OctreeNode的LocalBoundingBox则是由该SceneNode下面挂接的所有SceneNode的AttachedEntity (想像为一个或者多个3D模型)的boundingBox合并而成的…有点复杂了,多看几遍)

    从程序执行的步奏来讲解:

    首先,场景管理器为何物?答曰:cull,即剔除不可见的东东.

    场景管理器的调用步奏基本如下: --- 参考燕良大牛blog…

    1. SceneManager::_renderScene() 

    2. SceneManager:: _updateSceneGraph从root node开始递归的调用了所有scene node的update,主要是计算了transform;

    3.SceneManager::prepareRenderQueue 这里有一个Ogre场景管理的概念RenderQueue。粗略的看,这个类主要是为了把Objects按照材质分组,它还将管理对象的渲染优先权;

    4.OctreeSceneManager::_findVisibleObjects
    4.1 OctreeSceneManager::walkOctree
    4.2 OctreeNode::_addToRenderQueue
    可见这个操作利用SceneManager的空间管理算法来对所有的SceneNode进行了可见性判断,如果可能可见,则加入到RenderQueue中;

    步奏也看了,那么开始分析具体的函数吧.从调用的顺序开始…

    前三步是用于状态的update,和cull无关,从第4部开始才是关键…

    _findVisibleObjects{

    //没什么内容,初始化

    //调用walkOctree

    walkOctree(camera,renderQueue,root,visibilityInfo,false )

    //搞定

    }

    walkOctree(camera,renderQueue,octree,visibilityInfo,foundVisible )

    {

    //顾名思义,遍历树

    If(octree->NumOFNodes == 0)

    Return //没有OtreeNode,也就是没有模型关节,根本谈不上可见判断

    OctreeCamera::Visibility v = OctreeCamera::NONE; //不是伪代码

    If(foundVisible){

    //当为true的时候,说明这个节点的父节点已经判断为完全可见,所以不用费神了

    v = OctreeCamera::FULL;

    }

    Else if(octree == root){

    //根结点暴大,不可能全部visible

    v = OctreeCamera::PARTIAL;

    }

    Else{

    Box = octree->getCullBox() //Cull,剔除盒,大小比octree的boundingbox大125%

    v = camera->getVisibilty() //通过摄像机的Frustum平截头体(形状和透视体一样,

    //定义一个farClip面,一个nearClip,然后四点分别相连)大小判定

    }

    if ( v != OctreeCamera::NONE ) //如果不是完全不可见{

    //处理当前Octree节点下面挂接的OctreeNodeList的可见性

    //此处我会砍掉一些用于显示八叉树盒子线条的代码 – 无用

    Foreach(OctreeNode n in octree){

    //原文代码用的是迭代器

    //对于每一个OctreeNode n,判断是否可见,AABB是轴对称包围盒的意思

    Bool vis = camera -> isVisible( n -> _getWorldAABB() );

    if(vis){

    //Ogre很大很复杂,一下所有代码表示把该节点放到渲染队列

    sn->_addToRenderQueue(camera,queue ,visibleBounds );

    mVisible.push_back( sn );

    if ( mDisplayNodes )

    queue -> addRenderable( sn );

    }

    }

    }

    Foreach(Octree child in octree){

    //对于当前octree的八个子结点

    bool childfoundvisible = (v == OctreeCamera::FULL);

    if(child!=null)

    walkOctree … 递归调用

    }

    }

    OctreeSceneMgr:UpdateOctreeNode ( OctreeNode * onode ) {

    //由于挂接在八叉树上的OctreeNode:SceneNode的模型随时可能会移动,跑出当前的包

    //围盒,所以我们需要重新计算传入的节点所在的Octree位置

    If(该场景节点所挂接的Octree == null){

    //没有被挂接在任何Octree上

    //如果已经在root的范围以外,强行挂接的root上

    if ( ! onode -> _isIn( mOctree -> mBox ) )

    mOctree->_addNode( onode );

    else

    调用_addOctreeNode

    }

    If(该场景节点已经跑出所挂接的Octree){

    从原来的Octree上remove掉

    //没有被挂接在任何Octree上

    //如果已经在root的范围以外,强行挂接的root上

    if ( ! onode -> _isIn( mOctree -> mBox ) )

    mOctree->_addNode( onode );

    else

    调用_addOctreeNode

    }

    }

    //怎么添加一个新的场景节点 – 即挂接一个OctreeNode用于挂接模型

    OctreeSceneManager::_addOctreeNode( OctreeNode * n, Octree *octant, int depth ){

    首先获得要被挂接的节点n的包围盒

    if ( ( 没有超过八叉树的最大深度) &&

    被挂接的octree的包围盒大小是被挂接的节点包围盒的两倍 ){

    Child = 根据节点n的包围盒大小计算其属于octree的哪个子结点

    If(child == 0){

    建立该节点

    }

    Else{

    递归调用_addOctreeNode(n,child,++depth)

    //作用在于找到改好能包围该场景节点的子树

    }

    }

    }

    好了,基本的流程就是这样,那么我要动工开始移植代码了.把Ogre的C++移植到基于XNA的C#中去…ZZZ…

  • 相关阅读:
    vsprintf解析
    带grub的软盘镜像制作
    SunnyOS准备4
    SunnyOS准备3
    SunnyOS准备1
    汇编第七日
    汇编第六日
    解决k8s集群中mount volume失败的问题
    更新k8s集群的证书
    为k8s集群配置自定义告警
  • 原文地址:https://www.cnblogs.com/wonderKK/p/2378801.html
Copyright © 2011-2022 走看看