Unity3D 的大場景內存優化
我們公司的一個 MMORPG 項目最近在內存方面碰到了紅線,昨天開會討論了一下。我提出了一個改進方案,寫篇 blog 記錄一下。
問題是這樣的。在當下的手機及平板硬件設備條件下,操作系統留給應用的可用內存并不多,大約只有 500M 左右。
和 PC 環境不同,手機上是交換分區的機制來對應一些臨時突發性內存需求的。而手機必須保證一些系統服務(某些高優先級后臺業務)的運行,所以在接電話、收取推送等等意外任務發生時,有可能多占用一些內存,導致操作系統殺掉前臺任務讓出資源。
根據實際測試,游戲想跑在當前主流高端手機上必須把自己的內存占用峰值控制在 400M 內存以下,350 M 會是一個合理的值,而這個值是遠遠低于 10 年前的 PC 游戲標準的。而我們的項目卻是一個寫實類型的 擁有大場景的 MMORPG 。
Unity3D 是在智能手機普及以前設計的,遠沒有料到會被廣泛用于手機游戲的制作。它在設計之初并沒有為低內存環境考慮。內存使用太粗獷,是在使用 Unity 開發 MMORPG 項目時最被吐槽的一點。
為手機游戲定制游戲引擎,最特別的,和 PC 游戲不同的兩點就是內存必須嚴格控制、能耗必須嚴格控制。比如在我們開發的 ejoy2d 中,會為資源數據中的項目引用定制短指針,即資源內部的相互引用使用 32bit 偏移量來代替 64bit 指針,每個指針節省出 4 字節內存;變換矩陣使用 6 個 32bit 的定點數,資源中相同矩陣共享一份數據;資源數據盡量連續存放,避免小數據塊太多造成內存碎片浪費內存等等。這些顯然是 Unity 沒花精力去做的。在 PC 上,省下幾M 幾十M 內存微不足道,但在手機上很可能就是生死之間。
ps. 能耗問題是另一個有趣點。在 PC 上你可以通過多線程并行,壓榨出高 fps ,可以不管 CPU 多燙;但是到了手機上,即使 CPU 8 核心已經是標配,還是盡量不要這么做。因為在總任務相同的情況下,單線程能做完的工作只要拆分到多線程上完成,就一定意味著總工作量增加(至少增加了線程間協調的工作)。增加了總能耗。玩家是不想玩一個插著充電線也會玩關機,手機滾燙的游戲的。這個問題有機會我另寫一篇 blog 展開,今天是想談談內存。
在我們最近的測試中,在較壞情況下,我們的游戲會占到 360M 內存左右,已經接近了內存紅線,所以要考慮進一步的優化。其中,較大的一塊是游戲場景,占了 120M 內存。
有趣的是,這 120M 內存中只有大約 50M 是用于場景上物件的貼圖、模型等等資源數據(注:我們項目沒有使用靜態批次合并,那樣更消耗內存。);也就是說,有 70M 內存用于構建場景本身的結構。所以,并非讓美術人員盡量復用同樣模型的花花草草就可以省下內存的。換句讓美術人員更容易明白的說法,我們的場景中擺的東西太多了,不是減少貼圖用量,把同一塊石頭到處擺可以解決的。這和過去制作 PC 游戲的常識不同。
所以,大部分現有的手機 MMORPG 的畫風偏卡通幻想風格不是沒有道理。因為那樣,可以用有悖現實的物件比例,場景物件個頭大,就可以用更少的數量去充斥場景。而寫實風講將就細節豐富,用諸多細節去填滿視野。在過去 PC 上,這不是問題,只要少做點獨特的模型,少用貼圖就能把內存降下來,手機上不行了。
我們并非剛剛意思到這點。一開始,開發人員就針對大場景制定了技術解決方案。
我們在場景上,認為設定了若干包圍盒,勾畫出一塊塊小區域。一旦玩家離開包圍盒太遠,程序就會把包圍盒里面的物件卸載出內存。然后在美術設計上,不讓玩家有可以從遠處觀望的角度。我們的美術風格會盡量保證場景細而精致、不追求空曠宏大的場面。
但這還做的不夠,我提出了一個改進方案。
-
保留包圍盒方案。但是包圍盒略微擴大,允許包圍盒重疊,并可以用多個包圍盒來定義一個區域。同一個場景物件只可以屬于一個區域,即使它的位置在多個區域內。(區域可以重疊)
-
所有物件都標記分類出外觀物件和細節物件。比如一個城市的城墻就是外觀物件,而城內的所有東西都是細節物件;一片樹林的大顆植物是外觀物件,地面的花花草草是細節物件。一般情況下,大部分物件都默認是細節物件,只有少數需要遠觀的才標記成外觀。這點,其實原本就做了視距分層,只不過是為了在渲染時做顯示剔除用的,并沒有用于控制內存。而這次,需要對外觀物件和細節物件單獨打包分類,便于分開卸載。
-
當玩家處于一個區域內部時,必須保證這個區域的外觀物件和細節物件都加載到內存。如果之前并不在內存,也需要開啟異步加載的流程。當一個玩家距離另一個區域比較近時,只需要確保該區域的外觀物件在內存即可,可以卸載任何不在區域的細節物件。
在以上改進方案里,把包圍盒擴大以及允許多個包圍盒一起構成區域,是為了改進數據加載的時機。單一用距離判斷會有瑕疵。比如在城墻外,即使隔的很近也不需要加載城內的細節。而我們完全可以在城門外加一個緩沖的小區域并入城市區域,只要玩家一踩到城門口,城內的細節加載流程就開始啟動了。
而允許區域重疊,并讓物件唯一歸屬于單一區域則可以解決城門外的細節物體提前加載時機。城外的這塊緩沖區上的物件就不必歸屬到城市板塊,它們早在玩家在城外活動時就加載完畢了。
來自:http://blog.codingnow.com/2017/04/unity3d_memory.html