2010年11月27日至28日,在虛幻的引擎技術(shù)開放日非現(xiàn)實(shí)開放會(huì)議上,Yumi互助娛樂公司高級(jí)技術(shù)總監(jiān)韓天陽(yáng)就ue4移動(dòng)游戲的性能優(yōu)化進(jìn)行了專題分享。從該公司生產(chǎn)的兩款游戲開始,他告訴你生產(chǎn)過程中面臨的優(yōu)化問題,以及一些優(yōu)化的想法。
以下是劉薇的講話全文:
大家好,大家好,我是Yumi互助娛樂公司的高級(jí)技術(shù)總監(jiān)韓天陽(yáng),很榮幸能有這樣的機(jī)會(huì)與大家分享和交流。我們公司成立于2016年,是一家初創(chuàng)企業(yè),主要的創(chuàng)業(yè)人員來自金山和長(zhǎng)游,擁有十多年的游戲開發(fā)經(jīng)驗(yàn),我們公司從成立之初就把精品游戲的開發(fā)作為我們的發(fā)展方向,然后我們認(rèn)為--移動(dòng)游戲的后續(xù)發(fā)展將成為第二代手機(jī)游戲的發(fā)展趨勢(shì),所以我們選擇了ue4引擎。
最近幾年,我們開發(fā)了兩款手機(jī)游戲,一款是天空之門,另一款是救贖之地。其中之一的天空之門是一個(gè)開放的MMORPG,救贖之地是一個(gè)全方位的PBR工藝俯瞰競(jìng)爭(zhēng)的游戲,游戲預(yù)計(jì)將在不久的將來推出。天空之門已經(jīng)上線了,在這兩款游戲的開發(fā)中,其實(shí)我們也使用了一些常用的移動(dòng)游戲不敢使用的一些功能和效果,同時(shí)也給我們的優(yōu)化帶來了很大的壓力,今天是與大家分享這個(gè)主題也關(guān)系到我們的ue4手機(jī)游戲的性能優(yōu)化。因?yàn)槲覀冮_發(fā)的兩個(gè)游戲也是移動(dòng)游戲的實(shí)用版本,不同版本的ue4,這些東西的總結(jié)在以后的實(shí)際應(yīng)用中可能會(huì)有一些不同,如果有一些錯(cuò)誤,也歡迎您改正。
為什么我們要特別關(guān)注移動(dòng)游戲的性能優(yōu)化,因?yàn)槭紫?,它在我們的?guó)內(nèi)硬件設(shè)備。在國(guó)內(nèi)Android市場(chǎng)上,高、低端設(shè)備長(zhǎng)期并存,Android平臺(tái)上低端設(shè)備所占比例很大。以今年的模型為例,就像你在今年20年中所做的那樣。它已經(jīng)發(fā)布了很長(zhǎng)時(shí)間,但是你仍然可以在市場(chǎng)上看到Snap巨龍435,而且手機(jī)的處理器跨度比較大,屬于16年。我們?nèi)匀豢梢钥吹?,他們的性能從GPU可能是幾十倍的不同。為了給玩家一個(gè)好的體驗(yàn),我們還必須優(yōu)化這些低端機(jī)器.因此,如果你像iOS一樣,它的設(shè)備類型相對(duì)簡(jiǎn)單,硬件性能更強(qiáng)。我們今天交流的主要內(nèi)容就是針對(duì)這個(gè)Android平臺(tái)。
我們剛才提到了我們面臨的游戲類型,比如MMORPG和開銷競(jìng)爭(zhēng)游戲,所以MMORPG本身,它需要開放的視角,需要看到很多這樣的場(chǎng)景,也可以看到大量的角色,每個(gè)角色被分成多個(gè)部分,包括武器,競(jìng)爭(zhēng)性游戲需要高幀率,非Carton幀速率也很穩(wěn)定,在后期階段,可能會(huì)有10到20個(gè)人在一個(gè)區(qū)域內(nèi)戰(zhàn)斗。其特點(diǎn)是人口多、UI多、特效多。我們?cè)趦?yōu)化中所面臨的問題可以分為以下幾個(gè)維度,一個(gè)是內(nèi)存,一個(gè)是幀速率,一個(gè)是Carton,還有一個(gè)是博弈效應(yīng)。隨后的分享也將從這些維度逐一討論。
關(guān)于優(yōu)化的思路首先就是我們要對(duì)我們面對(duì)的目標(biāo)機(jī)型進(jìn)行分檔,我們面對(duì)的目標(biāo)機(jī)型是什么樣的,每一檔我們關(guān)注點(diǎn)是什么,要達(dá)到什么樣的指標(biāo),這個(gè)就涉及到一個(gè)量化,每一檔我們的性能指標(biāo)是什么,確定了這些以后我們就開始對(duì)項(xiàng)目進(jìn)行優(yōu)化,并且進(jìn)行迭代。
舉個(gè)例子,那么比如說我們對(duì)那個(gè)《救贖之地》它的這個(gè)高中低機(jī)型的分檔是這樣的,高端機(jī)是從驍龍845開始,驍龍845及以上的,中端機(jī)從驍龍660開始及以上的,低端機(jī)是驍龍435 為一個(gè)我們的樣板機(jī)。你會(huì)發(fā)現(xiàn)就是整個(gè)的這幾個(gè)機(jī)型,它的上市年份從16年到18年的都有,16年的機(jī)器和18年的機(jī)器它顯然不是一個(gè)檔次的,上市的價(jià)格也是從800塊錢到2000多塊錢都有,這也說明了本身我們的這個(gè)國(guó)內(nèi)安卓機(jī)分配市場(chǎng)的復(fù)雜性。
優(yōu)化的思路就是首先就要進(jìn)行匹配分檔,在UE4引擎里面提供一個(gè)比較方便的就是Base Device Profiles.ini,在里面可以配置Matching Rules,那么我們?cè)贛atching Rules里面就可以把各種機(jī)型根據(jù)GPU或者根據(jù)手機(jī)的型號(hào),進(jìn)行一些分檔,把這些具體的每一檔次的信息在Default Device Profiles.ini里面進(jìn)行控制。比如說我們可以去根據(jù)不同的這個(gè)檔次去控制它的陰影,控制它的Bloom Quality,比如說Mobile Content Scale Factor之類的這些東西。
首先作為優(yōu)化,我們需要首先就是要定位這個(gè)整個(gè)的性能瓶頸,性能瓶頸到底出現(xiàn)在哪,比如說它的幀率比較低,它到底是卡在CPU上還是卡在GPU上,我們最簡(jiǎn)單的方式就是可以通過這個(gè)stat fps和stat unit 獲得這些東西的信息,那么在運(yùn)行時(shí)我們就可以看到,跑的這個(gè)幀率和各個(gè)主要的這些線程的這么一個(gè)關(guān)系.如果這個(gè)我們從那個(gè)stat unit上可以看出來,像我們一個(gè)Frame他花了多少時(shí)間,我們?cè)倏碐ame、Draw和GPU大概花了多長(zhǎng)時(shí)間,大概就可以去定位它到底是卡在什么地方,因?yàn)檫@個(gè)Frame它代表了這一幀的時(shí)間,這一幀的時(shí)間 主要就是和Game、Draw以及GPU 它里面哪一個(gè)時(shí)間花的最長(zhǎng),和它的時(shí)間是相匹配的。
我們?cè)谑褂玫倪@個(gè)優(yōu)化時(shí)候用到的比較多的工具,在CPU方面用到的是UE4的前端工具,GPU方面我們用的比較多的就是高通的Pro?filer,那么CPU的瓶頸 常見就在Game線程上,通??梢苑殖蛇@么幾個(gè)點(diǎn),第一就是Tick,第二就是藍(lán)圖中的大量的廣播事件,然還有就是藍(lán)圖中的大量的使用循環(huán)、數(shù)學(xué)運(yùn)算這些東西,還有就是引擎的Character Movement,另外還有UI Slate Prepass音效并發(fā),Skeleton?Mesh的更新,以上這幾個(gè)都是我們?cè)陧?xiàng)目中實(shí)際遇到的問題,后續(xù)會(huì)給大家逐一講解。
1.Tick。這個(gè)本身它是必不可少的一個(gè)東西,我們能做其實(shí)就是減少Tick的頻率并且合理設(shè)置各個(gè)Actor和各個(gè)組件的Tick interval,Interval就是每一個(gè)Tick的間隔。舉個(gè)例子我們默認(rèn)每一個(gè)角色其實(shí)都是按照一定的固定的Tick在跑,我們面對(duì)的角色比如說主角還有一些其他的怪,這些東西有一些可能離我們非常遠(yuǎn)的,這些離我們比較遠(yuǎn)的這一些怪物或者人物,我們其實(shí)就是可以調(diào)節(jié)它的Tick的頻率,讓它以一個(gè)比較低的Tick頻率去運(yùn)行從而減少一部分的CPU開銷。對(duì)不敏感的邏輯可以進(jìn)行這些分幀的計(jì)算,舉個(gè)例子比如說我們游戲里面UI上有一些紅點(diǎn)提示,這些東西它對(duì)于這個(gè)整個(gè)的要求 不要求那么實(shí)時(shí),但它的一個(gè)計(jì)算量很大,本來我們?nèi)绻阉旁谝粠锩嫒プ?,那可能這一幀的開銷就會(huì)比較大,如果我們把它分散到10幀里面去做,主觀感受上不會(huì)有太大的差異,但是CPU會(huì)比較省。
我們還有一些功能開銷比較大的,比如說我們的游戲里面使用到的那個(gè)一些債務(wù)的和可見性不可見的計(jì)算,這些東西我們都使用多線程去分擔(dān)Game的壓力,我們經(jīng)常會(huì)將藍(lán)圖中特別耗時(shí)的計(jì)算的移到C++函數(shù)里面,我們發(fā)現(xiàn)就是藍(lán)圖里面處理一些純邏輯方面的東西,包括一些數(shù)學(xué)運(yùn)算什么的,它本身比起C++來講的話,它還是稍微要慢一些的,把這些東西尤其是大量的數(shù)學(xué)計(jì)算從藍(lán)圖里面移到C++里面去會(huì)節(jié)省出可觀的CPU時(shí)間。
2.藍(lán)圖中大量的廣播事件響應(yīng)。那么對(duì)于廣播事件也是在大規(guī)模的應(yīng)用的時(shí)候 需要非常小心的一個(gè)事情,我們要控制廣播的影響,廣播消息的影響范圍。舉個(gè)例子,我們游戲里面每一個(gè)角色其實(shí)它都有一個(gè)名字板,上面顯示了一些血條一些其他的信息,如果在少的時(shí)候看不出來,但如果在非常多的時(shí)候,比如說我同時(shí)有幾十只怪和這個(gè)人在那里戰(zhàn)斗的時(shí)候,你就會(huì)發(fā)現(xiàn)這些廣播消息它響應(yīng)的比較頻繁。我們的血量變化這個(gè)東西可能會(huì)同步給所有的人,但每一個(gè)人其實(shí)他只關(guān)心的又是自己的,于是它的名字板藍(lán)圖的事件響應(yīng)里面判斷了一堆東西之后,最后看這不是我的,只有一個(gè)人響應(yīng)了這個(gè)消息,但是我們可能有四五十個(gè)人,但是只有一個(gè)人響應(yīng)這個(gè)消息,這個(gè)時(shí)候其實(shí)是比較虧的。
我們其實(shí)完全可以在C++里面進(jìn)行判斷,在藍(lán)圖里面每一個(gè)名字板 ,只想要自己的這個(gè)消息就可以了,不要去擴(kuò)大消息的廣播,還有就是控制廣播消息的頻度,還是拿剛才的例子說,那如果我接受到一個(gè)HP變動(dòng)這個(gè)消息,那血量變動(dòng)其實(shí)我有時(shí)候一幀里面會(huì)接收到很多次,我在和一些怪物去戰(zhàn)斗的時(shí)候,那可能同時(shí)和幾十只怪,在戰(zhàn)斗的時(shí)候它的血量變化是非常頻繁的,但對(duì)于玩家的感官上來看,他其實(shí)注意不到就是一幀中的多次變化,所以我們可以把它合并一下每一幀處理一次,感受上差不多。還有一個(gè)就是控制這些就是響應(yīng)消息的處理的復(fù)雜度,如果有太復(fù)雜的東西盡量就是別在藍(lán)圖里面處理。
3.藍(lán)圖中大量的使用循環(huán)。數(shù)學(xué)運(yùn)算、查找等這些密集計(jì)算的操作這些也都比較費(fèi),盡量的這些東西不要在Tick中進(jìn)行循環(huán)。復(fù)雜的邏輯還是要把它從我們的藍(lán)圖里面挪出去,藍(lán)圖里面盡量地只控制處理的流程。
4.Character Movement。我們?cè)诖罅康耐婕乙苿?dòng)的時(shí)候,發(fā)現(xiàn)這些玩家移動(dòng)的Movement的組件這個(gè)Tick的耗時(shí)很高,后來查了一下代碼,本質(zhì)上除了Movement它自己的Tick消耗以外,它還要處理很多其它的東西,比如說要處理一個(gè)地方是不是可行走,是不是可以站在前面,是不是有坡這些東西,這一些東西其實(shí)是非常消耗CPU時(shí)間的。除了主角以外,我們其他的角色盡量使用從服務(wù)器發(fā)來的位置進(jìn)行插值計(jì)算模擬一下玩家的位置和他的狀態(tài),這樣也能減少一部分消耗。
5.UI Slate Prepass。在我們的UI比較復(fù)雜的情況下會(huì)發(fā)現(xiàn)UI Slate Prepass這個(gè)消耗比較大,UI Slate Prepass主要是用來處理,各個(gè)UI的位置以及這些位置刷新的這些東西,當(dāng)UI的這些組件比較多的時(shí)候它是非常消耗的,比較有效的方式其實(shí)是可以通過Invalidation Box來緩存Prepass的數(shù)據(jù),這樣盡量減少位置更新的計(jì)算,這個(gè)實(shí)際測(cè)試了一下,還是能帶來比較好的效果。還可以嘗試用控制臺(tái)的這個(gè)指令Slate.Enable?Layout?Caching或者Slate.Enable?Global?Invalidation,這兩個(gè)也都是比較有效的方式,也能夠大量地減少我們的Prepass的開銷。
6.音效并發(fā)。我們的MMO游戲遇到過大量的音效并發(fā)的情況。我們同時(shí)和幾十只怪進(jìn)行戰(zhàn)斗可能放了一個(gè)群攻特效,可能每一個(gè)音效可能會(huì)被播放了幾十次甚至上百次幾百次的量級(jí),我們可以通過控制聲音的并發(fā)來減少它的同時(shí)并發(fā)的數(shù)量,這樣也能減少一些爆破音的出現(xiàn)。
7.我們也遇到過一些Skeleton Mesh更新的問題。在一些低端機(jī)上,本來它的CPU其實(shí)就不是很強(qiáng),UE4里面如果我們?cè)诎压趋揽刂圃?5根之內(nèi),并且每一個(gè)頂點(diǎn)受到了骨骼這個(gè)影響的數(shù)量少于4根情況下,引擎默認(rèn)會(huì)使用GPU的蒙皮,它不會(huì)使用CPU的 效率會(huì)比較高,如果超過這個(gè)限制這個(gè)時(shí)候,它就會(huì)使用CPU的蒙皮。對(duì)于CPU的蒙皮來講,第一本身 它對(duì)CPU的消耗比較大,第二對(duì)于低端機(jī)來講,它肯定會(huì)加加重CPU的負(fù)擔(dān),在我們之前的一個(gè)項(xiàng)目里面也遇到過情況,到最后我們是把資源進(jìn)行了修改,保證骨骼的頂點(diǎn)都在這個(gè)限制之內(nèi),這樣的話GPU的蒙皮比CPU的這個(gè)蒙皮的話本身要快非常多。
除了剛才提到的CPU的這些消耗以外其實(shí)還有很多GPU的消耗,GPU這個(gè)瓶頸主要來源于渲染壓力太大,我們也進(jìn)行了一些分類,比如說Draw?Call、材質(zhì)的復(fù)雜度,Shadow,屏幕的分辨率,還有一些后處理效果以及大面積的Alpha疊加。我們針對(duì)這些情況會(huì)進(jìn)行一些LOD的處理。
1.Draw?Call。對(duì)于Static?Mesh場(chǎng)景中大量的這些Mesh都是Static?Mesh,對(duì)于這些Static?Mesh我們可以通過Merge?Actors 引擎提供的這個(gè)功能來合并,盡量把同樣的這個(gè)Material的Actor合并成同一個(gè)Actor。這樣的話它在繪制的時(shí)候一次就繪制出來了,這個(gè)能有效地減少咱們的Draw?Call。在移動(dòng)設(shè)備上它每一個(gè)這個(gè)合并完的Mesh的頂點(diǎn)數(shù)應(yīng)該要控制在65535之內(nèi),否則可能會(huì)出現(xiàn)問題,可能它會(huì)畫不出來,還有一個(gè)就是UI的的消耗 其實(shí)對(duì)Draw?Call也是消耗的比較大的,我們也進(jìn)行了一些Sprite的進(jìn)行合,也進(jìn)行了一個(gè)用的UI Sprite進(jìn)行,同樣的這個(gè)Sprite的UI會(huì)進(jìn)行合批,也能減少很多Draw?Call。需要注意的就是通過最新的機(jī)型發(fā)現(xiàn),隨著機(jī)型機(jī)器的效果,機(jī)器的性能越來越好,UI的簡(jiǎn)單的Draw?Call性能的影響其實(shí)是越來越小的,但是相反,機(jī)器越差的話可能這個(gè)影響會(huì)越大。
2.材質(zhì)復(fù)雜度。它分為很多的方面,一個(gè)就是材質(zhì)本身它的運(yùn)算節(jié)點(diǎn),它的采樣其他的一些東西,還有就是它本身的加減乘除運(yùn)算,這些都會(huì)影響材質(zhì)的復(fù)雜度,材質(zhì)節(jié)點(diǎn)提供了一個(gè)比較好的優(yōu)化的方法叫Quality?Switch,我們就可以使用這個(gè)Quality?Switch進(jìn)行高端機(jī)和低端機(jī)的區(qū)分。通過在控制臺(tái)里面我們就去設(shè)置這個(gè)Quality Label,它就會(huì)讓整個(gè)這個(gè)材質(zhì)走不同的Quality?Switch的分支。我們可以在低端機(jī)的分支上處理這些運(yùn)算的時(shí)候,例如我們?cè)谔幚硪恍┘y理混合的時(shí)候是不是可以少混合幾張,是不是我們?cè)谶M(jìn)行其他的法線運(yùn)算的時(shí)候也可以進(jìn)行一些其他的優(yōu)化,通過這個(gè)Quality?Switch很方便地實(shí)現(xiàn)。
材質(zhì)屬性里面的這個(gè)Fully Rough其實(shí)也會(huì)起到很好的效果,同樣的渲染起來它其實(shí)會(huì) 減少很多的Shader的運(yùn)算,實(shí)際效果非常好。不過它可能會(huì)對(duì)效果有一定的影響,所以使用這個(gè)的時(shí)候我們可能要一邊測(cè)試一邊使用。另外一個(gè)可以通過設(shè)置紋理的MipBias,通過這個(gè)方式對(duì)打開了MipMap的紋理進(jìn)行紋理的偏移,偏移之后因?yàn)楸旧硭遣蓸拥牟煌瑢?,?huì)對(duì)我們的采樣的消耗有一定的影響,會(huì)有好的效果,本身它也會(huì)對(duì)填充率有一定的影響。
3.Shadow。移動(dòng)設(shè)備上其實(shí)是推薦使用Custom?UV的,對(duì)低端機(jī)上就是影子的開銷,尤其是實(shí)時(shí)陰影它的開銷其實(shí)是特別大的,我們可以通過這個(gè)Shadow?Quality控制實(shí)時(shí)陰影的質(zhì)量,低端機(jī)甚至可以直接把它關(guān)掉,只給主角開一個(gè)實(shí)時(shí)陰影,其他的玩家為了我們表現(xiàn)效果可以加一些假的那種面片的陰影上去。
4.Mobile?Content?Scale?Factor和Screen?Percentage。這兩個(gè)對(duì)GPU的性能影響非常顯著,可以根據(jù)不同的檔次進(jìn)行調(diào)節(jié),也可以通過這個(gè)控制臺(tái)實(shí)時(shí)地去切換,需要注意的是因?yàn)樗旧碓O(shè)置完了以后是會(huì)改變渲染的這個(gè)Back?Buffer的大小從而提高性能的,當(dāng)我們把它拉伸到這個(gè)手機(jī)屏幕上去就會(huì)發(fā)現(xiàn),這個(gè)可能會(huì)使畫面有一些模糊,它是影響畫質(zhì)的一個(gè)選項(xiàng)。使用的時(shí)候我們需要注意,當(dāng)模糊比較厲害的時(shí)候可能還需要開AA去解決問題。
5.后處理效果。對(duì)于低端機(jī)消耗極大的是后處理效果,尤其是大量的后處理,好幾個(gè)后處理疊加在一起的時(shí)候,那么GPU弱的機(jī)器,我們其實(shí)也可以根據(jù)項(xiàng)目的實(shí)際需求確定了是不是要把這個(gè)后處理關(guān)掉,因?yàn)檫@個(gè)東西對(duì)這個(gè)低端機(jī)的GPU壓力太大。
6.面積的Alpha的疊加。在我們比如說像 多人戰(zhàn)斗的時(shí)候,會(huì)出現(xiàn)多個(gè)粒子系統(tǒng)的疊加,這個(gè)時(shí)候就會(huì)導(dǎo)致大面積的Alpha的疊加,這個(gè)大面積的Alpha面片的疊加本身導(dǎo)致的結(jié)果就是性能會(huì)急劇下降,這個(gè)肯定是卡在GPU上的,同時(shí)它也會(huì)導(dǎo)致發(fā)熱相當(dāng)嚴(yán)重。這個(gè)時(shí)候我們可以考慮設(shè)置Particle的LOD來減緩一部分問題,那么我們可以在做粒子系統(tǒng)的時(shí)候把粒子系統(tǒng)的 LOD 也做了,這個(gè)在多人戰(zhàn)斗的時(shí)候是比較有效的。
7.HLOD、Proxy Level、LOD、Detail?Mode、Max?DrallDistance。還有剛才提到的Draw?Call和Mesh的合并,除了這個(gè)Mesh的合并,其實(shí)我們還有很多LOD可以結(jié)合著使用,引擎提供了這么一種LOD ,一個(gè)本身就是單個(gè)Actor的LOD這個(gè)單個(gè)Actor的LOD 它不會(huì)減少Draw?Call,但是它可以為Actor設(shè)置不同的貼圖,你給它設(shè)置不同的紋理也可以達(dá)到效果,達(dá)到減少渲染的壓力的作用,我們離得比較遠(yuǎn)的Actor是不是可以給它考慮設(shè)一個(gè)不受光照影響的貼圖,不受光照影響的Material它其實(shí)比默認(rèn)的Default lit 性能要高很多,離遠(yuǎn)了我們是不是也發(fā)現(xiàn)不了區(qū)別,那就可以嘗試。
HLOD和距離遠(yuǎn)的Actor它可以有效地減少Draw?Call,因?yàn)槲覀兛梢园讯鄠€(gè)小的Actor合并成一個(gè)HLOD的Actor,那么這個(gè)HLOD的Actor它用一張貼圖,用一個(gè)材質(zhì)就可以解決問題,這個(gè)時(shí)候我們會(huì)發(fā)現(xiàn)Draw?Call少了,渲染效率會(huì)有大幅的提升。Proxy Level這個(gè)代理場(chǎng)景它適合用于特別遠(yuǎn)的場(chǎng)景,比如說離我們非常遠(yuǎn) 有這么一個(gè)地塊上面都有很多的東西,它可以極大地減少Draw?Call,只是它的顯示效果比較粗糙,同樣也意味著它的效率會(huì)比較高。
有些時(shí)候可能我們覺得有了HLOD了是不是還有必要開這個(gè)Proxy Level,它的主要的用途就是說當(dāng)我們的HLOD 其實(shí)它是隨著我們要加載的場(chǎng)景再加載出來的,但是如果本身這個(gè)場(chǎng)景沒有加載出來,其實(shí)這個(gè)HLOD它也是加載不出來的。舉個(gè)例子,我們的節(jié)能表默認(rèn)的可能是加載兩三百米的場(chǎng)景,有些時(shí)候因?yàn)槲覀冇酗w行坐騎的系統(tǒng),可能飛行的比較高,一下子可能看出幾千米去,你會(huì)發(fā)現(xiàn)這個(gè)時(shí)候遠(yuǎn)處的東西其實(shí)并沒有用這個(gè) Level Streaming 加載出來,因?yàn)樘h(yuǎn)了。遠(yuǎn)處就出現(xiàn)了很多比較空曠的情況,這個(gè)時(shí)候Proxy Level就能比較好地解決問題,它會(huì)在在沒有加載出真實(shí)場(chǎng)景的時(shí)候,它先把代理場(chǎng)景加載出來,因?yàn)楸容^遠(yuǎn)的所以效果我們也是可以接受的,如果覺得不行,這個(gè)Proxy Level其實(shí)也可以調(diào)節(jié)多級(jí),根據(jù)不同的遠(yuǎn)近調(diào)整效果以達(dá)到目的。
在低端機(jī)上我們可以設(shè)置一些Detail?Mode,比如說有一些裝飾的一些草和地表的一些細(xì)節(jié)什么的,沒有什么邏輯影響的我們就可以把它減掉緩解一部分GPU的壓力,尤其是針對(duì)那種帶半透明的物體,其實(shí)也是比較有效的一種方法。Max?DrallDistance它可以控制最遠(yuǎn)可見的這個(gè)Actor的距離,當(dāng)設(shè)置完了這個(gè)以后一些比較遠(yuǎn)的這個(gè)Actor它就直接被剔除掉了,它不會(huì)顯示在我們屏幕上,也是一種比較有效的優(yōu)化效果。在我們做這個(gè)兩個(gè)項(xiàng)目的優(yōu)化的時(shí)候這幾個(gè)這些方案其實(shí)都是摻雜在一起用的,單獨(dú)用某一個(gè)方案可能都沒有辦法解決所有的問題,而這個(gè)其實(shí)也需要一個(gè)非常仔細(xì)的調(diào)節(jié)才能做到效率和性能的平衡。
說完了這個(gè)效率,再提到一個(gè)卡頓,有些時(shí)候?qū)τ谧鰞?yōu)化不是非常熟悉的這些,可能會(huì)覺得這個(gè)卡頓和那個(gè)幀率低這兩個(gè)是一個(gè)事情,但這個(gè)就是卡頓和那個(gè)幀率低它不是一個(gè)事情,幀率低可能是因?yàn)槲覀兊倪\(yùn)算量太大,它的運(yùn)算量一直大,它可能持續(xù)的地比如說十幀、二十幀這樣的,那它是幀率低。但是對(duì)于卡頓,它是說在突然之間幀率有20幀,或者說突然之間由30幀掉到10幀馬上它可能又恢復(fù)了二三十幀的這個(gè)樣子,那么感受是非常差的。
通常有些時(shí)候比較有意思,我們?cè)诿鎸?duì)外部玩家的時(shí)候,可能外部玩家其實(shí)很難分得清楚到底哪一個(gè)是卡頓,到底哪一個(gè)是幀率低,這個(gè)時(shí)候我們?nèi)y(cè)試了,可能玩家就反饋 卡的厲害他說的這個(gè)卡到底是因?yàn)閹实瓦€是因?yàn)槌霈F(xiàn)忽然掉幀的情況,我們其實(shí)是要詳細(xì)地進(jìn)行去分析的,這個(gè)時(shí)候我們就用到了Unreal里面的前端工具,這個(gè)工具其實(shí)它對(duì)分析卡頓其實(shí)是非常有用的,因?yàn)槲覀兛梢灾苯涌吹矫恳粠?它的Game線程的波峰波谷,當(dāng)有出現(xiàn)一個(gè)特別特別高的這個(gè)卡頓的時(shí)候,它這一幀的時(shí)間就會(huì)花的特別長(zhǎng),我們可以很方便地就把這一幀就是抓下來從這個(gè)數(shù)據(jù)里面去分析,并且定位到到底是因?yàn)槭鞘裁矗@個(gè)工具它可以很方便提供咱們的堆棧信息。
在我們優(yōu)化的過程中其實(shí)也對(duì)這些優(yōu)化的點(diǎn)進(jìn)行了一些分析,大概卡頓主要出現(xiàn)在這么如下的幾個(gè)方面,一個(gè)是GC,一個(gè)是資源加載,一個(gè)是Actor的這個(gè)動(dòng)態(tài)創(chuàng)建。
1.GC。本身GC其實(shí)它是一個(gè)非常耗時(shí)的工作,因?yàn)樗闅v這些Object并且發(fā)現(xiàn)這些Object 哪一個(gè)是有引用的哪一個(gè)是沒有引用的,哪一個(gè)它可以釋放的,當(dāng)我們的Object相當(dāng)多的時(shí)候,本身它這個(gè)GC的時(shí)間也會(huì)跟著增長(zhǎng),在引擎里面提供了一個(gè)Max?Object?Not?Considered?By?GC這么一個(gè)選項(xiàng),默認(rèn)設(shè)置成1的話可以有效地提高整個(gè)GC的效率,因?yàn)樵O(shè)置成1以后引擎默認(rèn)創(chuàng)建的一些對(duì)象將不再會(huì)GC的時(shí)候進(jìn)行計(jì)算,從而可以少遍歷一些有這個(gè)的,這樣的話會(huì)有一定的效率提升。
當(dāng)我們的本身的內(nèi)存不是那么緊張的時(shí)候,也是可以考慮GC,稍微時(shí)間長(zhǎng)一些的話,這樣的話它卡頓的現(xiàn)象也能減少,它每一次的GC的時(shí)間不會(huì)像之前的那么長(zhǎng),那么的這些就會(huì)導(dǎo)致頻繁的卡頓。GC的卡頓有可能是在我們的那個(gè)性能分析工具里面去看到的最高的那么一個(gè)。
2.資源加載。尤其需要注意的是我們加載資源的時(shí)候,有時(shí)候會(huì)調(diào)用那個(gè)Static?Load?Object,當(dāng)我們調(diào)用Static?Load?Object的時(shí)候,由于引擎沒有辦法判斷這些資源的相關(guān)的依賴性,它首先會(huì)做的一件事情,它會(huì)Flush 異步加載的資源,這就有一個(gè)問題,如果我們正在手動(dòng)或者自動(dòng)地或者引擎 調(diào)用了一些異步加載的函數(shù)比如說我們發(fā)現(xiàn)現(xiàn)在加載了一個(gè)人,那么我遠(yuǎn)處看見一個(gè)人,我需要把它資源加載上來,如果用同步就很慢,同步這個(gè)東西它加載的時(shí)候就會(huì)很卡,我們就會(huì)把它設(shè)置成異步,異步加載它的這些衣服上的貼圖,異步地去加載一些它其它的一些資源之類的。這個(gè)時(shí)候如果剛好我們有一個(gè)其他的功能打開了,比如說一個(gè)UI這個(gè)Static?Load?Object,這個(gè)時(shí)候它就會(huì)先把異步加載的資源要求它先加載完成,所有的異步加載就都必須在這一幀馬上完成,這個(gè)時(shí)候就會(huì)帶來一個(gè)極大的卡頓,所以有些時(shí)候受這個(gè)影響比較大的。
在Level Streaming我們的場(chǎng)景地圖在進(jìn)行切換加載的時(shí)候,其實(shí)它有一些東西也是異步的,這個(gè)時(shí)候有些時(shí)候我們可能會(huì)非常奇怪,我加載了就這么一個(gè)Static Load Object的,加載了這么一個(gè)小小的這么一個(gè)UI為什么會(huì)造成這么大的一個(gè)卡頓,其實(shí)這個(gè)時(shí)候可能它會(huì)有一些其他的這些Level Streaming加載,或者說咱們一個(gè)異步加載的一些東西在里面,那么這些東西它才是真正造成卡頓的原因,所以對(duì)于Static Load Object的方式,我們?cè)谑褂弥幸欢ㄒ浅7浅5匦⌒摹?/p>
另外打開Texture?Streaming可以提高一些紋理的加載速度,一旦打開了Texture?Streaming它加載的時(shí)候其實(shí)會(huì)先顯示一些比較粗糙的精度的模型,它先從小的加載再把大的加載完了以后才會(huì)顯示出一些比較精細(xì)的就是上面的貼圖效果。資源加載這一塊,異步加載其實(shí)相對(duì)來講較慢,但它能緩解卡頓。所以Static?Load?Object它同步加載的這個(gè)它會(huì)加載的比較快,但是卡頓也是比較明顯,所以我們其實(shí)會(huì)傾向于比如說一些需要Static?Load?Object,盡量我們?cè)诩虞d場(chǎng)景讀條的時(shí)候把這些東西都加載完,場(chǎng)景里面如果有一些比較大的問題盡量使用異步的加載,就是少使用Static?Load?Object方式,因?yàn)樗鼤?huì)有太多的不可控的東西。
3.Actor的動(dòng)態(tài)創(chuàng)建。我們的這個(gè)場(chǎng)景里面殺怪可能有一波怪我們放了幾個(gè)AOE 技能,AOE的技能直接就把他們都?xì)⑺懒?,這個(gè)時(shí)候它又會(huì)重新創(chuàng)建,有些時(shí)候因?yàn)槲覀兊腁ctor帶著很多的Component,怪身上帶了很多的那個(gè)組件有不同的功能,還有不同的藍(lán)圖組件,這個(gè)時(shí)候我們?nèi)?dòng)態(tài)創(chuàng)建一個(gè)Actor的時(shí)候就會(huì)造成一定的卡頓,當(dāng)你的這個(gè)Component越多,你卡頓就會(huì)越明顯,你Component少的時(shí)候還好,但是我們會(huì)面臨著因?yàn)閯偛盘岬轿覀円驗(yàn)樯婕暗接螒蝾愋停覀兛赡軙?huì)進(jìn)行一些AOE的操作,比如說十幾個(gè)怪 一下子都?xì)⑺懒耍瑫r(shí)一下子又都加載出來了,那這個(gè)時(shí)候比較有效的方法第一就是要減少它Component的數(shù)量,同時(shí)它本身自己的貼圖精度也好,它的動(dòng)作也好,要盡量簡(jiǎn)單,我們加載它的這個(gè)時(shí)候相對(duì)來講就會(huì)好很多,但是這個(gè)不能解決根本問題。
我們也可以減少這個(gè)Actor的創(chuàng)建次數(shù),不要頻繁地生成和銷毀這些Actor,剛才提到的也可以進(jìn)行分幀加載,我每一幀只加載一個(gè),但是這樣又造成一個(gè)問題,比如說我十幾只怪都死了,那可能我加載出來它這個(gè)速度會(huì)非常的慢,分成了好幾幀可能有一些怪 它就跑了或者其他的感受又不好,所以我們后來把所有的就是重復(fù)的比較多的,比如說怪 同樣的怪特別多,那么我們就會(huì)為它創(chuàng)建一些Actor池,這樣的話我們當(dāng)一個(gè)怪死了以后我們不會(huì)直接把它Destroy,我們減少動(dòng)態(tài)創(chuàng)建,每次從池里面把它拿出來,當(dāng)它Destroy的時(shí)候我們?cè)俜呕剡@個(gè)池里,這是可以有效的減少方式,創(chuàng)建這個(gè)的時(shí)間幾乎就是沒有消耗的。
要解決卡頓,我認(rèn)為其實(shí)就是創(chuàng)建Actor池可能是一個(gè)終結(jié)的解決辦法,尤其對(duì)Actor的比較復(fù)雜的一種情況,當(dāng)然大家如果有一些其他比較好的方法也可以一起來討論。
剛才提到的就是整個(gè)的那個(gè)我們的造成幀率低和卡頓的原因,后續(xù)還有一個(gè)就是對(duì)于我們比較重要的其實(shí)是一個(gè)內(nèi)存,一個(gè)好的消息是我們現(xiàn)在機(jī)器 隨著機(jī)器越來越好,我們現(xiàn)在已經(jīng)能見到有配置12g的內(nèi)存的機(jī)器了,有沒有16g的我還沒見著,但這個(gè)基本上已經(jīng)快趕上我們的PC機(jī)了,但是同時(shí)我們又不得不面對(duì)很多低端機(jī),比如說像我們的剛才提到的那個(gè)比較老的機(jī)器,它可能只有兩g或者三g的內(nèi)存,這些的時(shí)候如果我們的內(nèi)存占用比較大,那么它崩潰的幾率就會(huì)比較大,很可能我們的東西還沒有加載完它崩潰了。
這里面又有幾個(gè)比較值得注意的東西,就是關(guān)于安卓機(jī)Armv7和Arm64的區(qū)別。ARM64它可用的內(nèi)存會(huì)多一些,如果我們只有一個(gè)ARM V7的包,那ARM V7它能夠利用的內(nèi)存可能只到了個(gè)2g以內(nèi)它肯定會(huì)崩潰,可能在一個(gè)1.45g的時(shí)候1.6g的時(shí)候可能它就已經(jīng)非常接近崩潰的邊緣了。我們其實(shí)就是應(yīng)該盡量的去使用ARM64的包,ARM64的包其實(shí)它的可用內(nèi)存會(huì)多一些,在我們使用內(nèi)存不太過分的情況下它崩的幾率就比ARM V7的要小要小很多了,例如我們使用到的1.7、1.8g的內(nèi)存可能它也不會(huì)崩潰。
我們?nèi)ゲ檫@個(gè)內(nèi)存的工具除了用一些ADB的指令以外,如果我們想去分析到底它占了多少內(nèi)存,我們用的Memory Report這個(gè)log比較多,以4.25這個(gè)引擎的log為例,有這么幾個(gè)東西是我們著重可以去觀察的,一個(gè)是Process,就是整個(gè)進(jìn)程占用的實(shí)際內(nèi)存是多少,另外的一個(gè)是通過引擎這分配的系統(tǒng)內(nèi)存是多少,還有一個(gè)是那個(gè)顯示所用的內(nèi)存是多少。當(dāng)然顯示所用的這個(gè)內(nèi)存其實(shí)由于它是驅(qū)動(dòng)層面去分配的,有可能也是分配在顯存里面或者其他方面的,RHI這部分內(nèi)存引擎log進(jìn)行的是一些估算,會(huì)有一定的偏差,但是可以給我們一定的作為我們?nèi)?yōu)化了這么一個(gè)參考的依據(jù)。
通過除了這幾個(gè)內(nèi)存以外,我們可能還會(huì)發(fā)現(xiàn)它和我們統(tǒng)計(jì)出來的系統(tǒng)內(nèi)存和顯示內(nèi)存加起來,比這個(gè)進(jìn)程實(shí)際占用的內(nèi)存要低一些,那是因?yàn)榈谝挥幸恍┑谌綆?kù)其實(shí)也會(huì)分配一些內(nèi)存,這些東西可能不在我們的這個(gè)系統(tǒng)內(nèi)存統(tǒng)計(jì)范圍內(nèi),還有就是我們本身自己運(yùn)行安卓的SO以及一些其他的這些東西現(xiàn)在也會(huì)占用一部分內(nèi)存,這些都是沒有接入引擎統(tǒng)計(jì)范疇內(nèi)的,但是實(shí)際又確確實(shí)實(shí)地占用了它的進(jìn)程實(shí)際的內(nèi)存之中。
我們常見的一些內(nèi)存的優(yōu)化的方式,剛才也提到了一些像我們的這個(gè)Level?Streaming,用Level?Streaming 本身就是可以讓我們的地塊進(jìn)行這個(gè)分塊加載,還有一個(gè)就是剛才也提到了就是Texture?Streaming。另外的一個(gè)就是說通過對(duì)于RHI內(nèi)存,我們可以多去考慮一下紋理的精度到底需要到一個(gè)什么樣的層面。
1.Level?Streaming 。它需要注意的是本身它是根據(jù)我們的這個(gè)包圍盒來進(jìn)行加載的,那么合理設(shè)置包圍盒和加載距離是非常重要的,它可以防止在某一個(gè)交界處人物來回移動(dòng)頻繁造成場(chǎng)景加載卸載的情況,你會(huì)覺得我就在這里走來走去的,會(huì)發(fā)現(xiàn)它的卡頓就非常的明顯,往往都是因?yàn)槲覀兊倪@個(gè)Level?Streaming。
Actor的序列化以及這些東西造成的一些問題。我們?cè)谧雨P(guān)卡的大小和加載之間需要做好權(quán)衡,太大了內(nèi)存太高,太小了這個(gè)加載又會(huì)頻繁那種卡頓,感受也都也都不是非常好。包括在做地形的時(shí)候,其實(shí)我們也有一些方式可以去 考慮,比如說我們做這個(gè)每一個(gè)這個(gè)子關(guān)卡的時(shí)候,它如果是一個(gè)比較接近于方形的,那在我們使用的時(shí)候會(huì)比較好用,如果非常不巧你把這個(gè)東西做成了一個(gè)梯形的,或者做成了一個(gè)L型的,這樣的話那你會(huì)發(fā)現(xiàn),它其實(shí)只有窄窄的一溜,但是在整個(gè)的這么一個(gè)大的Bounding?Box里面的話都需要去加載,這一塊就是也是需要我們?nèi)ブ谱髦行枰プ⒁獾囊粋€(gè)東西。
2.Texture?Streaming。由于這個(gè)UI它本身Texture一般不設(shè)置Mipmap,它是無(wú)法從這個(gè) Texture?Streaming中獲得優(yōu)化的,除了通過Pool?Size去設(shè)置這個(gè)內(nèi)存以外,它還可以加快這個(gè)Texture的紋理的加載速度,這個(gè)剛才已經(jīng)提到過了。我們?cè)O(shè)置完了這個(gè)Pool?Size以后,它可以比較好地去控制整個(gè)這個(gè)使用的紋理的總量,當(dāng)有一些超出池的這個(gè)Pool?Size的設(shè)置之外的,它可能會(huì)優(yōu)先的把一些遠(yuǎn)處的一些東西把它 替換成那個(gè)低的Mipmap。
3.紋理的精度。我們可以很清楚地發(fā)現(xiàn)每一個(gè)就是說紋理的精度是什么,我們可以通過剛才提到的那個(gè) log可以查到每一張紋理它大概占了多少的內(nèi)存,以及它是多大的,首先要考慮這些紋理有沒有減小,比如說我用了2048的貼圖是不是要用這么高的,還有我們可以通過設(shè)置這個(gè)在低端機(jī)上設(shè)置一些Mobile?Reduce?Loaded?Mips,這個(gè)可以把它最頂層的那一層忽略掉,這樣的話在它加載的時(shí)候也會(huì)減少很多的內(nèi)存。
因?yàn)榧y理差一半的話它的大小要差了4倍,所以這塊的話其實(shí)是可以很好地控制它的大小的。這些東西我們都可以在剛才提到的我們的Profiling里面去設(shè)置,根據(jù)高端機(jī)和低端機(jī)不同再切換,低端機(jī)既然內(nèi)存小的話,那我們有一些紋理的精度可能降了一些也是可以理解的。
那么剛才也大致講了一下就是我之前優(yōu)化過的一些東西,還有一些我關(guān)于這個(gè)優(yōu)化的幾點(diǎn)思考。
首先就是說優(yōu)化,我認(rèn)為應(yīng)該是盡量的不損失游戲的效果以及功能,那么在不損失這個(gè)游戲效果和功能的情況下進(jìn)行優(yōu)化了其實(shí)才是這個(gè)最優(yōu)的選擇。另外還有就是以產(chǎn)品的一個(gè)角度思考問題,它需要這個(gè)設(shè)計(jì)資源和技術(shù)的共同努力,有些時(shí)候如果只靠技術(shù)方向解決可能有一些東西不見得解決的非常完美,如果很多能從設(shè)計(jì)角度去規(guī)避的東西才是最優(yōu)的選擇。從設(shè)計(jì)完了以后,如果從資源的角度再能控制好,那么我們?cè)诩夹g(shù)上就可以少用一些剛才提到的技術(shù)手段。
優(yōu)化其實(shí)它是貫穿游戲開發(fā)始終的這么一個(gè)過程,它其實(shí)本身就是說是不斷地做,不斷地優(yōu)化,往往有些時(shí)候可能大家都想是不是我們可以開始的時(shí)候不做,我一開始的就是把所有的這些效果都做出來,等到了差不多快上線的時(shí)候我再去優(yōu)化,其實(shí)這個(gè)時(shí)候往往你會(huì)發(fā)現(xiàn)來不及,因?yàn)楦鞣N東西涉及到的問題太多。我認(rèn)為持續(xù)的優(yōu)化它是對(duì)產(chǎn)品品質(zhì)的這么一個(gè)不懈的追求,那么我們 為什么要做優(yōu)化,其實(shí)總結(jié)成一句話,就是要將更好的效果帶給更多的用戶,使更多的用戶可以體驗(yàn)到我們真正的高品質(zhì)的游戲,這就是關(guān)于優(yōu)化的一些思考。
謝謝大家!
405959