場景管理--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的引擎還沒什麼好的解決方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章