【Unity技巧】Unity中的优化技术
寫在前面
這一篇是在Digital Tutors的一個(gè)系列教程的基礎(chǔ)上總結(jié)擴(kuò)展而得的~Digital Tutors是一個(gè)非常棒的教程網(wǎng)站,包含了多媒體領(lǐng)域很多方面的資料,非常酷!除此之外,還參考了Unity Cookie中的一個(gè)教程。還有很多其他參考在下面的鏈接中。
這篇文章旨在簡(jiǎn)要地說(shuō)明一下常見(jiàn)的各種優(yōu)化策略。不過(guò)對(duì)每個(gè)基礎(chǔ)有非常深入地講解,需要的童鞋可以自行去相關(guān)資料。
還有一些我認(rèn)為非常好的參考文章:
Performance Optimization for Mobile Devices
4 Ways To Increase Performance of your Unity Game
Unite 2013 Optimizing Unity Games for Mobile Platforms
Unity optimization Tips
影響性能的因素
首先,我們得了解,影響游戲性能的因素哪些,才能對(duì)癥下藥。對(duì)于一個(gè)游戲來(lái)說(shuō),有兩種主要的計(jì)算資源:CPU和GPU。它們會(huì)互相合作,來(lái)讓我們的游戲可以在預(yù)期的幀率和分辨率下工作。CPU負(fù)責(zé)其中的幀率,GPU主要負(fù)責(zé)分辨率相關(guān)的一些東西。
總結(jié)起來(lái),主要的性能瓶頸在于:
- CPU
- 過(guò)多的Draw Calls
- 復(fù)雜的腳本或者物理模擬
- 過(guò)多的Draw Calls
- 頂點(diǎn)處理
- 過(guò)多的頂點(diǎn)
- 過(guò)多的逐頂點(diǎn)計(jì)算
- 過(guò)多的頂點(diǎn)
- 像素(Fragment)處理
- 過(guò)多的fragment,overdraws
- 過(guò)多的逐像素計(jì)算
- 過(guò)多的fragment,overdraws
- 帶寬
- 尺寸很大且未壓縮的紋理
- 分辨率過(guò)高的framebuffer
- 尺寸很大且未壓縮的紋理
對(duì)于CPU來(lái)說(shuō),限制它的主要是游戲中的Draw Calls。那么什么是Draw Call呢?如果你學(xué)過(guò)OpenGL,那么你一定還記得在每次繪圖前,我們都需要先準(zhǔn)備好頂點(diǎn)數(shù)據(jù)(位置、法線、顏色、紋理坐標(biāo)等),然后調(diào)用一系列API把它們放到GPU可以訪問(wèn)到的指定位置,最后,我們需要調(diào)用_glDraw*命令,來(lái)告訴GPU,“嘿,我把東西都準(zhǔn)備好了,你個(gè)懶家伙趕緊出來(lái)干活(渲染)吧!”。而調(diào)用_glDraw*命令的時(shí)候,就是一次Draw Call。那么為什么Draw Call會(huì)成為性能瓶頸呢(而且是CPU的瓶頸)?上面說(shuō)到過(guò),我們想要繪制圖像時(shí),就一定需要調(diào)用Draw Call。例如,一個(gè)場(chǎng)景里有水有樹(shù),我們渲染水的時(shí)候使用的是一個(gè)material以及一個(gè)shader,但渲染樹(shù)的時(shí)候就需要一個(gè)完全不同的material和shader,那么就需要CPU重新準(zhǔn)備頂點(diǎn)數(shù)據(jù)、重新設(shè)置shader,而這種工作實(shí)際是非常耗時(shí)的。如果場(chǎng)景中,每一個(gè)物體都使用不同的material、不同的紋理,那么就會(huì)產(chǎn)生太多Draw Call,影響幀率,游戲性能就會(huì)下降。當(dāng)然,這里說(shuō)得很簡(jiǎn)單,更詳細(xì)的請(qǐng)自行谷歌。其他CPU的性能瓶頸還有物理、布料模擬、粒子模擬等,都是計(jì)算量很大的操作。
而對(duì)于GPU來(lái)說(shuō),它負(fù)責(zé)整個(gè)渲染流水線。它會(huì)從處理CPU傳遞過(guò)來(lái)的模型數(shù)據(jù)開(kāi)始,進(jìn)行Vertex Shader、Fragment Shader等一系列工作,最后輸出屏幕上的每個(gè)像素。因此它的性能瓶頸可能和需要處理的頂點(diǎn)數(shù)目的、屏幕分辨率、顯存等因素有關(guān)。總體包含了頂點(diǎn)和像素兩方面的性能瓶頸。在像素處理中,最常見(jiàn)的性能瓶頸之一是overdraw。Overdraw指的是,我們可能對(duì)屏幕上的像素繪制了多次。
了解了上面基本的內(nèi)容后,下面涉及到的優(yōu)化技術(shù)有:
- 頂點(diǎn)優(yōu)化
- 優(yōu)化幾何體
- 使用LOD(Level of detail)技術(shù)
- 使用遮擋剔除(Occlusion culling)技術(shù)
- 優(yōu)化幾何體
- 像素優(yōu)化
- 控制繪制順序
- 警惕透明物體
- 減少實(shí)時(shí)光照
- 控制繪制順序
- CPU優(yōu)化
- 減少Draw Calls
- 減少Draw Calls
- 帶寬優(yōu)化
- 減少紋理大小
- 利用縮放
- 減少紋理大小
首先是頂點(diǎn)優(yōu)化的部分。
頂點(diǎn)優(yōu)化
優(yōu)化幾何體
這一步主要是為了針對(duì)性能瓶頸中的”頂點(diǎn)處理“一項(xiàng)。這里的幾何體就是指組成場(chǎng)景中對(duì)象的網(wǎng)格結(jié)構(gòu)。
3D游戲制作都由模型制作開(kāi)始。而在建模時(shí),有一條我們需要記住:盡可能減少模型中三角形的數(shù)目,一些對(duì)于模型沒(méi)有影響、或是肉眼非常難察覺(jué)到區(qū)別的頂點(diǎn)都要盡可能去掉。例如在下面左圖中,正方體內(nèi)部很多頂點(diǎn)都是不需要的,而把這個(gè)模型導(dǎo)入到Unity里就會(huì)是右面的情景:?
在Game視圖下,我們可以查看場(chǎng)景中的三角形數(shù)目和頂點(diǎn)數(shù)目:
可以看到一個(gè)簡(jiǎn)單的正方形就產(chǎn)生了這么多頂點(diǎn),這是我們不希望看到的。
同時(shí),盡可能重用頂點(diǎn)。在很多三維建模軟件中,都有相應(yīng)的優(yōu)化選項(xiàng),可以自動(dòng)優(yōu)化網(wǎng)格結(jié)構(gòu)。最后優(yōu)化后,一個(gè)正方體可能只剩下8個(gè)頂點(diǎn):?
它對(duì)應(yīng)的頂點(diǎn)數(shù)和三角形數(shù)目如下:
等等!這里,你可能要問(wèn)了,為什么頂點(diǎn)數(shù)是24,而不是8呢?美術(shù)朋友們經(jīng)常會(huì)遇到這樣的問(wèn)題,就是建模軟件里顯示的模型頂點(diǎn)數(shù)和Unity中的不一樣,通常Unity會(huì)多很多。誰(shuí)才是對(duì)的呢?其實(shí),它們是站在不同的角度上計(jì)算的,都有各自的道理,但我們真正應(yīng)該關(guān)心的是Unity里的數(shù)目。
我們這里簡(jiǎn)單解釋一下。三維軟件里更多地是站在我們?nèi)祟惖慕嵌壤斫忭旤c(diǎn)的,即我們看見(jiàn)的一個(gè)點(diǎn)就是一個(gè)。而Unity是站在GPU的角度上,去計(jì)算頂點(diǎn)數(shù)目的。而在GPU看來(lái),看起來(lái)是一個(gè)的很有可能它要分開(kāi)處理,從而就產(chǎn)生了額外的頂點(diǎn)。這種將頂點(diǎn)一分為多的原因,主要有兩個(gè):一個(gè)是UV splits,一個(gè)是Smoothing splits。而它們的本質(zhì)其實(shí)都是因?yàn)閷?duì)于GPU來(lái)說(shuō),頂點(diǎn)的每一個(gè)屬性和頂點(diǎn)之間必須是一對(duì)一的關(guān)系。UV splits的產(chǎn)生,是因?yàn)榻r(shí),一個(gè)頂點(diǎn)的UV坐標(biāo)有多個(gè)。例如之前的立方體的例子,由于每個(gè)面都有共同的頂點(diǎn),因此在不同面上,同一個(gè)頂點(diǎn)的UV坐標(biāo)可能發(fā)生改變。這對(duì)于GPU來(lái)說(shuō),這是不可理解的,因此它必須把這個(gè)頂點(diǎn)拆分成兩個(gè)具有不同UV坐標(biāo)的定頂點(diǎn),它才甘心。而Smoothing splits的產(chǎn)生也是類似的,不同的時(shí),這次一個(gè)頂點(diǎn)可能會(huì)對(duì)應(yīng)多個(gè)法線信息或切線信息。這通常是因?yàn)槲覀円獩Q定一個(gè)邊是一條Hard Edge還是Smooth Edge。Hard Edge通常是下面這樣的效果(注意中間的折痕部分):?
而如果觀察它的頂點(diǎn)法線,就會(huì)發(fā)現(xiàn),折痕處每個(gè)頂點(diǎn)其實(shí)包含了兩個(gè)不同的法線。因此,對(duì)于GPU來(lái)說(shuō),它同樣無(wú)法理解這樣的事情,因此會(huì)把頂點(diǎn)一分為二。而相反,Smooth Edge則是下面的情況:?
對(duì)于GPU來(lái)說(shuō),它本質(zhì)上只關(guān)心有多少個(gè)頂點(diǎn)。因此,盡可能減少頂點(diǎn)的數(shù)目其實(shí)才是我們真正對(duì)需要關(guān)心的事情。因此,最后一條優(yōu)化建議就是:移除不必要的Hard Edge以及紋理銜接,即避免Smoothing splits和UV?splits。
使用LOD(Level of detail)技術(shù)
LOD技術(shù)有點(diǎn)類似于Mipmap技術(shù),不同的是,LOD是對(duì)模型建立了一個(gè)模型金字塔,根據(jù)攝像機(jī)距離對(duì)象的遠(yuǎn)近,選擇使用不同精度的模型。它的好處是可以在適當(dāng)?shù)臅r(shí)候大量減少需要繪制的頂點(diǎn)數(shù)目。它的缺點(diǎn)同樣是需要占用更多的內(nèi)存,而且如果沒(méi)有調(diào)整好距離的話,可能會(huì)造成模擬的突變。
在Unity中,可以通過(guò)LOD Group來(lái)實(shí)現(xiàn)LOD技術(shù):
?
通過(guò)上面的LOD Group面板,我們可以選擇需要控制的模型以及距離設(shè)置。下面展示了油桶從一個(gè)完整網(wǎng)格到簡(jiǎn)化網(wǎng)格,最后完全被剔除的例子:
????
使用遮擋剔除(Occlusion culling)技術(shù)
遮擋剔除是用來(lái)消除躲在其他物件后面看不到的物件,這代表資源不會(huì)浪費(fèi)在計(jì)算那些看不到的頂點(diǎn)上,進(jìn)而提升性能。關(guān)于遮擋剔除,Unity Taiwan有一個(gè)系列文章大家可以看看(需FQ):
Unity 4.3 關(guān)于Occlusion Culling : 基本篇
Unity 4.3 關(guān)于Occlusion Culling : 最佳做法
Unity 4.3 關(guān)于Occlusion Culling : 錯(cuò)誤診斷
具體的內(nèi)容大家可以自行查找。
現(xiàn)在我們來(lái)談像素優(yōu)化。
像素優(yōu)化
像素優(yōu)化的重點(diǎn)在于減少overdraw。之前提過(guò),overdraw指的就是一個(gè)像素被繪制了多次。關(guān)鍵在于控制繪制順序。
Unity還提供了查看overdraw的視圖,在Scene視圖的Render Mode->Overdraw。當(dāng)然這里的視圖只是提供了查看物體遮擋的層數(shù)關(guān)系,并不是真正的最終屏幕繪制的overdraw。也就是說(shuō),可以理解為它顯示的是如果沒(méi)有使用任何深度檢驗(yàn)時(shí)的overdraw。這種視圖是通過(guò)把所有對(duì)象都渲染成一個(gè)透明的輪廓,通過(guò)查看透明顏色的累計(jì)程度,來(lái)判斷物體的遮擋。
上圖圖,紅色越是濃重的地方表示overdraw越嚴(yán)重,而且這里涉及的都是透明物體,這意味著性能將會(huì)受到很大影響。
控制繪制順序
需要控制繪制順序,主要原因是為了最大限度的避免overdraws,也就是同一個(gè)位置的像素可以需要被繪制多變。在PC上,資源無(wú)限,為了得到最準(zhǔn)確的渲染結(jié)果,繪制順序可能是從后往前繪制不透明物體,然后再繪制透明物體進(jìn)行混合。但在移動(dòng)平臺(tái)上,這種會(huì)造成大量overdraw的方式顯然是不適合的,我們應(yīng)該盡量從前往后繪制。從前往后繪制之所以可以減少overdraw,都是因?yàn)樯疃葯z驗(yàn)的功勞。
在Unity中,那些Shader中被設(shè)置為“Geometry” 隊(duì)列的對(duì)象總是從前往后繪制的,而其他固定隊(duì)列(如“Transparent”“Overla”等)的物體,則都是從后往前繪制的。這意味這,我們可以盡量把物體的隊(duì)列設(shè)置為“Geometry” 。
而且,我們還可以充分利用Unity的隊(duì)列來(lái)控制繪制順序。例如,對(duì)于天空盒子來(lái)說(shuō),它幾乎覆蓋了所有的像素,而且我們知道它永遠(yuǎn)會(huì)在所有物體的后面,因此它的隊(duì)列可以設(shè)置為“Geometry+1”。這樣,就可以保證不會(huì)因?yàn)樗斐蒾verdraws。
時(shí)刻警惕透明物體
而對(duì)于透明對(duì)象,由于它本身的特性(可以看之前關(guān)于Alpha Test和Alpha Blending的一篇文章)決定如果要得到正確的渲染效果,就必須從后往前渲染(這里不討論使用深度的方法),而且拋棄了深度檢驗(yàn)。這意味著,透明物體幾乎一定會(huì)造成overdraws。如果我們不注意這一點(diǎn),在一些機(jī)器上可能會(huì)造成嚴(yán)重的性能下面。例如,對(duì)于GUI對(duì)象來(lái)說(shuō),它們大多被設(shè)置成了半透明,如果屏幕中GUI占據(jù)的比例太多,而主攝像機(jī)又沒(méi)有進(jìn)行調(diào)整而是投影整個(gè)屏幕,那么GUI就會(huì)造成屏幕的大量overdraws。
因此,如果場(chǎng)景中大面積的透明對(duì)象,或者有很多層覆蓋的多層透明對(duì)象(即便它們每個(gè)的面積可以都不大),或者是透明的粒子效果,在移動(dòng)設(shè)備上也會(huì)造成大量的overdraws。這是應(yīng)該盡量避免的。
對(duì)于上述GUI的這種情況,我們可以盡量減少窗口中GUI所占的面積。如果實(shí)在無(wú)能為力,我們可以把GUI繪制和三維場(chǎng)景的繪制交給不同的攝像機(jī),而其中負(fù)責(zé)三維場(chǎng)景的攝像機(jī)的視角范圍盡量不要和GUI重疊。對(duì)于其他情況,只能說(shuō),盡可能少用。當(dāng)然這樣會(huì)對(duì)游戲的美觀度產(chǎn)生一定影響,因此我們可以在代碼中對(duì)機(jī)器的性能進(jìn)行判斷,例如首先關(guān)閉所有的耗費(fèi)性能的功能,如果發(fā)現(xiàn)這個(gè)機(jī)器表現(xiàn)非常良好,再嘗試開(kāi)啟一些特效功能。
減少實(shí)時(shí)光照
實(shí)時(shí)光照對(duì)于移動(dòng)平臺(tái)是個(gè)非常昂貴的操作。如果只有一個(gè)平行光還好,但如果場(chǎng)景中包含了太多光源并且使用了很多多Passes的shader,那么很有可能會(huì)造成性能下降。而且在有些機(jī)器上,還要面臨shader失效的風(fēng)險(xiǎn)。例如,一個(gè)場(chǎng)景里如果包含了三個(gè)逐像素的點(diǎn)光源,而且使用了逐像素的shader,那么很有可能將Draw Calls提高了三倍,同時(shí)也會(huì)增加overdraws。這是因?yàn)?#xff0c;對(duì)于逐像素的光源來(lái)說(shuō),被這些光源照亮的物體要被再渲染一次。更糟糕的是,無(wú)論是動(dòng)態(tài)批處理還是動(dòng)態(tài)批處理(其實(shí)文檔中只提到了對(duì)動(dòng)態(tài)批處理的影響,但不知道為什么實(shí)驗(yàn)結(jié)果對(duì)靜態(tài)批處理也沒(méi)有用),對(duì)于這種逐像素的pass都無(wú)法進(jìn)行批處理,也就是說(shuō),它們會(huì)中斷批處理。
例如,下面的場(chǎng)景中,四個(gè)物體都被標(biāo)識(shí)成了“Static”,它們使用的shader都是自帶的Bumped Diffuse。而所有的點(diǎn)光源都被標(biāo)識(shí)成了“Important”,即是逐像素光。可以看到,運(yùn)行后的Draw Calls是23,而非3。這是因?yàn)?#xff0c;只有“Forward Base”的Pass時(shí)發(fā)生了靜態(tài)批處理(這里的動(dòng)態(tài)批處理由于多Pass已經(jīng)完全失效了),節(jié)省了一個(gè)Draw Calls,而后面的“Forward Add” Pass,每一次渲染都是一個(gè)單獨(dú)的Draw Call(而且可以看到Tris和Verts數(shù)目也增加了):
這點(diǎn)正如文檔中說(shuō)的:The draw calls for “additional per-pixel lights” will not be batched。原因我不是很清楚,這里有一個(gè)討論,但里面的意思說(shuō)是對(duì)靜態(tài)批處理沒(méi)有影響,和我這里的結(jié)果不一樣,知道原因的麻煩給我留言,非常感謝。我也在Unity論壇里提問(wèn)里。
我們看到很多成功的移動(dòng)游戲,它們的畫面效果看起來(lái)好像包含了很多光源,但其實(shí)這都是騙人的。
使用Lightmaps
Lightmaps的很常見(jiàn)的一種優(yōu)化策略。它主要用于場(chǎng)景中整體的光照效果。這種技術(shù)主要是提前把場(chǎng)景中的光照信息存儲(chǔ)在一張光照紋理中,然后在運(yùn)行時(shí)刻只需要根據(jù)紋理采樣得到光照信息即可。
當(dāng)然與之配合的還有Light Probes技術(shù)。風(fēng)宇沖有一個(gè)系列文章講過(guò),但是時(shí)間比較久遠(yuǎn),但教程我相信網(wǎng)上有很多。
使用God Rays
場(chǎng)景中很多小型光源效果都是靠這種方法模擬的。它們一般并不是真的光源產(chǎn)生的,很多情況是通過(guò)透明紋理進(jìn)行模擬。具體可以參見(jiàn)之前的文章。
CPU優(yōu)化
減少Draw Calls
批處理(Batching)
這方面的優(yōu)化教程想必是最多的了。最常見(jiàn)的就是通過(guò)批處理(Batching)了。從名字上來(lái)理解,就是一塊處理多個(gè)物體的意思。那么什么樣的物體可以一起處理呢?答案就是使用同一個(gè)材質(zhì)的物體。這是因此,對(duì)于使用同一個(gè)材質(zhì)的物體,它們之間的不同僅僅在于頂點(diǎn)數(shù)據(jù)的差別,即使用的網(wǎng)格不同而已。我們可以把這些頂點(diǎn)數(shù)據(jù)合并在一起,再一起發(fā)送給GPU,就可以完成一次批處理。
Unity中有兩種批處理方式:一種是動(dòng)態(tài)批處理,一種是靜態(tài)批處理。對(duì)于動(dòng)態(tài)批處理來(lái)說(shuō),好消息是一切處理都是自動(dòng)的,不需要我們自己做任何操作,而且物體是可以移動(dòng)的,但壞消息是,限制很多,可能一不小心我們就會(huì)破壞了這種機(jī)制,導(dǎo)致Unity無(wú)法批處理一些使用了相同材質(zhì)的物體。對(duì)于靜態(tài)批處理來(lái)說(shuō),好消息是自由度很高,限制很少,壞消息是可能會(huì)占用更多的內(nèi)存,而且經(jīng)過(guò)靜態(tài)批處理后的所有物體都不可以再移動(dòng)了。
首先來(lái)說(shuō)動(dòng)態(tài)批處理。Unity進(jìn)行動(dòng)態(tài)批處理的條件是,物體使用同一個(gè)材質(zhì)并且滿足一些特定條件。Unity總是在不知不覺(jué)中就為我們做了動(dòng)態(tài)批處理。例如下面的場(chǎng)景:
這個(gè)場(chǎng)景共包含了4個(gè)物體,其中兩個(gè)箱子使用了同一個(gè)材質(zhì)。可以看到,它的Draw Calls現(xiàn)在是3,并且顯示Save by batching是1,也就是說(shuō),Unity靠Batching為我們節(jié)省了1個(gè)Draw Call。下面,我們來(lái)把其中一個(gè)箱子的大小隨便改動(dòng)一下,看看會(huì)發(fā)生什么:
可以發(fā)現(xiàn),Draw Calls變成了4,Save by batching的數(shù)目也變成了0。這是為什么呢?它們明明還是只使用了一個(gè)材質(zhì)啊。原因就是前面提到的那些需要滿足的其他條件。動(dòng)態(tài)批處理雖然自動(dòng)得令人感動(dòng),但它對(duì)模型的要求很多:
- 頂點(diǎn)屬性的最大限制為900,而且未來(lái)有可能會(huì)變。不要依賴這個(gè)數(shù)據(jù)。
- 一般來(lái)說(shuō),那么所有對(duì)象都必須需要使用同一個(gè)縮放尺度(可以是(1, 1, 1)、(1, 2, 3)、(1.5, 1.4, 1.3)等等,但必須都一樣)。但如果是非統(tǒng)一縮放(即每個(gè)維度的縮放尺度不一樣,例如(1, 2, 1)),那么如果所有的物體都使用不同的非統(tǒng)一縮放也是可以批處理的。這個(gè)要求很怪異,為什么批處理會(huì)和縮放有關(guān)呢?這和Unity背后的技術(shù)有關(guān)系,有興趣的可以自行谷歌,比如這里。
- 使用lightmap的物體不會(huì)批處理。多passes的shader會(huì)中斷批處理。接受實(shí)時(shí)陰影的物體也不會(huì)批處理。
上述除了最常見(jiàn)的由于縮放導(dǎo)致破壞批處理的情況,還有就是頂點(diǎn)屬性的限制。例如,在上面的場(chǎng)景中我們添加之前未優(yōu)化后的箱子模型:
可以看到Draw Calls一下子變成了5。這是因?yàn)樾绿砑拥南渥幽P椭?#xff0c;包含了474個(gè)頂點(diǎn),而它使用的頂點(diǎn)屬性有位置、UV坐標(biāo)、法線等信息,使用的總和超過(guò)了900。
動(dòng)態(tài)批處理的條件這么多,一不小心它就不干了,因此Unity提供了另一個(gè)方法,靜態(tài)批處理。接著上面的例子,我們保持修改后的縮放,但把四個(gè)物體的“Static Flag”勾選上:
點(diǎn)擊Static后面的三角下拉框,我們會(huì)看到其實(shí)這一步設(shè)置了很多東西,這里我們想要的只是“Batching static”一項(xiàng)。這時(shí)我們?cè)倏碊raw Calls,恩,還是沒(méi)有變化。但是不要急,我們點(diǎn)擊運(yùn)行,變化出現(xiàn)了:
Draw Calls又回到了3,并且顯示Save by batching是1。這就是得利于靜態(tài)批處理。而且,如果我們?cè)谶\(yùn)行時(shí)刻查看模型的網(wǎng)格,會(huì)發(fā)現(xiàn)它們都變成了一個(gè)名為Combined Mesh (roo: scene)的東西。這個(gè)網(wǎng)格是Unity合并了所有標(biāo)識(shí)為“Static”的物體的結(jié)果,在我們的例子里,就是四個(gè)物體:
你可以要問(wèn)了,這四個(gè)對(duì)象明明不是都使用了一個(gè)材質(zhì),為什么可以合并成一個(gè)呢?如果你仔細(xì)觀察上圖的話,會(huì)發(fā)現(xiàn)里面標(biāo)明了“4 submeshes”,也就是說(shuō),這個(gè)合并后的網(wǎng)格其實(shí)包含了4個(gè)子網(wǎng)格,也就是我們的四個(gè)對(duì)象。對(duì)于合并后后的網(wǎng)格,Unity會(huì)判斷其中使用同一個(gè)材質(zhì)的子網(wǎng)格,然后對(duì)它們進(jìn)行批處理。
但是,我們?cè)偌?xì)心點(diǎn)可以發(fā)現(xiàn),我們的箱子使用的其實(shí)是同一個(gè)網(wǎng)格,但合并后卻變成了兩個(gè)。而且,我們觀察運(yùn)行前后Stats窗口中的“VBO total”,它的大小由241.6KB變成了286.2KB,變大了!還記得靜態(tài)批處理的缺點(diǎn)嗎?就是可能會(huì)占用更多的內(nèi)存。文檔中是這樣寫的:
“Using static batching will require additional memory for storing the combined geometry. If several objects shared the same geometry before static batching, then a copy of geometry will be created for each object, either in the Editor or at runtime. This might not always be a good idea - sometimes you will have to sacrifice rendering performance by avoiding static batching for some objects to keep a smaller memory footprint. For example, marking trees as static in a dense forest level can have serious memory impact.”
也就是說(shuō),如果在靜態(tài)批處理前有一些物體共享了相同的網(wǎng)格(例如這里的兩個(gè)箱子),那么每一個(gè)物體都會(huì)有一個(gè)該網(wǎng)格的復(fù)制品,即一個(gè)網(wǎng)格會(huì)變成多個(gè)網(wǎng)格被發(fā)送給GPU。在上面的例子看來(lái),就是VBO的大小明顯增大了。如果這類使用同一網(wǎng)格的對(duì)象很多,那么這就是一個(gè)問(wèn)題了,這種時(shí)候我們可能需要避免使用靜態(tài)批處理,這意味著犧牲一定的渲染性能。例如,如果在一個(gè)使用了1000個(gè)重復(fù)樹(shù)模型的森林中使用靜態(tài)批處理,那么結(jié)果就會(huì)產(chǎn)生1000倍的內(nèi)存,這會(huì)造成嚴(yán)重的內(nèi)存影響。這種時(shí)候,解決方法要么我們可以忍受這種犧牲內(nèi)存換取性能的方法,要么不要使用靜態(tài)批處理,而使用動(dòng)態(tài)批處理(前提是大家使用相同的縮放大小,或者大家都使用不同的非統(tǒng)一縮放大小),或者自己編寫批處理的方法。當(dāng)然,我認(rèn)為最好的還是使用動(dòng)態(tài)批處理來(lái)解決。
有一些小提示可以使用:
- 盡可能選擇靜態(tài)批處理,但得時(shí)刻小心對(duì)內(nèi)存的消耗。
- 如果無(wú)法進(jìn)行靜態(tài)批處理,而要使用動(dòng)態(tài)批處理的話,那么請(qǐng)小心上面提到的各種注意事項(xiàng)。例如:
- 盡可能讓這樣的物體少并且盡可能讓這些物體包含少量的頂點(diǎn)屬性。
- 不要使用統(tǒng)一縮放,或者都使用不同的非統(tǒng)一縮放。
- 盡可能讓這樣的物體少并且盡可能讓這些物體包含少量的頂點(diǎn)屬性。
- 對(duì)于游戲中的小道具,例如可以撿拾的金幣等,可以使用動(dòng)態(tài)批處理。
- 對(duì)于包含動(dòng)畫的這類物體,我們無(wú)法全部使用靜態(tài)批處理,但其中如果有不動(dòng)的部分,可以把這部分標(biāo)識(shí)成“Static”。
一些討論:
How static batching works
Static batching use a ton of memory?
Unity3D draw call optimization
合并紋理(Atlas)
雖然批處理是個(gè)很好的方式,但很容易就打破它的規(guī)定。例如,場(chǎng)景中的物體都使用Diffuse材質(zhì),但它們可能會(huì)使用不同的紋理。因此,盡可能把多張小紋理合并到一張大紋理(Atlas)中是一個(gè)好主意。
利用網(wǎng)格的頂點(diǎn)數(shù)據(jù)
但有時(shí),除了紋理不同外,還有對(duì)于不同的物體,它們?cè)诓馁|(zhì)上還有一些微小的參數(shù)變化,例如顏色不同、某些浮點(diǎn)參數(shù)不同。但鐵定律是,不管是動(dòng)態(tài)批處理還是靜態(tài)批處理,它們的前提都是要使用同一個(gè)材質(zhì)。是同一個(gè),而不是同一種,也就是說(shuō)它們指向的材質(zhì)必須是同一個(gè)實(shí)體。這意味著,只要我們調(diào)整了參數(shù),就會(huì)影響到所有使用這個(gè)材質(zhì)的對(duì)象。那么想要微小的調(diào)整怎么辦呢?由于Unity中的規(guī)定非常死,那么我們只好想些“歪門邪道”,其中一種就是使用網(wǎng)格的頂點(diǎn)數(shù)據(jù)(最常見(jiàn)的就是頂點(diǎn)顏色數(shù)據(jù))。
前面說(shuō)過(guò),經(jīng)過(guò)批處理后的物體會(huì)被處理成一個(gè)VBO發(fā)送給GPU,VBO中的數(shù)據(jù)可以作為輸入傳遞給Vertex Shader,因此我們可以巧妙地對(duì)VBO中的數(shù)據(jù)進(jìn)行控制,從而達(dá)到不同效果的目的。一個(gè)例子是,還是之前的森林,所有的樹(shù)使用了同一種材質(zhì),我們希望它們可以通過(guò)動(dòng)態(tài)批處理來(lái)實(shí)現(xiàn),但不同樹(shù)的顏色可能不同。這時(shí)我么可以利用網(wǎng)格的頂點(diǎn)數(shù)據(jù)來(lái)調(diào)整。具體方法,可以參見(jiàn)后面會(huì)寫的一篇文章。
但這種方法的缺點(diǎn)就是會(huì)需要更多的內(nèi)存來(lái)存儲(chǔ)這些用于調(diào)整參數(shù)用的頂點(diǎn)數(shù)據(jù)。沒(méi)辦法,永遠(yuǎn)沒(méi)有絕對(duì)完美的方法。
帶寬優(yōu)化
減少紋理大小
之前提到過(guò),使用Texture Atlas可以幫助減少Draw Calls,而這些紋理的大小同樣是一個(gè)需要考慮的問(wèn)題。在這之前要提到一個(gè)問(wèn)題就是,所有紋理的長(zhǎng)寬比最好是正方形,而且長(zhǎng)度值最好是2的整數(shù)冪。這是因?yàn)橛泻芏鄡?yōu)化策略只有在這種時(shí)候才可以發(fā)揮最大效用。
Unity中查看紋理參數(shù)可以通過(guò)紋理的面板:
而調(diào)整參數(shù)可以通過(guò)紋理的Advance面板:
上面各種參數(shù)的說(shuō)明可以參見(jiàn)文檔。其中和優(yōu)化相關(guān)的主要有“Generate Mip Maps”、“Max Size”和“Format”幾個(gè)選項(xiàng)。
“Generate Mip Maps”會(huì)為同一張紋理創(chuàng)建出很多不同大小的小紋理,構(gòu)成一個(gè)紋理金字塔。而在游戲中可以根據(jù)距離物體的遠(yuǎn)近,來(lái)動(dòng)態(tài)選擇使用哪一個(gè)紋理。這是因?yàn)?#xff0c;在距離物體很遠(yuǎn)的時(shí)候,就算我們使用了非常精細(xì)的紋理,但肉眼也是分辨不出來(lái)的,這種時(shí)候完全可以使用更小、更模糊的紋理來(lái)代替,而這大量可以節(jié)省訪問(wèn)的像素的數(shù)目。但它的缺點(diǎn)是,由于需要為每一個(gè)紋理建立一個(gè)圖像金字塔,因此它會(huì)需要占用更多的內(nèi)存。例如上面的例子,在勾選“Generate Mip Maps”前,內(nèi)存占用是0.5M,而勾選了“Generate Mip Maps”后,就變成了0.7M。除了內(nèi)存的占用以外,一些時(shí)候我們也不希望使用Mipmaps,例如GUI紋理等。我們還可以在面板中查看生成的Mip Maps:
Unity中還提供了查看場(chǎng)景中物體的Mip Maps的使用情況。更確切的說(shuō)是,展示了物體理想的紋理大小。其中紅色表示這個(gè)物體可以使用更小的紋理,藍(lán)色表示應(yīng)該使用更大的紋理。
“Max Size”決定了紋理的長(zhǎng)寬值,如果我們使用的紋理本身超過(guò)了這個(gè)最大值,Unity會(huì)對(duì)其進(jìn)行縮小來(lái)滿足這個(gè)條件。這里再重復(fù)一點(diǎn),所有紋理的長(zhǎng)寬比最好是正方形,而且長(zhǎng)度值最好是2的整數(shù)冪。這是因?yàn)橛泻芏鄡?yōu)化策略只有在這種時(shí)候才可以發(fā)揮最大效用。
“Format”負(fù)責(zé)紋理使用的壓縮模式。通常選擇這種自動(dòng)模式就可以了,Unity會(huì)負(fù)責(zé)根據(jù)不同的平臺(tái)來(lái)選擇合適的壓縮模式。而對(duì)于GUI類型的紋理,我們可以根據(jù)對(duì)畫質(zhì)的要求來(lái)選擇是否進(jìn)行壓縮,具體可以參見(jiàn)之前關(guān)于畫質(zhì)的文章。
我們還可以根據(jù)不同的機(jī)器來(lái)選擇使用不同分辨率的紋理,以便讓游戲在某些老機(jī)器上也可以運(yùn)行。
利用縮放
很多時(shí)候分辨率也是造成性能下降的原因,尤其是現(xiàn)在很多國(guó)內(nèi)山寨機(jī),除了分辨率高其他硬件簡(jiǎn)直一塌糊涂,而這恰恰中了游戲性能的兩個(gè)瓶頸:過(guò)大的屏幕分辨率+糟糕的GPU。因此,我們可能需要對(duì)于特定機(jī)器進(jìn)行分辨率的放縮。當(dāng)然,這樣會(huì)造成游戲效果的下降,但性能和畫面之間永遠(yuǎn)是個(gè)需要權(quán)衡的話題。
在Unity中設(shè)置屏幕分辨率可以直接調(diào)用Screen.SetResolution。實(shí)際使用中可能會(huì)遇到一些情況,雨松MOMO有一篇文章講了這種技術(shù),可以去看看。
?
寫在最后
這篇文章是總結(jié)性質(zhì)的,因此對(duì)每種技術(shù)都沒(méi)有進(jìn)行非常詳細(xì)的解釋。強(qiáng)烈建議大家閱讀文章開(kāi)頭給出的各種鏈接,寫得都很好。
轉(zhuǎn)載于:https://www.cnblogs.com/xiaowangba/p/6314656.html
總結(jié)
以上是生活随笔為你收集整理的【Unity技巧】Unity中的优化技术的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 中国联通委任陈忠岳为总裁!
- 下一篇: 都工作两年了,还不知道浮点数如何转二进制