2016年10月19日 星期三

ReCore開發系列:Mecanim動作系統

作者:William Armstrong 原文
潤稿:Kelvin Lo


ReCore是一款支援Xbox One及Windows平台的遊戲,Unity有幸參與和Armature和Microsoft Game Studios一起合作開發。本文為大家分享如何在ReCore中重複利用Mecanim的技巧。

一條狗、一隻猿猴、一隻蜘蛛和一個人一起走進了遊戲場景......然後我們第一個問題就來了,如何讓他們都能走?在ReCore專案中我們遇到了許多有趣的動作製作挑戰,而這些挑戰也間接推動Unity Mecanim系統的狀態機(State machine)更上一層樓
。我們與Armature和Microsoft Game Studios團隊緊密合作,盡可能地利用內建工具來解決這個領域的問題。 

Joule – Layered for State
Joule - 狀態層級化

Joule是一個具有豐富動作的角色。她能跑、跳躍及用火箭靴進行衝刺,除此之外她還能夠瞄準、背後射擊或拿著槍做上述所有事情。所以她會需要一個非常複雜的狀態圖
火箭靴超好玩!

對於如此多樣的動作組合而言,如果要想將這麼多的狀態和轉換,複製並應用到每一個Joule能使用的武器上是不現實的。還好Mecanim系統的同步層(Sync'd Layers)提供了很好的解決方案。一旦勾選同步功能Mecanim就會複製層中的所有狀態機分佈到一個新的層,你就可以在
不用維護多個狀態下,根據需要更換動畫片段。當然,新層裡面無用的狀態和轉換會產生一點額外的消耗。但這樣做帶來的好處還是很高的,複製發生bug機率更低,迭代速度更快,整體來說這項功能物超所值。

在同步層的待機動作 - 底層, 掏槍動作層和瞄準動作層


Corebots -動物的動作 



Joule與她一些被稱為Corebot的朋友結伴開始冒險。它們會跟著她走過這片土地並參與戰鬥並有許多特殊攻擊和環境互動能力。它們用一套複雜的AI狀態機和相應的Mecanim狀態機控制著同步。理想狀態下,這種複雜的結構只需要建立一次,然後作為基礎給所有其他的AI使用。 

誇張的Corebot狀態機


遊戲中看到的三個Corebot看起來像人卻又不是人類。這時我們可能會想到用Mecanim Humanoid Rig中內建的重定向技術(Retargeting)來實現動畫,但這並不是一個好選擇。儘管Humanoid rig非常強大,但它有著一些很苛刻的設定條件。狗、猿猴、蜘蛛恰恰無法滿足這些條件。即便是Joule也沒使用Humanoid rig,因為她在脊椎上額外的骨骼造成她並不適用這套系統。

還好的是,這些AI變數全都有類似特徵,它們都要移動、戰鬥、跟隨,然後進入待機狀態。儘管這些動畫看起來截然不同,不過它們基礎上建立的邏輯是一樣的。這樣一來AI就非常適合使用執行覆蓋控制器(Runtime Override Controller)。這個控制器允許你從一個動作片段轉道到另一個特定的Animator進行重新定向。在ReCore裡,開發團隊設定好了狗的動畫控制器,然後讓猿猴和蜘蛛在運作時覆蓋控制行為就能達成目的。

相同的Animator Controller,同時的待機狀態。

透過這一個解決方案Armature得以發表他們的遊戲,也讓他們遇到了一些問題。如果在三個Corebot類型中僅有一個類型需要某個狀態,因為只有一個控制器,所以這個狀態必須放在共用的Animator Controller中。這就會導致一些額外的效能消耗,因此這種技術最好在擁有大量共用狀態和轉換時使用。不過對於Corebot來說,這點開銷是可以接受的。

Armature意識到他們必須謹慎處理動畫的轉換。因為當在Animator中覆蓋動作時無法更改轉換(Transition)的設定。ReCore中絕大多數轉換都是固定的時間,好控制讓Animator能有比較好的結果。也就是說對覆蓋動作的時間進行大量改變會導致轉換發生在錯誤的時間上。因此當使用覆蓋控制器時,儘量不要或少量修改多個動作之間的時間間隔。

當狀態機中開始涉及到大數量的動作時,覆蓋控制器很快會變得臃腫繁複,甚至變得很難去修改。ReCore專案將程式碼中的即時覆蓋狀態器,按字母進行排序處理才得以解決這個問題。隨著動作數量增長,最終還寫了一套工具來根據玩家設定的模式來對動作進行重映射(Remapping)。

效能


在Armature和Microsoft的團隊不眠不休努力下,我們抓到了幾個嚴重的效能問題。如果臨時記憶體池已滿,Unity就會退而求其次用預設的記憶體分配器造成速度會慢一些。目前我們正在尋找改善動作記憶體分配的辦法。這種情況通常發生在多個Animator同時運作時,可以在Profiler中找到“TempAlloc.Overflow”來查看消耗。我們還在尋找如何解決讓Mecanim擁有處理很多不同的低混合權重(low blend weights)動作的能力。目前情況下,所有的動作將會被完全執行,即便這些動作最終可能混合結果沒動作。因此要注意當建立Mecanim層或Blend Tree的時候請避免使用太多最後效果很小的動作。

即使有了所有的引擎核心提升支援,Armature的資料仍然需要盡可能的優化。為了避免在主執行緒UpdateTransform塊中產生上百個骨骼Transform更新,Armature還需要確保所有的模型都導入時要勾選Optimize Game Objects選項。它會合併SkinnedMesh的骨骼,也允許Mecanim直接向圖形記憶體中寫入資料,在更新動畫時就不需要再每幀都更新所有骨骼的GameObject了。Armature同樣還需要避免使用OnStateMachineEnter和OnStateMachineExit返回呼叫。Mecanim能夠檢測到這些呼叫(Callback)是否存在,如果發現的話會阻止狀態機執行緒進行存取,因為這個操作對執行緒是不安全的。儘管這操作會佔用總動畫系統開銷的5%,但這是在每幀中間一點點的佔用,對於大型遊戲而言將會是大問題。

為了加快載入速度,Armature做了一個AI池放這些物件,而不是即時實例化(Instance)物件,然後根據需要開啟或禁用物件。但這種作法會導致Animator元件重建所有內部的狀態。這種做法的目的是保持禁用Animator不要佔用記憶體,並在禁用時乾淨的重置Animator的狀態。可以透過停用Animator Controller而不是停用整個物件來避免這種現象。Armature已經不能接受任何額外記憶體佔用或處理重置Animator狀態的程式碼了。

在啟動期間,太多的時間花在分配每個狀態使用的狀態機實例化上。這個記憶體消耗對於任何狀態機行為是必要的,因為它可以有自己的變數,每個Animator都需要一份新的副本,而不僅僅是每個AnimatorController。為此我們將[SharedBetweenAnimators]屬性加到不需要自己狀態的行為上,這樣可以節省大量的開銷。這屬性會通知Mecanim系統不需要對這部分實例狀態進行追蹤,它只要幫相關類別建立一個靜態實例就好了。


結論


我們與Armature一同研究如何讓Joule和她的Corebot夥伴奔跑、戰鬥和跳躍非常有意義,這是一個很好的Mecanim案例。透過與這樣一個雄心勃勃的團隊合作,我們借機提升引擎的品質,同時Armature也能得到他們想要的效果。想了解更多Armature與我們合作開發的ReCore專案,未來我們還會發佈更多的內容。

ReCore開發系列:全域光照

作者:Raymond Graham 原文
潤稿:Kelvin Lo

*註:ReCore是一款由Armature Studio在Xbox平台上所發行的動作冒險遊戲

*註:LPPVs(Light Probe Proxy Volumes)必須要支援Shader Model 4以上的硬體以及支援32位元的浮點運算和線性過濾(Linear filtering)的3D材質,可參考之前的文章

眾所皆知,我們樂於聽到從開發者給的建議,從功能需求到各種建議,Unity社群推動Unity不斷的改進,幫助開發者更好更滿的完成工作。但真的要做好坐滿,我們認為自己也要用自家引擎開發過遊戲才行,因此當我們聽到有個機會和Armature、Xbox的Microsoft Game Studio和Windows game ReCore合作時,當然不會錯過這個機會。

接下來的幾個月裡,我們會分享一些從我們內部一些關於改進和我們支援功能的看法,以及我們如何計劃將這些經驗帶回引擎本身。今天我們主要重點介紹Unity 5.4如何幫助團隊實現ReCore的全域光照。首先來看看一張ReCore的畫面:



Unity支援多種計算場景全域光照的方法。其中一種是用Enlighten來為場景表面計算烘培包含全域光照資訊的光照貼圖。另一種方法是將全域光照資訊烘焙到光照探針(Light probes)中。這兩個系統可以整合使用實現場景中各種不同效果。光照貼圖適合用於大型靜態物件,光照探針則適合用於動態物件。對於大型的室外場景,光照貼圖會比光照探針消耗更多的記憶體。
Armature希望全域光照系統的烘培時間很短,並且烘培光照貼圖不要額外增加記憶體消耗。為了實現這個目的,我們開發了一個透過捕捉光照探針六面貼圖(Cubemap),將光的反射資料烘到光照探針的全域光照方案。那麼光照探針就可以作為場景中所有物件的全域光照的唯一來源。但是,使用Unity的光照探針系統幫很大的環境物件計算全域光照時會導致不同物件的光照結果不一致。

本文將討論在ReCore中如何使用Unity 5.4新功能"光照探頭代理器 (LPPV)"來解決這個問題的過程,並分享一些最佳實踐。想知道什麼是LPPV以及如何使用它?請閱讀我們之前的文章(中文)。

在ReCore中使用光照探針的問題



Unity光照探針系統基於物件的世界座標幫物件指派光照探針,如果一個物件距離多個光照探針都很近,那麼光照的效果將混合這些光照探針。對於小的動態物件如人物角色效果會很好。然而對於能覆蓋大區域的大物件來說,這個系統會因為光探針差值和用於渲染整個物件的錨點不同會產生很不一致的結果。理論上,你需要針對光照探針插值每個像素去修正來獲得較好的位置坡度對照來為物件著色。這問題在光照變化明顯的大物件表面上特別明顯。

ReCore中的效果如下圖1:

圖1 – 光照探針全域光照


在圖1中,紅框區域明顯接受了不一樣的全域光照,這看起來是不對的。為了獲得一致的光照,需要一種將全域光照效果平滑應用到物件表面的方法。而LPPV可以解決這個問題!


在ReCore中設定LPPV



在Unity中,LPPV可以作為元件(Component)加在任何有MeshRenderer元件的物件上,或者MeshRenderer可以被強制使用場景中特定的LPPV。

幫每個渲染器附加不同的LPPV是不切實際的,這會導致記憶體消耗過度,還會導致物件之間的光照不一致。ReCore就使用單一LPPV來包覆關卡大部分的區域,多個渲染器共用相同的LPPV。LPPV作為元件被加到Light Probe Groups及自訂腳本,腳本用於將MeshRenderer指定到包含該MeshRenderer的LPPV。我們發現在網格模式(grid pattern)安排光照探針可以獲得較好的結果,我們還寫了一些自訂腳本來自動執行這個過程。這是ReCore關卡中典型的LPPV設定的俯視圖:


Light probe group和mesh renderer的設定如下:


結果


在美術設定好LPPV並烘焙完光照之後,全域光照的結果將更加一致,如下圖2:

圖2 – 使用了LPPV的全域光照


在圖2中,你可以看到紅框區域全域光照更加一致。全域光照對大型岩石結構的作用也更加平滑。ReCore中的光照是靜態的,而且LPPV更新的消耗僅在於它們載入的時候。這將使用LPPV的CPU消耗維持在最低狀態。而且傳送到GPU的LPPV貼圖也很小,只在g-buffer傳遞時增加了3個貼圖樣本,總體上說對GPU時間的影響幾乎沒有感覺。

Unity 5.4和LPPV使ReCore的自訂全域光照解決方案在不犧牲品質下得以解決。敬請繼續關注部落格,瞭解更多使用Unity開發ReCore的其它技術。

2016年10月18日 星期二

Progressive Lightmapper - 漸進光照貼圖介紹

作者:Jesper Mortensen 原文
潤稿:Kelvin Lo


自從今年三月份我們在GDC上首次展示了這個漸進光照貼圖技術(Progressive Lightmapper)之後,我們就一直在努力把它開發出來。你可以看這段影片就能了解這技術目前的發展:

What?漸進光照貼圖是甚麼?



那到底漸進光照貼圖是什麼?它是一種可以與Unity裡的全域光照GI搭配使用的無偏移蒙特卡羅路徑追蹤器(Monte Carlo path tracer),在過去的40年來,人們預估即時光跡追蹤運算技術將很快的出現在即時應用裡,在未來5年內就可能實現。因此目前為止漸進光照貼圖,只是作為烘焙光照(Baked Lighting)的替代方案。

那麼Enlighten裡的Baked GI怎麼辦?Enlighten目前還不會有什麼大變動。它提供了即時的全域光照,而且它還無法與路徑追蹤器(path tracer)搭配使用。

Why? 為什麼選擇它?


漸進光照貼圖的目標是最大化改善場景光照烘焙的流程。目前Unity對場景做任何修改都需要重新烘焙場景後,才能看到最後的結果。而Enlighten帶來的好處是能夠讓我們即時看到光照運算後的結果。但是當你修改任何參數、貼圖或模型時都需要對場景重新烘焙,烘焙期間你是無法針對結果有任何的資訊。

例如,在專案開發的迭代時期,需要不斷的調整陰影或光線反射等級,為了看到調整的結果通常都需要等上一段時間甚至更久,大大的降低了工作效率。有了漸進光照貼圖技術你就可以立刻看到調整結果。像這樣,一開始場景裡的渲染結果會有一些噪點,但隨後畫面品質會立刻提升。


此外我們還做了優化調整,優先對場景視圖先看到的範圍進行計算和烘焙。該範圍結束後再繼續對場景其餘部分計算和烘焙。

另外,大多數情況下漸進光照貼圖會比舊的更穩定,因為它是在全光照解析度之下進行間接光源烘焙,所以渲染結果中產生的瑕疵更少。設定容易,並提供一個展開的UV或讓Unity自己建立一個並可以自行設定烘焙的解析度。此外,開發者之前對於烘焙光照的相關知識在這裡仍然是適用的。我們還能預測渲染需要的時間,下圖的倒數計時能夠精確地告訴你還需要多少時間才能完成烘焙。


最後,如果您覺得渲染結果已經夠好了,還可以直接停止烘焙,此時場景就會保持在停止烘焙時的狀態。所以你可以隨時停止烘焙,或增加一些內容後繼續烘焙。

What Does it NOT DO?它無法做什麼?



它不是Realtime GI的替代品,它只能在Unity編輯器中進行烘焙,因此不能在Run time(遊戲中)進行烘焙。這代表你還沒辦法在遊戲中用程式即時烘焙一個Baked GI,或是無法根據特定時間更改光照並即時烘焙。我們打算先在Unity編輯器中的烘焙工作流逐步完善後再考慮其他的部分。

另外,它的渲染速度不一定比Enlighten(*註一)的快。如果你為Enlighten調整好了場景,則Enlighten的渲染速度也可以非常快。因此我們不能保證漸進光照貼圖在所有場景中烘焙都是最快的。但使用漸進光照貼圖的話,從開始烘焙到看到成果所需的時間還是最快。

最後,它目前還沒有像
PowerVR Ray Tracing支援特定GPU或一般GPU那樣針對任何硬體提供特殊支援。目前而言這個烘焙過程只會在CPU上執行。需要再次強調的是,最初的版本我們會將注意力放在烘焙本身的工作流程上。


When Can I Have It?我什麼時候能用?


我們目前還處在Alpha封測階段,已經有少數的開發者幫我們一起測試這個功能。當結果滿意的時候我們就會開放beta版本給各位。

以下是幾張用漸進光照烘焙出來的成果圖:



*註一:由於Enlighten提供了realtime GI和backed GI,本文裡的Enlighten指的是Unity Lighting介面裡的Baked GI功能。

2016年10月17日 星期一

詳解Unity WebGL記憶體

作者:MARCO TRIVELLATO 原文
翻譯/潤稿:Kelvin Lo

自Unity支援WebGL以來,我們開發團隊就一直致力於優化WebGL的記憶體消耗。在Unity文件上已有對於WebGL記憶體管理的詳盡分析,在Unite Europe 2015與Unite Boston 2015兩屆大會上,也有專題針對WebGL進行深入的講解。然而開發者仍然對這方面的內容依舊討論熱烈,所以我們意識到應該分享更多關於這方面的內容。

Unity WebGL和其它發佈平台有何不同?


如果開發者從一些本來就要控制記憶體的平台像是PC或是WebPlayer轉過來的話或許已經有個基礎,應該都不會造成問題。

如果目標是遊戲機(Console)的話,記憶體管理相對比其他平台容易,因為可以準確知道記憶體是如何運用的。你可以很好的管理記憶體確保遊戲完美運作。手機平台記憶體管理就有些複雜,因為設備種類繁多,但至少可以選擇最低標準的設備,並根據市場情況略過那些標準更低的設備。

網頁就沒那麼輕鬆了,理想狀況下所有玩家(Client)都有64位元瀏覽器和大量記憶體,但事實總是殘酷的。首先你無法知道它們電腦的硬體規格。然後除了他們的作業系統和瀏覽器之外,並無法取得其它資訊。最後玩家可能像執行其它網頁一樣的方法開啟你的WebGL內容。因此這是一個非常複雜的問題。

總覽


這張圖描述的是當Unity WebGL在瀏覽器執行的記憶體配置

這張圖顯示在Unity Heap區域上,Unity WebGL的內容會需要分配額外的記憶體,這裡需要理解清楚才能進一步優化來降低玩家流失率。


從圖裡能看到有幾個記憶體分配,DOM, Unity Heap, Asset Data以及程式碼,程式碼一旦載入網頁就會永遠存在記憶體裡,其他像是Asset Bundles, WebAudio, Memory File System將會依據內容不同來分配(Asset Bundle下載大小不同,音樂的播放不同等等)

在載入的時候,asm.js解析和編譯期間也有幾個瀏覽器記憶體臨時分配,這裡有時候會導致32位元瀏覽器出現記憶體不足。


Unity Heap



一般來說,Unity Heap是包含所有Unity特定的物件(Game Objects),元件(Components),材質(Textures)和著色器(Shaders)等等。
在WebGL,Unity Heap的大小需要先算好讓瀏覽器分配記憶體空間,一旦分配好之後Buffer就不能調整大小了。

負責分配Unity Heap的程式如下:

buffer = new ArrayBuffer(TOTAL_MEMORY);

這段程式可以在產生的build.js裡找到,並會交由瀏覽器的JS VM執行。
TOTAL_MEMORY可以從Player Settings裡的WebGL Memory Size來指定,預設情況是256MB,實際上一個空專案只有16MB。

然而,真的遊戲專案可能需要更多,在大多數情況下會需要用到256MB或386MB,記住,設定越多記憶體表示越多玩家能正常執行。
原始碼/編譯碼 的記憶體

程式可以執行之前需要:


1.被下載到Client

2.複製到一個Text blob區塊
3.編譯

顧慮到上述每一個步驟都需要一塊記憶體


  • 下載暫存區是暫時的,但原始碼和編譯碼在記憶體裡會留著直到關閉頁面。
  • 下載暫存區和原始碼區分配的記憶體大小都是由Unity所產生的無壓縮js空間。估計會需要多少空間:
    1.製作一個可發佈版本
    2.將jsgz及datagz改名為*.gz,然後用解壓縮的工具解開它們。
    3.解開後的大小會是需要的瀏覽器記憶體大小
  • 編譯碼的大小取決於不同瀏覽器
一個簡單的優化方法是啟用引擎剝離功能(Strip Engine Code),那樣的話發佈的包就不會包含不需要的原生的引擎碼,(例如:不需要2d物理模組將會被剝除)。

請記住,例外支持(Exceptions support)和第三方的套件會影響你的程式大小,話雖如此,我們知道開發者想要在發佈時做空值檢查(null checks)和陣列檢查(Array bounds checks)時記憶體不要銷超出了例外支持的範圍,為此,你可以送-emit-null-checks-enable-array-bounds-check給il2cpp,像這樣:
PlayerSettings.SetPropertyString("additionalIl2CppArgs", "--emit-null-checks --enable-array-bounds-check");

最後請記住,發佈development build會產生更多程式碼的包因為它不是minified,這應該對你來說不是問題,因為最後你會發佈給玩家的是最終版本(release build),對吧? ;-)。


資源(Asset Data)


在其他平台上,應用程式可以永久的存取硬碟上的內容,但在網頁是沒有實際的檔案系統所以是不可能的,因此,一旦Unity WebGL的資料(.data檔案)被下載完成後,它就會存在記憶體裡。缺點是和其他平台相比它需要額外的記憶體(從5.3開始.data的檔案會壓縮成lz4放在記憶體)。例如這個Profiler說明,256mb的Unity heap會產生約40mb的.data檔案。


甚麼是.data檔案?他是Unity產生的文件組合:data.unity3d(全部的場景,有依賴關係的資源和Resources目錄底下的所有東西),unity_default_resources和引擎所需要的一些小檔案。

要了解資源的確切大小,可以在WebGL打包完後看一下 Temp\StagingArea\Data裡的data.unity3d(記住,當你關閉Unity時,temp資料夾會被刪除)。你也可查看傳給UnityLoader.js裡DataRequest的偏移植。

new DataRequest(0, 39065934, 0, 0).open('GET', '/data.unity3d');

(這段程式碼可能會依照不同的Unity版本而不同 - 這段從Unity 5.4節錄)

記憶體檔案系統(Memory File System)

上面提到Unity WebGL雖然沒有真正的檔案系統,但還是可以存取資料,和其他平台相比主要的差異是在所有的I/O行為都在記憶體裡面完成。重要的是這個檔案系統並不在Unity heap裡面,因此會需要額外的記憶體開銷,比如當我們寫一個陣列到檔案時:


var buffer = new byte [10*1014*1024];
File.WriteAllBytes(Application.temporaryCachePath + "/buffer.bytes", buffer);

這個檔案會寫到記憶體裡,你也可以在瀏覽器的profiler找到:



這段Unity heap的記憶體是256mb


同理,由於Unity的快取系統(caching system)依賴著檔案系統,整個快取也是放在這樣的檔案系統,這代表像PlayerPrefs和快取的Asset Bundle會同樣永遠放在記憶體裡直到關閉,而且是在Unity heap區的規畫之外。

Asset Bundles


降低WebGL記憶體消耗最好的方法之一就是採用Asset Bundle(如果你對這個不熟可以參考文件)。然而,不同的Asset Bundle使用方式可能會對記憶體(Unity heap內或外)產生重大的影響,有可能會造成無法再32位元瀏覽器無法正常執行。

現在你需要用到Asset Bundle,然後你該怎麼做?將所有的資源打包成一包Asset Bundle?

千萬不要!就算這樣能降低網頁的載入時間,你還是需要下載(可能超大)這個Asset Bundle,導致記憶體飆高。來看看下載AB之前的記憶體用量:


如你看到的,256mb被Unity heap定義了。這是下載了AB之後還沒有放入暫存。




你現在看到的是一個額外的緩衝區,大約是同等於硬碟上的65mb,由XHR分配。這只是一個臨時的緩衝區但是他可能造成記憶體幾禎的尖峰,直到GC(garbage collect)回收它。


那麼如何做可以減低這些記憶體尖峰?需要幫每個資源建立一個Asset Bundle?很有趣的想法但不太實用。

打包Asset Bundle是沒有一個規則,需要根據你專案的需求來讓包裝下載有意義。(例如:單機遊戲可選男女主角,且遊戲週期只會用到一種,那選完角色之後可以只載入該性別的包)

最後,記住完成之後要用AssetBundle.Unload來卸載。

Asset Bundle Caching



WebGL的Asset Bundle外取和在其他平台一樣用WWW.LoadFromCacheOrDownload,雖然有一個明顯的不同是這是放在記憶體的。在Unity WebGL裡AB的快取依賴IndexedDB,被放在記憶體檔案系統裡的emscripten編譯器支援。

來看看用LoadFromCacheOrDownload下載Asset Bundle之前的記憶體抓圖:


如你所見,512mb被Unity heap用掉了,4mb左右其他分配。包被載入之後的圖:



額外的記憶體需求上升至167mb左右,這是這個Asset Bundle額外需要的記憶體(原本是64mb壓縮包)。然後下圖是js vm做完GC之後的圖:



結果好多了,但仍需要85mb左右,大多數的記憶體用來存放Asset Bundle,這些記憶體就算你用unload去釋放到結束你都無法取回記憶體。還有,當玩家第二次用瀏覽器打開你的內容時,不管之前是否有分配過區塊,這些記憶體又會再次被分配。

這是來自Chrome的記憶體快照參考:



在Unity heap之外還有一個Asset bundle系統所需要
快取相關的臨時分配。壞消息是最近我們發現它比預期的大很多,我們預期會在Unity 5.5 beta 4, 5.3.6p6和5.4.1p2修復這個問題。


如果你用更舊的Unity版本不想升級或是你的WebGL內容已經接近發佈了,可以透過編輯器腳本來設定一些屬性:

PlayerSettings.SetPropertyString("emscriptenArgs", " -s MEMFS_APPEND_TO_TYPED_ARRAYS=1", BuildTargetGroup.WebGL);

長遠來看要最小化Asset Bundle快取記憶體用量最好的解決方案是用WWW構造函數而不是用LoadFromCacheOrDownload()或是沒有hash/version參數的UnityWebRequest.GetAssetBundle()
如果你使用新的UnityWebRequest API的話。


然後在XMLHttpRequest等級使用備用的快取機制,將下載的檔案直接存到indexedDB裡避開記憶體檔案系統。這是我們最近開發放在Asset Store的方案,需要的話可隨意取用修改。

Asset Bundle 壓縮


在Unity 5.3和5.4,都是支援LZMA和LZ4壓縮的,雖然使用LZMA(預設值)結果會比未壓縮的LZ4來的小,但用在WebGL上有幾個缺點:有明顯的效能問題、需要更多記憶體分配。所以我們比較推薦用LZ4不要壓縮(實際上,LZMA壓縮法在Unity5.5會被移除),為了彌補這個缺口,你可能會希望能用gzip/brotli來壓縮你的資源。

可以查看更多關於打包壓縮的相關資料

WebAudio


Unity WebGL的音效是有別於其他平台的,這和記憶體會有什麼關聯?

Unity將在JavaScript上支援建立特定的AudioBuffer物件,用來更方便播放網頁音效。
由於WebAudio緩衝區存在Unity heap外面,因此無法用Unity profiler追蹤,你需要用特定的瀏覽器工具來檢查記憶體查看有多少用在音效上。這個例子用Firefox來查詢音效記憶體用量:


考慮到這些音效緩衝存放未壓縮的資料,不太適合放超大型的音效檔案(例如:很長的背景音樂)。所以現在你可能需要寫些自己的js套件來使用<audio>標籤,這樣音效檔案壓縮用較少的記憶體。

FAQ


減少記憶體使用的最佳方法是什麼?簡單概括如下:

1.減少Unity Heap的大小

  • 盡可能保持“WebGL Memory Size”夠小
2.減少包裡程式碼量
  • 啟用Strip Engine Code
  • 關閉異常檢測(Disable Exceptions)
  • 避免使用第三方外掛程式
3.減少資料大小
  • 使用Asset Bundle
  • 壓縮材質

是否有能夠決定最小WebGL Memory Size的方法?

有,最佳方案是使用記憶體分析器(memory profiler),分析內容實際所需的記憶體大小,然後依據結果改變WebGL Memory Size。

以空專案為例,記憶體分析器告訴我們總使用量僅為16MB(這個值可能在不同Unity版本上有所不同),這代表只須要設定WebGL Memory Size大於16MB即可。當然,記憶體的總使用量將會依據內容而有所不同。

然而,如果因為某些原因無法使用分析器,可以簡單地通過不斷減少WebGL Memory Size 值,直到發現你的內容真正所需要的最小記憶體使用量為止。

另外值得注意的是任何不是16的倍數的值都將被自動四捨五入(執行期間)為下一個16的倍數,這是Emscripten編譯器所要求的。

WebGL Memory Size(mb)設定將決定產生html中TOTAL_MEMORY(bytes)的值。


在不重新打包專案的前提下要測試記憶體堆疊的值,建議使用編輯html的方式。一旦找到適合的值,只需在Unity專案設定中更改WebGL Memory Size即可。

最後,記住Unity分析器將佔用一些來自Unity heap的記憶體,所以在使用分析器時可能需要加一些WebGL記憶體大小。



執行時發生記憶體溢位,如何修復?
這要看是Unity還是瀏覽器的記憶體溢位。錯誤資訊會指出問題所在和解決辦法,“如果你是開發者,可以透過在WebGL設定中為專案分配更多(或更少)的記憶體來解決。”可以依據此來調整WebGL記憶體大小。然而還有很多可以解決記憶體溢位的方法。如果出現以下錯誤資訊:


除了訊息所提之外,你還可以嘗試減少程式和資料的大小。因為當瀏覽器載入網頁時,它會嘗試為一些內容尋找空餘的記憶體,其中最重要的是:代碼,資料,Unity heap和被編譯的asm.js。它們可能相當大,尤其是資料和Unity heap記憶體,這對32位元瀏覽器來說可能是問題。


在一些例子中,儘管有足夠的記憶體,瀏覽器仍載入失敗,因為記憶體是碎片化的。這就是為什麼有時候你的內容可能在重新啟動瀏覽器之後,可以成功載入的原因。

另一種情況是,當Unity 記憶體溢位時提示以下訊息:


這種情況下就需要優化你的Unity專案。


如何衡量記憶體消耗?

為了分析內容所使用的瀏覽器記憶體,可以使用Firefox瀏覽器的記憶體工具或Chrome的Heap snapshot。但它們無法顯示WebAudio記憶體使用情況,因此還可以透過about:memory裡的方法在Firefox裡拍張快照然後搜尋“webaudio”找到。如果需要透過JavaScript分析記憶體,請嘗試使用window.performance.memory(只支援Chrome)。

使用Unity Profiler測量Unity heap記憶體使用。需注意您可能需要增加WebGL的記憶體大小,以便能夠使用Profiler。

此外,我們一直致力於開發新的工具,以便分析發佈版本:使用時先包成WebGL版本,訪問http://files.unity3d.com/build-report/就能使用該工具。雖然這個工具在Unity5.4中可用,但請注意這還是開發中的功能,可能隨時會更改或刪除。但至少現在可以使用它達到測試的目的。

WebGL Memory Size的最小值與最大值是多少?

16MB是最小的,最大是2032MB,然而我們通常建議保持在512MB以下。

是否可能因為開發目的需要分配超過2032MB的記憶體?

這是一個技術上的限制:2048MB(或更多)將會超出TypeArray所用的32位元整數型態的最大值,而TypeArray被用於在JavaScript中實現Unity heap。

為何Unity Heap大小不可改變?

我們一直在考慮使用Emscripten編譯器標誌ALLOW_MEMORY_GROWTH,來允許調整Heap的大小,但目前是沒這麼設定,因為它會禁用一些Chrome中的優化。我們還未對這個影響做一些真正的基準測試。預計使用這個flag會導致記憶體問題更嚴重。如果您遇到Unity heap太小,以至於無法滿足所需記憶體的情況,這時就需要更多記憶體,那麼瀏覽器就必須分配一個更大的Heap,從舊的裡面中複製一切,然後再釋放舊的Heap。這麼做需要同時維持新Heap和舊Heap兩份記憶體(直到完成複製),這樣需要更多的總記憶體。因此反而會比使用預定固定記憶體的方式佔用更大。

為什麼32位元瀏覽器在64位元作業系統上會記憶體溢位?

32位元瀏覽器執行時的記憶體限制是一樣的,無論作業系統是64或32位元。



結論


最後建議使用瀏覽器專用的分析工具,來分析你的Unity WebGL內容,因為Unity profiler無法追蹤超出Unity heap之外的記憶體分配。

希望這些資訊對你有用。如有任何疑問請到論壇討論。

著作人