祖龙娱乐王远明:如何用UE4做出3A级材质和天气系统?
在近日舉行的北京國際游戲創(chuàng)新大會(BIGC 2021)上,來自祖龍娛樂的引擎專家王遠明帶來了「龍族幻想材質系統(tǒng)優(yōu)化和在天氣系統(tǒng)中的應用」主題分享,以祖龍娛樂旗下手游《龍族幻想》為例子,講述祖龍娛樂在基于UE4引擎的移動游戲制作過程中,如何對游戲的材質以及天氣系統(tǒng)針對移動端進行優(yōu)化。
以下是手游那點事整理的演講實錄:
同學們下午好。今天我給大家?guī)淼念}目是《龍族幻想》材質系統(tǒng)的優(yōu)化,以及在天氣系統(tǒng)中的應用。《龍族幻想》是祖龍娛樂在2019年推出的一款使用UE制作的次世代手機游戲。它也可以說是國內第一款使用UE在手機平臺的MMORPG的次世代游戲,同時也證明了UE可以在移動端制作品質非常優(yōu)秀的游戲。因為它之前的給人印象是優(yōu)勢主要在主機和PC平臺。《龍族幻想》充分展示了UE在移動平臺也是可以制作出性能優(yōu)良和畫面精美的游戲。
?
所以在這里也借助這個機會,感謝UE的同學給予祖龍娛樂大力的支持,謝謝。今天我的主題分兩個方面,第一方面是材質系統(tǒng)優(yōu)化,第二個方面是天氣系統(tǒng)。我今天講的并不是說如何使用它來制作逼真的天氣效果,而是采用一個什么樣的方式、利用UE引擎的哪些特性來制作天氣系統(tǒng)。
可能有些同學沒有玩過《龍族幻想》這個游戲,我準備了幾段視頻來展示游戲效果。給大家放一下。相信大家通過這三個視頻,會對《龍族幻想》的天氣效果變化會有初步的感覺。
?
好,我下面具體講一些技術方面的東西。我今天帶來的內容沒有王總剛才的nanite那么硬核,是偏應用層的。我剛剛說有兩個主要的部分。第一個部分是材質系統(tǒng)的優(yōu)化,我主要講的是優(yōu)化,并不是全部的介紹材質系統(tǒng),由于時間關系,我只講我們對于UE引擎在材質方面做出一些優(yōu)化。
一、材質系統(tǒng)
?
第一部分就講一下材質系統(tǒng)。材質系統(tǒng)我分四個方面來講,第一方面是UMaterial,就是最核心的一類。第二部分是ShaderMap、第三個部分是在渲染方面做的一些優(yōu)化,第四個是在Shadercode方面做的優(yōu)化。
在講這個優(yōu)化之前呢,我要先講一下優(yōu)化所涉及到的一些關鍵的類,可能程序員比較敏感點,我直接寫了類的名字。從基類到派生類,以及其他的一些輔助類,UMaterialInterface,是材質的最基本的類。任何的一個材質,不管是UMaterial還是UMaterial Instance,都是UMaterialInterface的一個子類,他所表示的是一個最基礎的類。
?
第二個UMaterial是對應在你們編輯器里面去創(chuàng)建一個材質的時候,文件最終就會對應的一個UMaterial類。這里面會存儲有不同的參數(shù),在打開之后會有各種節(jié)點,效果、貼圖參數(shù)之類的都會存在這個里面,它對應的就是UMaterial類。
第三個是UMaterial Instance,當UMaterial寫好之后呢,可能里面有很多參數(shù)比如一個float值,還有貼圖。但是如果不用Instance,而直接用UMaterial的話,那么參數(shù)是固定的,在運行期的話是沒法改的。當你把一些參數(shù)導出成Parameter參數(shù)的時候,你可以創(chuàng)建一個UMaterial Instance的類,再通過這個類賦值。在運行期的時候,可以通過接口來修改參數(shù)。這樣的話,其實可以做到UMaterial共享,不同的參數(shù)有不同的UMaterial Instance,設置不同的參數(shù)。底層使用同樣的一個UMaterial類,來達到共享以及效率的提升。
第四個類是UMaterial Instance Dynamic,這個類是在UMaterial Instance的基礎上。在運行期你可以有一些參數(shù)的設置,去設置不同的值。
在運行期間,如果你還有一些參數(shù)需要改,那怎么辦呢?有Dynamic這么一個派生類,在這里面可以放你自己想要的東西。我們后續(xù)所做的優(yōu)化還有包括天氣系統(tǒng)的實現(xiàn),其實最主要就是在這個Dynamic上去存儲每一個基于模型的質量等級(QualityLevel)。
這前面的四個是有派生關系的,后面四個其實跟前面四個沒有多大關系,但是他是渲染最核心的四個類。第一個是FShader,程序員都知道,渲染的時候有Computer Shader、有Vertex Shader還有Pixel Shader。FShader是一個Shader最基礎的父類,他的派生類里面可以是Vertex Shader、Computer Shader,也可以是Pixel Shader。
下一個FShaderMap,是當你做好了UMaterial之后,派生球拉好了之后、編譯完了之后,你可以給這個UMaterial賦予不同的模型,會有不同的頂點結構、還有各種參數(shù),肯定是需要有不同的Shader。
他可以通過這個Shader以及factory去查找最終需要渲染這個Mesh所需要的一個Shader。Shader最后對應的可能是不同的,首先你需要根據(jù)當前的模型來查找到你合適的Shader。
所以它其實是一個記錄查詢表。對于mesh,是可以頂點結構不一樣的,需要結合vertex factory來查詢。有些是不需要頂點結構,只通過一個FShaderType作為關鍵詞就可以找到的。通過FShaderType和Vertex Factory,就可以找到一個最終的FShader,所以FShaderMap是一個類似于查詢表,也是我們后續(xù)做天氣系統(tǒng)的關鍵。
FMaterial這個類包含了一個FShaderMap。他增加了些接口方便大家用。比如說我現(xiàn)在是一個高質量的畫質,或者是一個低質量的畫質,通過參數(shù)的調節(jié),來找到一個適合的FMaterial。在拿到這個FMaterial之后呢,他會最終找到這個FShaderMap。拿到FShader Map之后呢,你可以通過剛才說的FShaderType或者是頂點結構factory去找到真實的渲染需要的Shader。
最后一個ShaderCode,ShaderCode很簡單,就是不同的Shader寫完之后,編譯出來的二進制的數(shù)據(jù),一個UMaterial對應的一個文件,它可以編譯出很多很多ShaderCode。一個ShaderCode,其實對應的是一個FShader的實例。
?
這就是一個關于材質系統(tǒng),最核心的八個類。關系圖我只是列了上面這四個,他們是這樣的一個關系:UMaterialInterface是最上面的父類,派生出來UMaterial和UMaterial Instance。UMaterial和UMaterialInstance是兄弟關系。
UMaterialInstance還有一個子類,叫做UMaterialInstanceDynamic。Dynamic上面存在著我們的質量等級。然后在他的父類UMaterialInstance上面有一個parent,這個parent一般就指的是UMaterial,當然它也可以指向一個UMaterialInstance。
FShader是依靠在FMaterial里面存在的渲染線程所使用的ShaderMap來查詢到的。這個是一個大致的關系圖,后續(xù)我還會講,他們在運行期會是怎樣這樣的一個結構,是一個什么樣的情況。
?
剛才是繼承關系,這個是在引擎層面上的包含關系。
當你做好了一個材質,在編輯器里創(chuàng)建好了一個之,材質加載進來的時候,它會變成一個UMaterial類,UMaterial的實例。他在這里面最關鍵的成員是一個LoadedMaterial Resources數(shù)組。
這個數(shù)組記錄著很多ShaderMap,它是做什么用的呢?比如說我們在游戲里面,在系統(tǒng)設置里面,會選擇高畫質低畫質。每一種畫質每一種質量級別,對應著Loaded Material Resources這里面的一項。比如說游戲里有3種級別,高中低的畫質。對應的數(shù)組里面就有三項,每一項都是一個ShaderMap,每一項最終都會得到一個ShaderMap,每一個FMaterial Resources里面他表示的一個質量等級。
我們可以想象一下,如果你在系統(tǒng)設置里面選擇了一個最高畫質,那么在游戲引擎運行的過程中,他就會選擇這個數(shù)組里面的最高畫質所對應的那一個FMaterial Resources。
在運行的時候,為什么一個模型,它會呈現(xiàn)出最高畫質呢?是因為我們拿到了最高畫質所對應的FMaterial Resources,從而拿到了他的ShaderMap。我們在畫一個Mesh的時候,就可以通過FShaderType的關鍵標識,在這個map里找到那個FShader,找到那個最高畫質的Shader,然后把它繪制出來。
這樣就達到了一個,你現(xiàn)在設計是最高效果,那它是一個非常好看的、很有質感的這么一個渲染效果。如果你設置的是一個最低的,那么通過這么一個流程下來,你找到的就是一個最差的FShader。那么效果可能就會很差。
UMaterial本質上是一個數(shù)組的容器,他在運行期并不會知道需要去用哪一個質量等級,也就是不知道自己用哪一個Shader Map。他只是容器,后續(xù)是通過別的類來達到這樣的選擇。
?
剛才介紹了一些這個材質系統(tǒng)的一些關鍵的類的概念,我們講一下我們在《龍族幻想》里面做了哪些優(yōu)化的措施。
首先我們加了一個QualityBias,這個Bias就是一個偏差。我們可以設置一個全局的級別,因為當你在系統(tǒng)設置頁面,你設置的可能是一個最高的精度,也可能是最差的精度,不管什么精度,可能你的機器差一點,設計個中等精度。那么我們通過材質的QualityBias,這個偏差值,來疊加在你設置的這個級別上。比如說0的話,你在系統(tǒng)設置里面選的中精度,渲染還是中精度。如果是1的話,它的這個質量級別應該會高一個等級,中就會變成高。當然這個中是不是變成高,這取決于你代碼里的具體數(shù)值,看你們怎么去改引擎。
另外在lod中,Material Slot設置好之后,在每個LOD里面,如果你們對編輯器比較熟悉,在每個LOD里面會選擇用哪一個Material Slot,這樣的話,就決定了那個LOD,他在這個等級的偏差到底是多少。
到最終會體現(xiàn)在右下角的Dynamic,他會有一個Material 質量等級。我們會通過這個Bias,它的偏差值和系統(tǒng)設置里的值,來計算出它當前的這個實例,所使用的材質的等級,從而讓這個模型本身達到區(qū)別于整體的畫質效果。
這樣我們就可以用他來做LOD的優(yōu)化。比如說這個機器可能會比較好,設置的是一個最高精度的畫質,原來的引擎由于原生的功能,是只能切換LOD,切換完了之后在材質上就可以替換。但是材質替換完之后還是用最高的精度其實是一種浪費。于是我們就加了優(yōu)化功能,不僅切LOD,還降低質量等級,這樣的話就會更節(jié)省資源。
?
我們在Shader Map方面也做了一些優(yōu)化,Shader Map剛才我講他最直觀的就是對應著系統(tǒng)設置里面的高中低那么幾檔,可能游戲做了很多檔,不光是高中低,還有最高、最低。每一個這樣的等級,都在UMaterial這個材質文件里都會對應著一個Shader Map。不管是存儲資源,還是運行期的開銷,還是內存顯存,都是要占資源的。
所以我們就做了優(yōu)化,對于某些材質,其實不需要導出所有的質量級別。也許他本身就很挫,可能是遠處的一個裝飾物,永遠沒有走近的機會,那可能無法展示出最高精度的那一面。所以我們就加了一個flag,沒有導出所有的質量等級。勾選的話才會如同原生引擎效果一樣。如果不勾選的話,它會導出默認的一個級別。默認的級別我們現(xiàn)在是用high來作為它的默認等級。
還有,我們又加了一個標志,是記錄著哪些等級被使用。如果只有第一個的話,是否導出?只有是或者否,要么就所有的都導,要么就只導一個缺省的high。
后來我們還有個需求,就是它可能需要導一個high是不夠的,可能需要導一些。為什么有這種需求,是因為后面會講到,我們使用了質量等級系統(tǒng)來做天氣。買二手手游賬號有些模型可能沒有雨和雪的這種效果,它也不需要雨和雪的這種質量等級。所以我們加了一個標志位,加了一個flag,來記錄哪些質量等級是導出了,哪些沒有導出。
然后我們在UMaterial的函數(shù)里面,我們優(yōu)化了不需要的Shader Map。這個地方其實是可以優(yōu)化的,但是因為它的文件是逐字節(jié)寫的,最好的方式是跳過那些不需要的Shader Map。但是現(xiàn)在我們并不太清楚每一個Shader Map占的容量有多少。所以對于不需要的,我們直接加載進來,然后再丟棄,保證它序列化文件的偏移值是正確的。
當你全部讀完之后,不需要的我們就把它扔掉,這樣能達到節(jié)省內存的目的。第四個優(yōu)化方式是當我們寫了很多FShader子類的時候, FShader在這個UMaterial里面肯定是用于特定的模型,當你不用于特定模型的時候,這種組合其實是不應該生成ShaderCode代碼的。
舉一個最簡單的例子,我們角色的頭發(fā)是用一個另外的渲染方式,我們?yōu)榇烁牧朔浅6嗟拇a、也加了很多的FShader類型。這些FShader的類型基本上導致了UE原生的FShader數(shù)量double了一下,是很恐怖的。如果我們不去通過這個函數(shù)指代需要的一些FShaderType和VertexFactory上面去使用它的話,FShader的量會很大。
還有就是我們需要降低Material 質量等級的數(shù)量。因為UE原來是有低、中、高、最高,后來我們發(fā)現(xiàn)FShader數(shù)量太多了扛不住,再做了一些優(yōu)化,將不需要的一些質量等級去掉,這樣也會節(jié)省大量的FShader和ShaderCode,包體也會小很多。
這個是界面。在材質上面會加了一個導出所有質量等級的選項,默認它是勾選的,只有對于一些特殊的材質才去掉。
剛才講了我們在游戲的系統(tǒng)設置里面,你會設置高中低。但是那樣一來的話,可能整體的游戲都很挫。比如說你設計一個低的,后來又去設計一個高的話,有的時候你機器很差,但是我想讓玩家自己的那個角色呈現(xiàn)出一個比較好的效果,那么我們需要針對于某些模型單獨設置它的級別。這個時候我們就改了UE引擎,我們基于PrimitiveComponent來設置一個質量級別。
?
為什么放Primitive Component呢?因為他靜態(tài)的Mesh和蒙皮動畫都是這個類的子類。所以我把接口放在這個類上面。那么第一步需要改的,就是當你調用這個接口的時候,我們發(fā)現(xiàn)當你設置的Material它不是一個Dynamic Material的時候,一般情況下美術做的時候它都會是一個Instance,美術做不出來Dynamic,Dynamic只是程序運行期生成的。一般都是Material Instance這么一個類,它在這個類上是存不了數(shù)據(jù),存不了我自己自定義的質量等級的。只有Dynamic這么一個類,它上面會存著我的材質的質量等級。
當你發(fā)現(xiàn)這個材質需要設置,他不是一個Dynamic的時候,我會把它創(chuàng)建出一個Dynamic的一個Material類。然后再進行具體調整的方法。這個方法其實就是把當前的ShaderMap,就是FMaterial交給渲染線程。讓它把這FMaterial里面對應的那個ShaderMap的Shader讓渲染線程創(chuàng)建出來。
?
每一個所使用的質量等級,它其實是保存在這個UMaterialInstanceDynamic類上面的。在渲染的時候,它是怎么使用的呢?在主線程你創(chuàng)建了Dynamic的Material Instance,獲取了對應的ShaderMap交給了渲染線程,來切換渲染線程使用的ShaderMap。
切換完之后,在FMaterial Instance Resource這個類里面,它是渲染的一個代理類,是運行在渲染線程的。運行時候它會調用這么一個方法去得到這個FShaderMap。因為最終它是要拿到Shader,渲染的時候你要拿到Shader,拿到Shader就要從ShaderMap里去拿。
那么,它最終是調了這個函數(shù),在這個函數(shù)里面拿到了正確的FMaterial。如果引擎不改原生的UE引擎,它怎么拿呢?他是通過全局的質量等級去拿。拿了之后呢,如果你改變了這個全局的,那所有的、整個游戲的畫質全部都要改。
我們是怎么拿的呢?我們是在這個UMaterial Instance Dynamic里面去拿到那個變量,最終需要的是這個子類變量而不是全局變量,他們的類型都是一樣的。我們不讀全局的變量,我們讀取在Dynamic里面的變量來拿到這個ShaderMap。這樣的話,我們拿到了正確的ShaderMap之后,后續(xù)的邏輯、后續(xù)的功能,都是和UE原生的一樣。
我講一下ShaderCode。這個ShaderCode呢,是當你把材料球連好之后、質量等級建好了之后,他會編譯出很多的變種。因為你的Shader Type不一樣,你的頂點結構不一樣,你的質量等級不一樣,他會變出很多變種。這些變種都會變成一個二進制的數(shù)據(jù),有兩種存儲方式,在UE里面需要設置。
?
如果你不選這個勾的話,這些東西它是存在這個UMaterial里頭的,編譯完以后存在那個文件里面。如果你勾選這個勾,因為你不同的UMaterial生成的ShaderCode有可能是完全一樣的,為了共享,UE就把它放到了一個超大的文件里。因為ShaderCode很多,有幾萬個,他會放在一個很大的文件里面去。這個UMaterial文件和那個UMaterial文件,變出來的可能是一個完全相同的ShaderCode。
這樣的話,它在你最終發(fā)布的包里面,就只存在一份,整個的包體就會小。所以我們《龍族幻想》所有的ShaderCode方面優(yōu)化都是需要勾選這個勾才行的。因為我們這種龐大的游戲材質很多,如果不選這個勾,每一個都儲存在Material里面的話,那整體的包體容量會很大,所以必須勾選這個。
如果對于小游戲的話,本身包體就不是很大,那么建議可以放在這里面,不勾選這個。它的好處是你創(chuàng)建那個FShader的時間會比較快,會節(jié)省一點。他的包體,當你資源造好了之后,他這個Shader就已經準備好了。
?
接下來,再講一下我們對于ShaderCode存儲方面的優(yōu)化,我記得是在UE4.17的時候,每一個Shader當你勾選的剛才那個選項之后,共享了ShaderCode之后呢,UE的方式是將每一個ShaderCode都存成一個文件,放在磁盤的某一個目錄里去。
這個我們當時覺得用的還是挺好的,后來在后續(xù)版本里它合并成一個大的文件了,這個大的文件里面包含著若干個小的ShaderCode,相當于一個虛擬文件系統(tǒng)。他的初衷可能想做一個這方面的優(yōu)化,因為它的虛擬文件包是pak,可能是想單獨為ShaderCode做一個優(yōu)化。
但是這種優(yōu)化,和我們《龍族幻想》的方式還有點沖突。所以我們在新版里面,我們重新把這個大的文件分拆成之前的版本,一個ShaderCode是一個文件,文件名是它的哈希值。因為很多的游戲公司都有自己的虛擬文件系統(tǒng),當面對UE的這么一個很大的文件的時候,其實是沒辦法使用自己的虛擬文件系統(tǒng)來做這個熱更新的。現(xiàn)在這個ShaderCode按照《龍族幻想》的級別應該是在四百兆左右的大文件,如果是用UE的方式組成一個大包,那更新量是很大的。所以我們把它分拆了。
分拆了之后若干個文件,我們需要使用自己的文件系統(tǒng)。我們改變了哪些ShaderCode文件,就單獨更新那些文件。然后在這個FShaderCodeLibrary這個類,也做了修改。這個類的話,原來它的實現(xiàn)方式是你打開一個文件,首先打開那個library,那個library里面是一個很大的文件,里面再去逐個地打開讀取ShaderCode。那么我們現(xiàn)在就是去打開單個ShaderCode文件的時候,我們就直接打開某一個文件,然后把里面的文件內容讀出來。它的好處就是我們只會下載單獨的ShaderCode的更新包。
?
這是改進之后的長相。因為文件特別多,有幾萬個文件,在開發(fā)期的時候必須要做一個文件夾的映射。因為幾萬個文件都存在一個文件夾里的時候,查看會卡很久。所以我們通過ShaderCode的文件名哈希出來,把它分攤到若干個目錄里去,這樣每個目錄的文件數(shù)量不是很多。
然后每個文件都是以它的哈希值作為文件名的,這樣的話,當你在運行的時候去加載一個ShaderCode的時候,你拿到它的哈希值,直接定位到這個文件把它讀出來就ok了。然后,這樣的文件通過我們的虛擬文件管理打成一個大包,我們更改了哪些文件,就只下載那些文件,做到包體的最小化。這就是我們對于材質系統(tǒng)所做的一些優(yōu)化。
二、天氣系統(tǒng)
?
下面我講一下天氣系統(tǒng),之前我跟UE同學溝通,他們也收到了不少同學反饋說很想知道一些游戲里面的天氣系統(tǒng)是怎么實現(xiàn)的,所以我覺得講一下這個還是很有必要的。在《龍族幻想》里我們打算做天氣系統(tǒng)的時候想到很多方案。開始想的是,我們可以通過改Shader的參數(shù),把雨啊雪啊什么的都放在一個Shader里面,通過它的參數(shù)來做一個切換。
最后發(fā)現(xiàn)這樣的話,我們的材質編器里面的圖就會非常多,線畫的非常亂,并且效率非常低。我們后來想,要不就運行期改FShader,但改FShader其實對于每個模型,比如說當前是一個晴天的材質,里面對應的是晴天的FShader,我們需要把它改成雨,那我們去找到這個雨的FShader,把它調整一下,這也是一個方案。
但是后來想一想,我們針對于每個模型去改FShader的這樣一個架構,在UE引擎里面其實是沒有的。改動會比較大,挺麻煩的。直到我們看到了質量等級。我們發(fā)現(xiàn)它在全局有一個功能是設置質量等級的,那我們在想,場景里面很多天氣變化的時候,其實有一些模型是不會受天氣變化影響。比如室內的東西,我們不能通過改全局的質量等級來切換Shade,我們就改成基于每個模型的。
所以我們最終想到,我們可以利用材質的質量等級這么一個引擎自帶的功能去實現(xiàn)天氣系統(tǒng)。但是我們還需要改進,原先的功能不能滿足我們的要求。
然后第二個方面我介紹一下,就是我們需要采用這個Material Parameter Collection。因為每一個材質,它都會有一些差值。他需要有一些全局的數(shù)據(jù),比如我們現(xiàn)在需要知道他是從晴天開始切換到雨天,從雨天切回晴天,那么這些全局的數(shù)據(jù)和參數(shù),就是取用這個參數(shù)的集合。
還有就是,我們在材質編輯器里面,我們可以通過Quality Switch這個節(jié)點來做一個不同的等級效果的LOD,我現(xiàn)在細化一下。我們決定用質量等級來做天氣系統(tǒng)之后呢,我們就開始著手加,因為它原來不夠,只有低中高最高。我們加了好幾種,有雨和雪,雨和雪最終都加了最低、中、高、最高,很多很多。后來我們覺得這樣不行,我們砍掉了很多,只留下最基礎的一些。
?
所以我們在最差的機器上沒有天氣的變化,因為我們只有l(wèi)ow,沒有l(wèi)ow rain、low snow。在中等的機器上和最高級機器上,我們會有分別對應的雪和雨。比如說我們雪的變化,從medium的質量等級,切換到medium snow,然后晴天到雪、從晴天到雨還有反向的切換。
既然做了這種質量等級的劃分,我們就不需要把所有天氣做在一個Shader里面。一個Shader里,我們只做兩種天氣的過渡,比如晴天到雪天的過渡,我覺得這個不是特別的復雜,你只需要考慮兩種天氣。但是如果你要在這個材質里面,還要做從雪到雨的切換的話,那這個Shader就會比較復雜,因為你要做到無縫的切換。
當前如果是雨的話,你想切換到雪, Shader即使換了之后,你還要保證現(xiàn)在效果跟換之前效果是對接好的,新的這種材質里肯定也要有雨和雪的成分在里面。所以我們做了限制,我們沒有雨和雪之間的切換,我們只做兩種。如果想做雨到雪的話,那通過策劃的玩法,先讓他變到晴天,然后再通過一個玩法或者事件讓他變到雪天。這樣曲線救國一下,Shader的復雜度就會大大的降低。
?
然后在一些低端機器上,我們會砍掉一些不必要的質量等級。比如說因為質量等級的加載是運行期做的,在UMaterial的函數(shù)里面去做的,發(fā)現(xiàn)是比較差的機器的話,就只保留一種Low或者是Medium。要看實際的效果。可以寫一個很大的配置表,這個機器是用低還是用高,在運行期決定到底是扔掉哪些、保留哪些。
?
再講一下參數(shù)集。這個參數(shù)集我并不想特別展開每一項參數(shù)的意義。因為每一個游戲,它天氣效果的渲染是非常復雜的一個圖表,會使用不同的參數(shù)。我現(xiàn)在只講一下它使用的思路。
天氣過渡會使用到到一些全局的東西,比如說晴天到雨天,晴天到雪天過渡的一些權重,比如說時間,光線、風速,每個參數(shù)都可以在運行期改變。當你在做天氣切換的時候,你需要在游戲邏輯上把一些參數(shù)設置好,讓它能夠有一個過渡銜接。比如說我們從晴天到雨天的過渡,當前這個模型所處的是晴天的一個Shader,這里面的雨權重就是0。
?
切換的過程是在代碼里瞬間切換的。這個時候就需要我們在切換前把這些參數(shù)的集合,比如說雨,權重設成0,才不會突變。設成0之后,我們在運行期里面去update時逐漸修改這個雨的權重值。這個雨的Shader里面,他自然就會讀到雨的數(shù)值,雨的貼圖的比重,晴天貼圖的比重都會下降,漣漪的貼圖就慢慢的呈現(xiàn)出來。其他的效果就會慢慢地淡入,直到達到百分之百的穩(wěn)定態(tài)。這個取決于你們的TA怎么去連線材質的圖。
?
這是大概的一個長相。我們的參數(shù)非常多,有標量的參數(shù),還有矢量的參數(shù),我只截了兩個。天氣材質球的連線也是相當復雜。在《龍族幻想》里面還有一些效果,是區(qū)域性的,比如說我在這個區(qū)域里面,可能是迷霧森林再走到一個別的區(qū)域里面,我們會預先保存很多的預制鍵,預制的一些數(shù)據(jù)集。
這些數(shù)據(jù)集就是對剛才那堆參數(shù)預先設置好的,比如說一個充滿黃色霧氣的地方,還有傍晚、早晨。我們存有很多參數(shù)集的文件,當你在運行期想進入某一個區(qū)域的時候,通過邏輯把這些值加載進來,然后跟當前的值通過位置做一個lerp,取代或者是設置這個Shader讀取的那套參數(shù)集。最終的效果就會發(fā)生變化,就能達到魔獸世界那種穿過一個區(qū)域到達另外一個區(qū)域的效果。
?
這是一個簡單的示例,比如說像右邊就是一個全局的參數(shù)集,它會有復雜的連線,這里只是展示一個用法。用上這些全局的參數(shù)之后,我們在引擎里面去改這些值,整體渲染效果就會有變化。在我們的Shader圖標里面,材質圖表里面充斥著大量的、對于全局的參數(shù)集合的應用,來達到這種最終的變化的效果。
?
當你在材質編輯器里面去連接節(jié)點的時候,不光是通過一些剛才的那些全局的參數(shù)來達到效果,還有一個重要的是通過Quality Switch節(jié)點。在最低精度下面,我可以走這條分支,高精度我走另外一條分支,達到一個Shader的優(yōu)化。
?
這就是剛剛說的,基于這個節(jié)點的Switch。每一個節(jié)點的Switch最終都會被編譯成對應的ShaderCode,放在它自己的ShaderMap里面。延伸過來之后可能會有非常復雜的邏輯,比如說這個Low,連過來之后可能是一個非常簡單的邏輯,這個高的雨、最高的雨連過來,可能它有更多的貼圖、更多的采樣,更多其他的效果,然后輸出到一個最終的節(jié)點。
?
限于時間關系,我給大家簡單介紹一下在渲染優(yōu)化上做的一些事。UE原生的FShader沒有卸載的機制,加載進來之后就永遠留在那個地方,我覺得其實是一種效果浪費。因為可能FPS游戲的Shader類型不是很多,當我們做《龍族幻想》的時候,對渲染效果要求很高。再加上我們加了很多質量等級去實現(xiàn)天氣效果,FShader的量就會很大,這個時候如果不卸載FShader的話,就會帶來很大的內存開銷,因此我們做了FShader的釋放。
這個原理其實也非常簡單,一個FShader創(chuàng)建出來會使用引用計數(shù)記錄在多少個地方使用,在切場景的時候我們會統(tǒng)一過一遍,我們會檢查一遍這些所有的FShader。如果它的引用計數(shù)為零了,我們就會把它釋放掉,需要放到渲染線程,或者是RHI線程里面去釋放這個Shader。在不切場景的時候,我們也會時不時做一下,但是我們在切場景的時候,在Loading條走的時候,肯定要做一遍釋放檢查。因為loading的時候卡一下的話也沒什么關系。
然后就是OpenGL的一個program binary data cache。這個是在OpenGL里面,安卓平臺下面,當你設置一個shader的時候,你需要Link。Link有點卡,當然如果Fps游戲FShader不多的時候,不會感覺特別明顯。但是《龍族幻想》有很多Shader,并且不同的模型可能Shader會不一樣,他的這種卡頓就會變成一個問題。
?
當Link完了之后,生成一個program,我們會拿到這個data,把它存下來,存在一個文件里。我做了一個虛擬的文件系統(tǒng),這個虛擬文件的key就是那段program的data。
存下來以后,當?shù)谝淮芜\行游戲時這個文件是空的,每次都會Link,然后存進去。當?shù)诙芜\行的時候,情況就會變好了。以前曾經Link過的,我直接在文件里找到,通過這個Program加載進來,也不需要去設置Shader,也不需要Link,直接就可以用了。
第三個是多個PSO cache file 。UE4自己有一個功能是對PSO的緩存,原來是叫Shader cache,后來改成pipelinecache。
他只有一個文件,我們改進了這個功能。在錄的時候,我們可以錄很多個文件。比如我們在打Boss戰(zhàn)的時候,那個Boss以前從沒出來過。他一出來,不管你是Link還是怎么樣,反正他會卡頓。因為就算是你Link了,他往顯卡送的那一刻,往鏡頭送那一刻,他也會有一定的時間開銷。然后當那個Boss從來沒出現(xiàn)過,一出來就卡一下,這個效果不太好。
還有就是我們游戲運行Loading完了之后,我們需要播一個CG,那個CG有很多也是游戲不太用到的資源,它也會卡一下。我們還是想用引擎的PSO功能,記錄的功能,然后把它預熱一下。但是一個不夠,尤其是出現(xiàn)怪物的時候。然后我們就做了一個錄不同的PSO的cache。PSO cache需要錄渲染所有的參數(shù),Shader,各種參數(shù)都錄下來。錄下來之后當你需要播這些、需要畫這些文件的時候,它會在后臺給你把這些東西跑一遍。這樣的話,當你真正渲染模型的時候就不會卡頓,我們做了多個這樣的文件。比如說,在這個Loading條結束的時候,我們需要播CG,那在Loading條結束的時候,就加載這個場景所對應的記錄好的文件。當這個CG播放的時候就會非常的平滑,沒有一絲的卡頓。對于boss也是這樣,快到播boss的時候,我們也在后臺把這個cache文件加載進來,做一下這樣的預熱,就會達到非常好的平滑效果。今天的分享大概就是這些,非常感謝。
總結
以上是生活随笔為你收集整理的祖龙娱乐王远明:如何用UE4做出3A级材质和天气系统?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 科技感的动态设计方法-2
- 下一篇: FunPlus特效专家张韶勇:如何利用跨