遊戲引擎十大核心競爭力 之 (一);情境分割(alpha純預覽版、持續更新)

來源:互聯網
上載者:User

 遊戲引擎中最關鍵的問題之一,是情境管理技術;其中,最基礎的部分,就是情境分割。情境分割要解決的幾個問題如下:

遊戲情境是一次載入還是需要即時的流載入
遊戲情境情境過大而無法一次載入的時候,怎樣一次載入一部分
一次載入一個部分,這個部分怎樣定義,根據什麼原則
對於已經分割的情境,動態物體在移動的時候,在各個分割之間移動是如何處理的(尤其類似碰撞檢測的功能)
編輯器怎樣建立一個情境,怎樣動態管理情境的大小,是否支援情境的合并和拼接
物理系統的情境需要怎麼處理,是和圖形情境一致的嗎?
本文的目的,就是討論上面的幾個問題,並給出我現在的理解。

描述世界
描述世界,就是定義遊戲情境的層次。如果遊戲情境很小,只是一次載入,只需要一個octree或者其他什麼亂七八糟的【分割樹】就可以了。我們先給這個方法定義一個名字,叫【白癡型-單情境-單分割樹】。如果情境無法一次載入,也有不同的選擇。選擇1. 一個超大的情境,還是只用一個octree表示,只是octree的深度會根據情境的大小變得不同,在超大的情境中,這個深度會很恐怖,我們定義這個方法為【蠻力型-單情境-單分割樹】。選擇2. 就是對選擇1的最佳化,對於octree的子結點,進行了壓縮或者動態分配,而不是一次分配一個極其恐怖的龐大的分割樹,我們定義這個方法為【智力型-單情境-單分割樹】。選擇3.這個也是最常用的,情境分成多個子情境,也就是多個小的level,遊戲進行中,就只是載入一部分的level,對於活動狀態的level,也是載入其中的一部分,世界描述的邏輯定義,就是這樣,至於碰撞檢測、可見度分析等等,都是和單情境沒有區別,所有的東西,都在一個樹裡面,我們把這個方法定義為【多情境-單分割樹】。選擇4.世界描述的邏輯定義和選擇3一樣,也是多個level,只是分割樹也是多個的,而且分割樹基本上都是和level一一對應的,我們定義這個方法為【多情境-多分割樹】。選擇5.有些比較蛋疼的方案,用了這樣的中間過渡的方法,主要是針對室內和室外處理方法的不同,即使同一個地區內,也有可能出現兩個不同的分割樹,我們定義這個為【妖蛾子-單情境-多種分割樹】。當然,不止這樣幾個方法,但是沒有列舉出來的方法,都是這些沒有太大的區別的,已經列舉的方法,基本上就是幾種排列組合中最典型的。接下來就具體說明各種分割的方法:

【白癡型-單情境-單分割樹】:動態物體的移動,只需要處理在【分割樹】內部結點、層次結點之間的移動。編輯器的要求也會相對簡單,建立情境的時候,就規定好整個情境的包圍體,可以選擇規定物體不會被允許移出這個包圍體。

【蠻力型-單情境-單分割樹】:動態物體的移動,同樣只需要處理【分割樹】內部的節點,層次結點之間的移動,但是當樹的深度增大的時候,這個計算量是幾何級數的增加,而且,這個幾何級數的基數還不一定是2,有時候會是8,如果你是用octree的話。編輯器在控制情境大小的時候,也不好做,究竟支不支援動態擴充呢,還是必須要固定大小呢?

【智力型-單情境-單分割樹】:相對前者,針對一個龐大的樹進行了最佳化的處理,一般就是【動態展開】的方法, 只在相機所在的地方,樹的深度,才是最大化的,看不見得大結點,連子結點都不給分配;一個結點移出視線的時候,也釋放他和他的子結點,保證樹的整體空間最小。這樣進行可見度判斷、碰撞檢測的時候,複雜度就低的多得多,複雜度就是BIG-Oh(n),具體來說,n的多項式係數,就應該是樹分支數k(八叉樹就是8,kd-tree就是k),與一個和視距相關的常數c的乘積。編輯器方面的問題,和前者同樣糾結。

【多情境-單分割樹】:這種劃分方法,好處就是,分割樹只是一個容器,多情境系統不停地往裡面拿東西和放東西,而樹本身的任務很簡單,就是可視性判斷和碰撞檢測。

【多情境-多分割樹】:不一定必須是一種分割樹,可以是多種分割樹。所以分析演算法複雜度就省了,這個情況比較複雜。這個方法美妙的地方就是,每棵樹,都可以是完整的,所以,對於開發人員來說,這個地方容易實現,不需要動態展開樹,或者進行線性化的壓縮,而且,針對不同類型的子情境(室內和室外),可以選擇最合適的樹;對於複雜情境,可以選擇更深的樹,簡單情境,就只需要稀疏的,層次很淺的樹。當然,缺點也不少,最核心的一點,就是物體移動的問題,這個問題,不僅在編輯器內很難搞定,而且,在遊戲運行時,還更糾結。比如,一個物體從一個子level移動到另一個子level,需要處理物體再分割樹之間進行穿梭;如果物體同時處於兩個情境的包圍體中,怎麼辦,根據什麼原則進行優先順序的選擇該放到哪個情境的分割樹中?能夠簡單的歸到上次所在的情境中這麼簡單嗎?有特殊情況嗎?在遊戲運行時,如果一個物體運動到另一個情境中,然後,物體之前所在的情境被運行時歸為非活躍關卡,緊接著,這個已經非活躍的關卡再次被載入的時候,會不會多出一個物體出來,就是那個不該在原地出現的物體?

【妖蛾子-單情境-多種分割樹】:要處理室內和室外,是需要兩棵樹,這個是必須的,但是為什麼不乾脆把這個弄成兩個子情境呢,做的時候也可以分開做,只需要在編輯器內進行一次匯入,匹配好相對位置,甚至,可以學習cryengine的,分成layer,即使是同時出現在編輯器內,也是有一個嚴格的劃分。所以,這樣弄,只是一個過度的方案,完全可以繼續做成【多情境-多分割樹】,如果只是單情境,他就有所有單情境劃分的缺點,和所有多分割樹的缺點,這個方案,還能更奇葩一點嗎?不過的確使很多商業引擎還真這麼弄了,只是現在的引擎,這樣弄的越來越少了。

上面的討論,我沒有提到物理方面,這個問題,我還沒有想明白,各種選擇都和開發人員的特殊需求相關;對於我來說,我使用第三方的物理引擎,havok和physX,兩個引擎都傾向於讓遊戲對象管理-繪圖物件管理-物理對象管理,都是用同樣的情境劃分,所以,game world,分成多個level,每個level對應了一個graphics scene,一個physics scene(or physics island),這樣物體的物理座標的描述和圖形座標的描述,可以很簡單的統一,跨越level的時候,需要處理的問題,也是一致的,可以盡量保證改變在同一個地方發生。

具體的分割樹演算法細節
octree
 

(圖片來自http://www.gamasutra.com/features/19970801/octree.htm)

octree劃分空間的平面,是和座標軸正交的;對於每個結點,都是一個AABB(axis-aligned-bound-box),每個結點內部,都有8個大小完全相等的子結點。

在這篇文章裡面,我們只談到了空間的分割,而沒有講可視性判斷,所以,就空間分割而言,octree不是那麼的有優勢,而且,很多時候,空間的利用率不是很理想,尤其是情境的廣度比較大(水平方向的),而深度(垂直方向的)比較淺的時候。具體談空間相關的演算法:

1. 靜態物體、動態物體的添加:

添加一個靜態物體,首先計算這個物體最終的AABB,然後看這個AABB是否在octree的根節點內部,如果在,就繼續判斷這個AABB是不是在各個子結點中,如果在,則繼續判斷。。。

如果一個開始這個AABB就不在octree中呢,對於無法伸縮的octree,這個地方,就應該是一個錯誤,所以,編輯器在建立、移動靜態物體(甚至動態物體)的時候,必須要保證這個物體不會移出根結點所在的範圍。

添加靜態物體的虛擬碼:

bool OctreeNode::AddObject( Object *object )

{

     if ( this->aabb.Contains(object->aabb) )    // 這個物體的aabb在當前結點的範圍內嗎?

     {

           for ( int i = 0; i < 8; i++ )  // 繼續判斷這個物體是不是在各個子結點內

           {

                if ( children[i]->AddObject( object ) )  // 如果某個子結點【接受】了這個物體,則任務完成

                {

                        return true;

                }

           }

           // 物體雖然在當前結點內,但是任何一個子結點都不能完全包含這個物體,那麼就把它放在當前

           this->AddObjectImpl( object );

           return true;

     }

     return false;

}

2. 動態物體的移動,多簡單。。。

void OctreeRoot::UpdateObject( Object *object )

{

       // 開啟冰箱門

       rootNode->RemoveObject( object );

       // 把大象放進去

       bool result = rootNode->AddObject( object );

       // 關上冰箱門

       object->SetUpdateResult( result );

}

關於空間劃分的,就只有這麼一點東西,每個引擎在細節的部分會有一些不同,但是核心的思想就是這麼簡單的。

BSP
BSP的細節,放到quake3章節中詳細討論,這裡就省略了。

kd-tree
kd-tree一般都是針對碰撞檢測(和光線追蹤)的最佳化進行劃分,對於圖形情境,很少有用kd-tree的。kd-tree,很多地方和bsp相近,有時候很難分辨具體是bsp還是kd-tree。實際上,也有一些工具是根據kd-tree的演算法,產生bsp的情境的。

Case Study-Engine
Unreal Engine 3
unreal engine 3,從04年公布到今天,經曆了很長的時間,演算法的細節變化了很多。我主要分析04年的版本,這個版本可以從網上獲得(悄悄的說,比如verycd),能不能編譯我不清楚,但是肯定是沒有辦法啟動並執行,因為沒有04年那個時候的美術資源,用現在的ue3的遊戲資源代替也不行。因為這樣一些理由,分析的結果無法那麼準確。

ue3的04年的版本,是使用的【蠻力型-單情境-單分割樹】,但是從設計來看,已經考慮了以後擴充成【多情境-單分割樹】的方案。04版ue3,一個關卡就是一個ULevel,從代碼來看,一個遊戲運行時只會有一個ULevel。ULevel中有一個FPrimitiveHashBase成員,這個就是Octree的馬甲,FPrimitiveHashBase的一個子類,就是FPrimitiveOctree:

 

所有關於octree的具體操作,就在類FOctreeNode中完成:

 

其他引擎基於octree的實現方法,也是和這個一樣一樣的,每個OctreeNode中,有一個列表,存放在這個Node下的一組Object,然後是8個子結點。

核心函數AddPrimitive

 

先檢查物體是不是在世界範圍內,在遊戲運行時,使用SingleNodeFilter進行添加物體

 

SingleNodeFilter中,首先看子結點能否完全包含該物體的,如果子結點無法包含物體,那麼就【嘗試】自己包含這個物體。如果子結點可以包含該物體的,則,繼續遞迴對於子結點的SingleNodeFilter。

【嘗試】自己包含這個物體,就是StoreActor,具體實現如下:

 

如果當前結點已經包含了過多的物體,同時,這個結點還沒有子結點,(同時,這個結點比定義的最小粒度的結點要大),那麼劃分這個結點的空間,然後把當前已經包含的物體,以及新的物體,都嘗試再放到當前結點一次(因為已經分配了子結點,所以,這個時候,很多物體,有可能放到子結點中了,而另一些物體,則再次放到這個結點)。

如果該結點的分配子結點的操作不符合要求,或者之前已經分配過,那麼就直接把這個新添加的物體放到object list中,同時通知這個物體,Primitive->OctreeNode.AddItem(this)【你現在已經在我裡面了】 :P 

以上就是04版ue3的情境分割相關的主要演算法。

接下去看06版ue3和09版ue3的改進。

06年,Unreal Tournament 3開發接近尾聲,Gears Of War的開發已經進行了相當長的一段時間。GOW最大的特點就是多情境系統。

在代碼中,最醒目的地方,就是多了UnWorld:

 

FSceneInterface *scene,和可視性、sorting相關的情境管理,從Level中移到了UWorld中,情境分割的情境樹,FPrimitiveHashBase *Hash也從Level中移到了UWorld中,Level裡面,現在還剩什麼呢?

 

Level中,還剩下BSP,主要用於室內Brush的碰撞檢測、靜態光照、渲染等等;以及Kismet可視化指令碼的對象。其他的部分,已經不直接和情境管理、尤其是情境分割相關了。但是其中有一個成員,比較讓我感興趣:

 

這個地方,很明顯應該就是Unreal怎麼處理跨越邊界的物體,尤其是那些動態物體的;最關鍵的,"streaming a level in/out”,啊哈!注意看前面提到過的,【多情境-多分割樹】的痛點。我相信這裡會是相當糾結的(後面具體分析)。

Level中唯一和遊戲對象直接打交道的地方,就是Level的父類,LevelBase:

 

所以,我們可以得到06版Unreal的情境分割的大概方法,UWorld管理Level(主要是runtime streaming),以及一些其他的亂七八糟的事務(事務相當多而且類別各異,這是unreal設計策略的一個通病),level簡單的作為遊戲對象的提供(從streaming的結果中得到新的遊戲對象)。

Unreal 06到09在這個方面變化不大。(ps:由於我一直沒有找到一個可以調試的版本,unreal的進一步介紹可能要停一陣。不過可以肯定的,unreal是這個系列中的最重要的參考引擎。)

 

CryEngine
首先說說CryEngine。。。啊。。。CryEngine~~~~ 在這麼多的引擎中,CryEngine的設計是我最喜歡的,恩,甚至超過了nebula device系列。21世紀遊戲引擎什麼最重要?模組設計!我理解的模組設計最重要的就是兩點:分層和分塊。CryEngine、C4、nebula device 3在分層方面都是做的相當好的。在分塊方面,ND3就開始淩亂了;C4就不清楚了,手頭連個sdk都沒有。CE在分塊方面的劃分也是非常合理。我對CE設計的全部理解,來自Crysis Mod SDK,引擎部分的代碼全部只有部分標頭檔,一般都是公用的介面,直接動態載入DLL可以使用的。就暴露的介面類而言,設計那是相當華麗!恩,廢話不說了,大家自己去看。

在應用程式層的情境管理,就只有這麼一點點的介面,是在是沒有辦法分析:

 

 

Quake 3 && Half Life
Ogre
Gamebryo
Gamebryo,在這個系列中多半是反面教材。在這一輯中,gb也沒有什麼好說的,壓根就沒有情境分割的說法。和情境相關的類都放到了core appFramework中,的entity system;而且沒有情境分割,所有的東西看起來都像是他們已經被分割好放到了情境中;其他的工程中,好像也沒有相關的實現。而且多半看起來,這個情境管理還是傾向scene graph的(在今天看來,scene graph就是主流中的非主流),的確是挺符合Gamebryo在人們心中的形象的。

Cube 2
Case Study-Editor
gtk-radiant
World Craft( my project )
Getic
Torque 3D

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/xjyhust/archive/2009/09/01/4509286.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.