您好,登錄后才能下訂單哦!
這篇文章主要介紹“如何使用Unity 2018深度優(yōu)化渲染管線”,在日常操作中,相信很多人在如何使用Unity 2018深度優(yōu)化渲染管線問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何使用Unity 2018深度優(yōu)化渲染管線”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
這段時間一直沒有出文章,其實除了學校課程繁忙這些因素,更是把許多時間都放在了整理和優(yōu)化舊代碼的工作上,其中很大一部分包括對內(nèi)存管理的“苛求”,乃至使用一些新特性對渲染管線的在CPU執(zhí)行的代碼進行深度優(yōu)化。
首先來介紹一下Unity現(xiàn)有的3種編譯方法,Mono VM, IL2CPP, Burst。
Mono虛擬機是傳統(tǒng)而古老的實現(xiàn)方案,其跨平臺的優(yōu)勢使得早年的Unity一直依賴于它,然而Mono本身性能極差,代碼執(zhí)行效率只有Native C++的一半左右,這也使得早年的Unity扣上了“做PC不行”“做不了大作”“引擎性能差”的帽子,而現(xiàn)在除了Editor中為了開發(fā)效率仍在使用Mono VM,PC,主機,手機等平臺對其的需求越來越低。
IL2CPP是通過IL作為中間層,將代碼編譯成Native CPP的辦法,在2018.3正式版發(fā)布以后,IL2CPP的編譯優(yōu)化有了長足進展,一些古怪的小問題也逐漸減少,官方也大力推薦在高端平臺代替Mono使用,這將會是我們目前使用的主要的編譯方法。但是這種編譯方法也并非沒有坑, 相反它的坑要比Mono VM還要多。因為經(jīng)過IL層的翻譯,一些C#高級用法在翻譯的過程中極有可能產(chǎn)生額外的性能消耗,所以在開發(fā)時我們應(yīng)該用CPP的思維去考慮開發(fā),畢竟游戲開發(fā)與傳統(tǒng).Net開發(fā)還是有巨大差距的,當然也不是要求所有的開發(fā)者都像我一樣代碼里指針亂飛,把C#當C++寫,但是最起碼編程習慣方面的問題還是要留意一下的,該犧牲開發(fā)舒適度就要犧牲一下下(笑)。
Burst Compiler是一個專用于科學運算的編譯器,之所以說是“專用于科學運算”,是因為除此之外它基本沒有其他功能,本身不支持托管類型,所以代碼中不能有任何訪問托管類型的代碼,既然托管類型不支持,那么抽象,多態(tài)等面向?qū)ο缶透鼊e想了,可以說就是最原始的C語言編程,Burst的設(shè)計是專門用來處理科學運算的,比如處理矩陣,向量等提供了諸如SIMD之類的優(yōu)化黑科技。官方一直在神化Burst,稱其運行性能能遠超C++,但是其實在我看來,Burst只在處理個別數(shù)據(jù)處理類方法時可以派上用場,在處理其他普通代碼時并不會比C++快多少,同時只允許使用非托管類型,禁止一切OOP對于項目開發(fā)而言許多時候也不切實際(至少在ECS全面普及開來前是這樣),所以Burst在目前只會被我們當成開發(fā)時佐料。
而我們主要的優(yōu)化思路基本分為以下幾種:
盡量使用非托管的數(shù)據(jù)類型,并手動管理內(nèi)存,做到真正的Runtime 0 GC。
盡量把C#當成C++來開發(fā),原因上方已經(jīng)介紹過。
盡可能多的使用Job System計算邏輯,充分發(fā)揮多線程優(yōu)勢。
嘗試使用Burst Compiler編譯純數(shù)學運算的代碼。
在之前文章中介紹的GPU Driven Pipeline的代碼其實有很大的優(yōu)化空間,因此我們首先處理的就是光照部分,因為我們的管線使用了Tile/Cluster Based Deferred Rendering,所以需要在CPU中準備傳入Compute Buffer的數(shù)據(jù),同時燈光陰影的運算需要生成視錐體的矩陣,那么這一部分的運算就可以扔到Job System中進行運算,當然這一部分中有不少訪問托管類型的部分,而且運算也大多數(shù)是邏輯而非運算,所以這里使用傳統(tǒng)的IL2CPP編譯而非Burst,代碼結(jié)構(gòu)大概是這樣:
因為整個渲染管線都是單例的存在,因此直接使用static非常安全,另外Job System中是不允許出現(xiàn)實例化的變量,所以這里也只能使用靜態(tài),如果不想使用靜態(tài),但又一定要使用托管類型,也并非沒有辦法,我的解決方案如下:
這樣的方法可以將托管類型的地址強行賦值到指針中,但是指針并不在GC的管理范圍,所以在這樣處理時一定要注意,是否應(yīng)該開啟GCHandle保證非托管代碼執(zhí)行的途中該托管類型不會被GC干掉。當然,Unity的Component是受引擎管理的,所以不需要考慮C# GC會作妖。
可以看到,在上方的Job中,處理了包括點光源,聚光源的信息和矩陣,并將運算結(jié)果轉(zhuǎn)存到上方的NativeArray和NativeList中,最后再將值直接傳入到Compute Buffer中,這個過程的計算一直是一個邏輯重點,比如Cubemap需要計算6個面的View projection Matrix。在將這一部分代碼放入到Job中后,在本人的高端PC測試機上,在燈光數(shù)量較多的時候,代碼執(zhí)行時間直接減少了0.1ms-0.2ms,這是一個可喜的進步,尤其是項目開發(fā)后期主線程的壓力可能會成為性能短板,這樣的優(yōu)化是非常有意義的。
除此之外,還有一個運算的大頭,就是Cascade Shadowmap的矩陣布置,Cascade Shadowmap的計算方法,這里已經(jīng)不需多做解釋了,其中耗費較高的就是一個通過Frustum Corner獲取正方體位置,以及通過ViewProjection的逆矩陣反推世界坐標,進而計算像素的棋盤格,防止因為攝像頭移動導(dǎo)致的shadowmap鋸齒抖動,而逆矩陣的推導(dǎo)看似其貌不揚,實則運算量頗高,在CPU單核運算能力較弱的平攤上消耗絕對不容小覷,因此我們將其放到Job System中是一個很不錯的選擇,值得一提的是,這個過程屬于純數(shù)值類運算,因此可以使用Burst Compiler:
傳入一大堆需要用到的或者返回的屬性值,最后在Execute中計算,值得一提的是,Unity現(xiàn)有的GL.GetGPUProjectionMatrix是不支持Burst也不支持在分線程中運算的,這個真的很坑,所以我們只能自己寫一個D3D和OpenGL投影矩陣標準轉(zhuǎn)換的函數(shù),同時因為Burst不支持靜態(tài)變量,所以連isD3D這種flag也需要手動傳遞(一種隱隱的蛋疼感傳來)。
雖然因為Burst的鬼畜限制,代碼變得奇丑無比,But it just works well! 計算部分的代碼大概如下:
總共4個cascade,因此這段代碼將會在4個線程中分別執(zhí)行,這段代碼又為我們省下了主線程的0.1ms。
燈光數(shù)據(jù)的運算基本是整個渲染管線中消耗最大的一部分了,畢竟SRP已經(jīng)將更復(fù)雜的場景剔除之類邏輯封裝好了,所以實際需要我們做的大概就是這些自定義的數(shù)據(jù)類型,一些自定義的Volume組件的視錐體也是需要我們手動計算的,比如之前Froxel volumetric Rendering中曾經(jīng)提到的Fog Volume,也就是管理局部霧效的組件,我們當仁不讓的也使用了Burst Compiler加速:
其他大大小小的Job還是有不少的,Unity的Job System的原理是將任務(wù)累計到一起,通過調(diào)用JobHandle.ScheduleBatchedJobs()的方法同時開始計算所有任務(wù),這是因為開啟線程本身是一個相對昂貴的步驟,所以我們應(yīng)該盡量把更多的任務(wù)統(tǒng)一堆到Job Queue中,再讓執(zhí)行流程負責啟動任務(wù),最后通過Complete保證當前任務(wù)執(zhí)行完畢,保證線程安全。綜上所述,Job System的使用本著:“益多,益雜,不益散”的原則,多,是因為每個struct在加入隊列的時候需要memcpy,因此體積不應(yīng)該太大,同時較多的任務(wù)也更容易讓Unity的Job Threads自行挑選,平衡核心負載,益雜的意思是,許多大大小小的任務(wù),積少成多,應(yīng)該盡可能的寫成Job而不是堆砌在主線程,不益散則映射了剛才說過的ScheduleBatchedJobs的原理,應(yīng)該盡可能同時啟動所有的任務(wù),保證執(zhí)行的統(tǒng)一性。
除了多線程優(yōu)化,在內(nèi)存優(yōu)化方面我們也是完成了許多的工作,首先值得說的一點就是Unity的Memory Allocator的使用方法,學過C語言的朋友自然知道Malloc和Free的用法,這里就不用多說了,Unity提供了3種Allocator,分別是Temp, TempJob和Persistant。
Temp: 開辟的內(nèi)存會在幀結(jié)束后被釋放,適合只在當前幀使用的,同時也是開辟速度最快的,可以說其開辟速度僅次于棧內(nèi)存stackalloc,如果為了處理幀內(nèi)傳遞的數(shù)據(jù),應(yīng)該盡可能使用這種,而且不需要Free,在幀結(jié)束后會被Allocator自己回收,沒有泄露危險。
TempJob:開辟速度僅次于Temp,可以維持4幀,在4幀后會被自動回收,坦率的講我個人不是很喜歡這個設(shè)定,因為“4幀”這個限制顯得有點不倫不類,但是存在即是正義,想必也有其用途所在。
Persistant:永久的開辟內(nèi)存,開辟速度最慢,必須手動調(diào)用Free,否則會泄露,這也是最傳統(tǒng)的malloc方法,適合常駐的數(shù)據(jù)結(jié)構(gòu)使用。
配合C# 7.3給出的unmanaged的泛型限制,可以使用這套內(nèi)存管理系統(tǒng)寫出純手動管理內(nèi)存的泛型,真正做到0 GC。然而目前引擎本身還是有一些限制,譬如SRP還不支持獲取ECS的Component類型,因此代碼還很難做到純粹的“面向性能編程”,總是顯得有些不倫不類,還帶著許多面向?qū)ο蟮陌?,希望在接下里幾年里,Unity能夠逐漸完善ECS和SRP,并逐漸徹底拋棄傳統(tǒng)面向?qū)ο蟮拈_發(fā)方式。
到此,關(guān)于“如何使用Unity 2018深度優(yōu)化渲染管線”的學習就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。