转:场景管理--BSP

 对于一个3D引擎来讲,最核心的部分应该算是场景组织(scene graph)了,若是这部分你都没有设计好, 那么就别期望开发一个成熟的3D引擎了。为了开发3d引擎,因此我首先就研究这方面的内容,对一个3D的场景来讲,又不少的物体,最简单的组织方法就是把他们用一个List链接起来,而后在绘制没一帧的时候依次送入渲染器(render)进行处理。
  这显然不是一个颇有效的方法,当处理一个普通的游戏场景都会显得很是慢的。实际上虽然一个场景中的物品不少,可是一般可见的指是以小部分,如何可以用很小的计算代价排除那些不可见的物品呢,这种方法叫作剔除隐藏面,减小绘制元素(Hidden Surface Complexity Reduction)。为了实现这样的方法,牵涉到空间排序(Spatial Sorting),最基本的方法要算二叉空间分割树(BSP)了,DOOM是第一个使用了二叉树的商业游戏。二叉树的构造简单地说就是对于要处理的一组对象,选择一个平面,将该组对象分红两组(若是由某个对象与该平面相交则用这个平面将这个对象分红两个对象)做为该结点的两个儿子,而后分别对两组对象用相同的方法,直到知足一个特定的条件(一般是到结点上只有一个对象)为止。
  二叉树确实是一种颇有效的场景组织结构,由于,当给出视锥(view frustum)之后,在穿过(traverse)这棵树的时候,若是发现视锥(frustum)与结点所表明的平面不相交,那么这个结点上有一棵子树必然不可见,那么这个子树就不用送入渲染器了,当遇到Leaf的时候,就能够得到所需的多边形数据,能够送入渲染器处理。
  虽然二叉树已是很是有效的方法,可是仅仅依靠二叉树仍是不能知足游戏的要求,由于如今的游戏的场景是在是很大很复杂,又不少的物品,按照二叉树的方法凡是与view frustum相交的Leaf必然要送入渲染器,由于view frustum是很大的,因此会有不少的Leaf与他相交,这就意味着渲染器仍是要处理不少的数据,若是你确实可以看到这么多的物体,那也没有办法,可是一般,好比不少室内的场景,虽然在你的frustum里面会由不少物体,可是你真正可以看到的仍是不多的一部分,好比一个封闭的房间。
  所以被称为Portal的技术被引入到游戏中来,之因此可以使用Portal技术,那是由于不少室内场景自身的限制条件所致。咱们引入region的概念,一个region就是一个相对封闭的空间,好比一个房间,region与region之间都是经过Portal(好比门或窗)相链接,所以,若是你处于一个region当中,你就只能看到这个region中的物体,若是你可以看到其余region中的物体,那么你必定是经过Portal看到的,因此处理的过程以下(考虑Portal是单向的状况,若是两个region能够经过一个门相互看到,我就是用两个单向的Portal)。

  void CRegion::Draw(LPRender lpRender_)
  {
    if (m_bVisited) return;  // 防止两个相邻的region的Portal造成死循环
    m_bVisited = TRUE;
    for (int i=0;i< m_NumOfPortals;i++)
    {
      if (m_aPortals[i].m_bOpen)
      {
        // 若是Portal在view frustum中
        if (!lpRender->Cull(m_aPortals[i]))
          m_aPortals[i].m_pRegion->Draw(lpRender_);
      }
      m_apObjects->Draw(lpRender_);
    }
    m_bVisited = FALSE;
  }

  一般咱们使用二叉树的方法来组织region,理想的状况下每一个二叉树树的Leaf就是一个region,经过二叉树的遍历能够很容易的找到照相机(camera)所在的region。不过我以为实际作场景的时候不会这么理想,因该是一个region可能被划分红了几个leaf,不过只要保证每一个leaf必定属于某个region,咱们就能够对每一个leaf增长一个region的引索(index),一样能够很方便的找到所在的region。
  Portal引擎的一个不太好的地方就是,你必须手动设定许多Portal,设计场景的会有一些限制,不然得不到很好的效果。在了解了这些技术之后我又去看了“Genesis3D”的源代码,只看了场景组织的部分,我先把个人理解说一下。

编辑器

Trace.h Trace.c vis.h vis.c world.h world.c


  Genesis3D有以下几个概念:

  Model  // Model[0]表示场景全部中不动的部分,
       // Model[i](i>0)表示场景中的活动物体(好比:门,升降台)
       // Model[0]对应一个二叉树
       // Model中还有FirstLeaf,NumOfLeafs来记录对应的Leafs
       // Model结构中有一个int Area[2]的结构,
       // 对于自己是活动门的Model,正好能够记录连通的两个Area

  Cluster // 不敢确定,推测是一种区域的概念,比Area要大
       // 并且Cluster之间没有动态的连通关系,只有临街关系。

  Area   // 至关于咱们上面所说的Region的概念,
       // Genesis3D的一个场景中最多容许256个Area,
       // 这能够从它的world结构中的AreaConnection[256][256]看出,
       // 1表示连通,0表示不通
       // Area之间的连通性经过Model[i](i>0)来控制
       // int VisFrame表示Area是否可见

  Node   // BSP上的结点
       // int VisFrame表示Node是否可见

  Leaf   // 划分世界的二叉树的叶子,
       // 每一个Leaf上都有一个Area的index
       // 每一个Leaf上都有一个Cluster的index
       // 以及一个Polygon List的指针

  Actor  // 活动的人

  所以我能够基本推断若干Leaf构成一个Area,若干Area又能够构成一个Cluster?(猜想)对于二叉树上的每一个Node都设置了一个VisFrame,用于判断是该结点表明的子树是否可见。咱们能够看到它的渲染过程:

  RenderScene(...)
  {
    Vis_VisWorld(...);   // 检测并设定可见性
    RenderWorldModel(...); // Render场景不动的部分就是Model[0]
    RenderSubModels(...);  // Render场景中活动的部分
    RenderActors(...);   // Render全部的人物
  }

  下面咱们来分析每一个过程:

  Vis_VisWorld(...)
  {
    将全部的结点设置为不可见  // 它用的方法很巧妙,这个留给读者本身去看了
      找到Camera所在的Leaf,假设为Leaf[E]
      Leaf[E].VisFrame=可见
    Area[Leaf[E].AreaIndex].VisFrame=可见
    // 经过一下这个递归过程设定全部Area的可见性
    // 经过AreaConnection[][]来判断,
    // 凡是跟Area[Leaf[E].AreaIndex]可以连通的都设定为可见
    // 具体方法比较简单,留给读者本身去看了
      Vis_Flood_r(Area[Leaf[E].AreaIndex])
      for (int i=0;i< Model[0].NumOfLeafs;i++)
      {
        // 我就是根据这里的顺序,推测Cluster是比Area更大的区域
        // 不然就应该先判断Area了
        if (Cluster[Leaf[E].ClusterIndex]与Cluster[Leaf[i].ClusterIndex ]不相通)
          continue;
        // 若是Leaf[i]所在的Area不可见,那么Leaf[i]不可见
        if (Area[Leaf[i].AreaIndex] != 可见)
          continue;
        Leaf[i].VisFrame = 可见
        // 既然Leaf[i]可见,那么i的全部父结点都应该可见,
        // 这个方法也很简单,留给读者本身去看了
        MarkVisibleParents(i);
        // 下面的过程是将Leaf所包含的全部surface设定为可见
        // 我不清楚他为何要作这一步
        ...
      }
      for (i = 1;i>NumOfModels;i++)
      {
        // 判断Model[i]是否可见的方法是,
        // 求Model[i]的Axis-Aligned Bouding Box的Center
        // 遍历Model[0]的二叉树,找到Center所在的Leaf
        // 若是该Leaf可见,那么该Model可见
        // 不然该Model不可见
        if (ModelVisible(Model[i]))
          Model[i].VisFrame = 可见
      }
  }

  RenderWorldModel(...);  // 渲染场景不动的部分就是Model[0]
  {
    遍历Model[0]对应的二叉树,
    除了通常用Frustum来剪枝之外,
    一旦发现Node.VisFrame不可见,
    那么该Node表明的整个子树都被拣选(Cull)掉。
    若是Leaf.VisFrame不可见,
    那么Leaf中的全部Polygon都被拣选(Cull)掉
  }

  RenderSubModels(...);  // 渲染场景中活动的部分
  {
    for (i = 1;i< NumOfModels;i++)
    {
      if (Model[i].VisFrame = 可见)
        绘制Model[i]
    }
  }

  RenderActors(...);  // 渲染全部的人物
  {
    for (i=0;i>NumOfActors;i++)
    {
      Actor[i]的AABB的Center所在的Leaf若是可见
      绘制Actor[i]的PolygonList,不然不绘制。
    }
  }

  由于Actor不会同时属于两个Area,因此只要找到Actor的Center所在的Leaf是否可见就能够判断Actor是否可见了。如今有些游戏使用其它的组织方法,好比Oni中就使用了八叉空间分割树(Octtree),比起二叉树、Portal技术由很大的优点,在2000年游戏开发者年会中“Hidden Surface Reduction and Collision Detection Based on Oct Trees”一文(pease.doc)就比较详细的介绍了Bungie公司的这个方法,我以为很值得一试。
  我在看了peace.doc之后决定采用oni的作法,使用他们介绍的那种八叉树+光线追踪(Raycasting)的组织结构。由于在思考二叉树+Portal的引擎时有不少问题难以解决,我以为难点在于构造含有Portal的二叉树结构,地图编辑器很难作,Genesis3D的源代码并不包含地图编辑器的部分,因此你没法得知它是如何构造它的二叉树的。给出一个静止的场景部分,划分二叉树并不难,可是若是你但愿可以构造含有Portal的region就比较麻烦了。
  1)首先,基本上不太会有一个Leaf刚好等于一个region,实际划分可能出现一个Leaf与若干region相交,我最后的结论是能够用如下规则来划分,若是一个Leaf属于某一个region,那么该Leaf就不用再划分了,若是它与n个(n>1)个region相交,那么就要将该Leaf继续划分下去。如此应该能够保证每一个Leaf必定属于某个region,那么在渲染的时候,只要找到照相机所在的Leaf就能够经过该Leaf上记录的region索引,找到所需处理的region了(Genesis3D里面的Leaf结构就能够找到他所谓的Area)。若是是这种思路,那么下面问题就必需要解决。
  2)Region如何识别或者划分,计算机自动(不太可能,这种region的概念彻底是人定的),手工识别(如何手工识别,在一个复杂的场景中选择一个个面,而后还必须构成封闭的空间才能定为region,这样恐怕也不现实)我还想过,全部的模型都有3DS MAX来作,每次美工确保作一个Region(好比一个房间),咱们本身作一个工具去识别包围该region的多面体,还必须可以手工加少数辅助对该region进行Portal的指定和识别,而后在地图编辑器中仅仅导入这样的结构,构造实际场景的时候只是设定一下region的位置,而后对于每一个Portal设定他们指向的region代号。 看似可行,可是实际上识别或者指定region和portal真的是很困难的,至少是很是复杂的事情。每当你想到一点作法,还会发现对其它的一些问题解决不方面,一直找不到关于划分region,设定Portal的文章,因此我以为作一个二叉树+Portal的引擎,在地图编辑器方面就难以完成。
  在vanly的ftp上面有Quake引擎的分析,他们的作法是将场景划分红二叉树之后,对于每一个Leaf都预先算好它的PVS。在渲染的时候,找到照相机所在的Leaf,而后查表获得预先算好的该Leaf的PVS,而后再绘制PVS中的Leaf。这里它没有介绍如何计算这个PVS,并且它如何压缩使得巨大的PVS表格只变成20K也没有说。还有它并无考虑会开关的Portal。因此我感受仍是没有什么进展。
  最后只有oni的八叉树+光线追踪还算有但愿,他不须要将处于切分平面上的物体分割,并且不要指定region和portal,对于美工建模来讲限制不多,能够自由发挥,对于程序来讲,地图编辑器由于不要什么识别功能,只要根据现有的数据划分出八叉树就能够了,负担也比较轻,只是它的消隐过程麻烦一些,也有些缺陷,可是感受代价比二叉树+Portal要低,至少咱们感受基本能够实现,而二叉树+Portal的引擎还没什么好的解决方法。
工具

相关文章
相关标签/搜索