日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

unity3d优化收集摘录

發(fā)布時(shí)間:2023/12/8 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 unity3d优化收集摘录 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

  Unity3D 對(duì)于移動(dòng)平臺(tái)的支持無(wú)可厚非,但是也有時(shí)候用Unity3D 開發(fā)出來(lái)的應(yīng)用、游戲在移動(dòng)終端上的運(yùn)行有著明顯的效率問(wèn)題,比如卡、畫質(zhì)等各種問(wèn)題。

  影響因素:

  • Drawcall 值過(guò)大,所需要的 GPU 的處理性能較高,從而導(dǎo)致CPU的計(jì)算時(shí)間過(guò)長(zhǎng),于是就卡了。
  • 點(diǎn)、面過(guò)多,GPU 根據(jù)不同面的效果展開計(jì)算,并且CPU計(jì)算的數(shù)據(jù)也多,所以效果出來(lái)了。
  •   優(yōu)化方式:

  • 對(duì)于模型:Mesh 合并,有個(gè)不錯(cuò)的插件(DrawCall Minimizer???--->??直接上Asset Store 下載即可,免費(fèi)的,而且有文檔,很容易上手)。
  • 對(duì)于UI:?盡量避免使用Unity3D自帶的 GUI 換用 NGUI或者EZGUI,因?yàn)檫@兩個(gè)UI插件對(duì)于UI中的圖片處理是將UI圖片放置在一個(gè) Atlas 中,一個(gè) Atlas 對(duì)應(yīng)一個(gè)Drawcall。
  • 對(duì)于燈光:可以使用 Unity3D 自帶的??Lightmapping?插件來(lái)烘焙場(chǎng)景中的燈光效果到物體材質(zhì)上。
  • 對(duì)于場(chǎng)景:可以使用 Unity3D 自帶的 Occlusion?Culling 插件把靜止不動(dòng)的場(chǎng)景元素烘焙出來(lái)。
  • 對(duì)于特效:盡量把材質(zhì)紋理合并。  
  •   在屏幕上渲染物體,引擎需要發(fā)出一個(gè)繪制調(diào)用來(lái)訪問(wèn)圖形API(iOS系統(tǒng)中為OpenGL ES)。每個(gè)繪制調(diào)用需要進(jìn)行大量的工作來(lái)訪問(wèn)圖形API,從而導(dǎo)致了CPU方面顯著的性能開銷。Unity在運(yùn)行時(shí)可以將一些物體進(jìn)行合并,從而用一個(gè)繪制調(diào)用來(lái)渲染他們。這一操作,我們稱之為“批處理”。一般來(lái)說(shuō),Unity批處理的物體越多,你就會(huì)得到越好的渲染性能。?   Unity中內(nèi)建的批處理機(jī)制所達(dá)到的效果要明顯強(qiáng)于使用幾何建模工具(或使用Standard Assets包中的CombineChildren腳本)的批處理效果。這是因?yàn)?#xff0c;Unity引擎的批處理操作是在物體的可視裁剪操作之后進(jìn)行的。Unity先對(duì)每個(gè)物體進(jìn)行裁剪,然后再進(jìn)行批處理,這樣可以使渲染的幾何總量在批處理前后保持不變。但是,使用幾何建模工具來(lái)拼合物體,會(huì)妨礙引擎對(duì)其進(jìn)行有效的裁剪操作,從而導(dǎo)致引擎需要渲染更多的幾何面片。   材質(zhì)。只有擁有相同材質(zhì)的物體才可以進(jìn)行批處理。因此,如果你想要得到良好的批處理效果,你需要在程序中盡可能地復(fù)用材質(zhì)和物體。如果你的兩個(gè)材質(zhì)僅僅是紋理不同,那么你可以通過(guò)紋理拼合操作來(lái)將這兩張紋理拼合成一張大的紋理。一旦紋理拼合在一起,你就可以使用這個(gè)單一材質(zhì)來(lái)替代之前的兩個(gè)材質(zhì)了。如果你需要通過(guò)腳本來(lái)訪問(wèn)復(fù)用材質(zhì)屬性,那么值得注意的是改變Renderer.material將會(huì)造成一份材質(zhì)的拷貝。因此,你應(yīng)該使用Renderer.sharedMaterial來(lái)保證材質(zhì)的共享狀態(tài)。?   動(dòng)態(tài)批處理。如果動(dòng)態(tài)物體共用著相同的材質(zhì),那么Unity會(huì)自動(dòng)對(duì)這些物體進(jìn)行批處理。動(dòng)態(tài)批處理操作是自動(dòng)完成的,并不需要你進(jìn)行額外的操作。
  • 批處理動(dòng)態(tài)物體需要在每個(gè)頂點(diǎn)上進(jìn)行一定的開銷,所以動(dòng)態(tài)批處理僅支持小于900頂點(diǎn)的網(wǎng)格物體。?
  • 如果你的著色器使用頂點(diǎn)位置,法線和UV值三種屬性,那么你只能批處理300頂點(diǎn)以下的物體;如果你的著色器需要使用頂點(diǎn)位置,法線,UV0,UV1和切向量,那你只能批處理180頂點(diǎn)以下的物體。請(qǐng)注意:屬性數(shù)量的限制可能會(huì)在將來(lái)進(jìn)行改變。
  • 不要使用縮放尺度(scale)。分別擁有縮放尺度(1,1,1)和(2,2,2)的兩個(gè)物體將不會(huì)進(jìn)行批處理。
  • 統(tǒng)一縮放尺度的物體不會(huì)與非統(tǒng)一縮放尺度的物體進(jìn)行批處理。使用縮放尺度(1,1,1)和 (1,2,1)的兩個(gè)物體將不會(huì)進(jìn)行批處理,但是使用縮放尺度(1,2,1)和(1,3,1)的兩個(gè)物體將可以進(jìn)行批處理。
  • 使用不同材質(zhì)的實(shí)例化物體(instance)將會(huì)導(dǎo)致批處理失敗。
  • 擁有l(wèi)ightmap的物體含有額外(隱藏)的材質(zhì)屬性,比如:lightmap的偏移和縮放系數(shù)等。所以,擁有l(wèi)ightmap的物體將不會(huì)進(jìn)行批處理(除非他們指向lightmap的同一部分)。
  • 多通道的shader會(huì)妨礙批處理操作。比如,幾乎unity中所有的著色器在前向渲染中都支持多個(gè)光源,并為它們有效地開辟多個(gè)通道。
  • 預(yù)設(shè)體的實(shí)例會(huì)自動(dòng)地使用相同的網(wǎng)格模型和材質(zhì)。
  •   靜態(tài)批處理。相對(duì)而言,靜態(tài)批處理操作允許引擎對(duì)任意大小的幾何物體進(jìn)行批處理操作來(lái)降低繪制調(diào)用。因此,靜態(tài)批處理比動(dòng)態(tài)批處理更加有效,你應(yīng)該盡量低使用它,因?yàn)樗枰俚腃PU開銷。為了更好地使用靜態(tài)批處理,你需要明確指出哪些物體是靜止的,并且在游戲中永遠(yuǎn)不會(huì)移動(dòng)、旋轉(zhuǎn)和縮放。想完成這一步,你只需要在檢測(cè)器(Inspector)中將Static復(fù)選框打勾即可。使用靜態(tài)批處理操作需要額外的內(nèi)存開銷來(lái)儲(chǔ)存合并后的幾何數(shù)據(jù)。在靜態(tài)批處理之前,如果一些物體共用了同樣的幾何數(shù)據(jù),那么引擎會(huì)在編輯以及運(yùn)行狀態(tài)對(duì)每個(gè)物體創(chuàng)建一個(gè)幾何數(shù)據(jù)的備份。這并不總是一個(gè)好的想法,因?yàn)橛袝r(shí)候,你將不得不犧牲一點(diǎn)渲染性能來(lái)防止一些物體的靜態(tài)批處理,從而保持較少的內(nèi)存開銷。比如,將濃密森里中樹設(shè)為Static,會(huì)導(dǎo)致嚴(yán)重的內(nèi)存開銷。靜態(tài)批處理目前只支持Unity iOS Advanced。
    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    Unity(或者說(shuō)基本所有圖形引擎)生成一幀畫面的處理過(guò)程大致可以這樣簡(jiǎn)化描述:引擎首先經(jīng)過(guò)簡(jiǎn)單的可見性測(cè)試,確定攝像機(jī)可以看到的物體,然后把這些物體的頂點(diǎn)(包括本地位置、法線、UV等),索引(頂點(diǎn)如何組成三角形),變換(就是物體的位置、旋轉(zhuǎn)、縮放、以及攝像機(jī)位置等),相關(guān)光源,紋理,渲染方式(由材質(zhì)/Shader決定)等數(shù)據(jù)準(zhǔn)備好,然后通知圖形API——或者就簡(jiǎn)單地看作是通知GPU——開始繪制,GPU基于這些數(shù)據(jù),經(jīng)過(guò)一系列運(yùn)算,在屏幕上畫出成千上萬(wàn)的三角形,最終構(gòu)成一幅圖像。

    在Unity中,每次引擎準(zhǔn)備數(shù)據(jù)并通知GPU的過(guò)程稱為一次Draw Call。這一過(guò)程是逐個(gè)物體進(jìn)行的,對(duì)于每個(gè)物體,不只GPU的渲染,引擎重新設(shè)置材質(zhì)/Shader也是一項(xiàng)非常耗時(shí)的操作。因此每幀的Draw Call次數(shù)是一項(xiàng)非常重要的性能指標(biāo),對(duì)于iOS來(lái)說(shuō)應(yīng)盡量控制在20次以內(nèi),這個(gè)值可以在編輯器的Statistic窗口看到。

    Unity內(nèi)置了Draw Call Batching技術(shù),從名字就可以看出,它的主要目標(biāo)就是在一次Draw Call中批量處理多個(gè)物體。只要物體的變換和材質(zhì)相同,GPU就可以按完全相同的方式進(jìn)行處理,即可以把它們放在一個(gè)Draw Call中。Draw Call Batching技術(shù)的核心就是在可見性測(cè)試之后,檢查所有要繪制的物體的材質(zhì),把相同材質(zhì)的分為一組(一個(gè)Batch),然后把它們組合成一個(gè)物體(統(tǒng)一變換),這樣就可以在一個(gè)Draw Call中處理多個(gè)物體了(實(shí)際上是組合后的一個(gè)物體)。

    但Draw Call Batching存在一個(gè)缺陷,就是它需要把一個(gè)Batch中的所有物體組合到一起,相當(dāng)于創(chuàng)建了一個(gè)與這些物體加起來(lái)一樣大的物體,與此同時(shí)就需要分配相應(yīng)大小的內(nèi)存。這不僅會(huì)消耗更多內(nèi)存,還需要消耗CPU時(shí)間。特別是對(duì)于移動(dòng)的物體,每一幀都得重新進(jìn)行組合,這就需要進(jìn)行一些權(quán)衡,否則得不償失。但對(duì)于靜止不動(dòng)的物體來(lái)說(shuō),只需要進(jìn)行一次組合,之后就可以一直使用,效率要高得多。

    Unity提供了Dynamic Batching和Static Batching兩種方式。Dynamic Batching是完全自動(dòng)進(jìn)行的,不需要也無(wú)法進(jìn)行任何干預(yù),對(duì)于頂點(diǎn)數(shù)在300以內(nèi)的可移動(dòng)物體,只要使用相同的材質(zhì),就會(huì)組成Batch。Static Batching則需要把靜止的物體標(biāo)記為Static,然后無(wú)論大小,都會(huì)組成Batch。如前文所說(shuō),Static Batching顯然比Dynamic Batching要高效得多,于是,Static Batching功能是收費(fèi)的……

    要有效利用Draw Call Batching,首先是盡量減少場(chǎng)景中使用的材質(zhì)數(shù)量,即盡量共享材質(zhì),對(duì)于僅紋理不同的材質(zhì)可以把紋理組合到一張更大的紋理中(稱為Texture Atlasing)。然后是把不會(huì)移動(dòng)的物體標(biāo)記為Static。此外還可以通過(guò)CombineChildren腳本(Standard Assets/Scripts/Unity Scripts/CombineChildren)手動(dòng)把物體組合在一起,但這個(gè)腳本會(huì)影響可見性測(cè)試,因?yàn)榻M合在一起的物體始終會(huì)被看作一個(gè)物體,從而會(huì)增加GPU要處理的幾何體數(shù)量,因此要小心使用。

    對(duì)于復(fù)雜的靜態(tài)場(chǎng)景,還可以考慮自行設(shè)計(jì)遮擋剔除算法,減少可見的物體數(shù)量同時(shí)也可以減少Draw Call。

    總之,理解Draw Call和Draw Call Batching原理,根據(jù)場(chǎng)景特點(diǎn)設(shè)計(jì)相應(yīng)的方案來(lái)盡量減少Draw Call次數(shù)才是王道,其它方面亦然。

    -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    寫在前面


    這一篇是在Digital Tutors的一個(gè)系列教程的基礎(chǔ)上總結(jié)擴(kuò)展而得的~Digital Tutors是一個(gè)非常棒的教程網(wǎng)站,包含了多媒體領(lǐng)域很多方面的資料,非常酷!除此之外,還參考了Unity Cookie中的一個(gè)教程。還有很多其他參考在下面的鏈接中。


    這篇文章旨在簡(jiǎn)要地說(shuō)明一下常見的各種優(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ù)雜的腳本或者物理模擬

    • 頂點(diǎn)處理
      • 過(guò)多的頂點(diǎn)
      • 過(guò)多的逐頂點(diǎn)計(jì)算

    • 像素(Fragment)處理
      • 過(guò)多的fragment,overdraws
      • 過(guò)多的逐像素計(jì)算

    • 帶寬
      • 尺寸很大且未壓縮的紋理
      • 分辨率過(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í)候使用的是一個(gè)material以及一個(gè)shader,但渲染樹的時(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ù)開始,進(jìn)行Vertex Shader、Fragment Shader等一系列工作,最后輸出屏幕上的每個(gè)像素。因此它的性能瓶頸可能和需要處理的頂點(diǎn)數(shù)目的、屏幕分辨率、顯存等因素有關(guān)。總體包含了頂點(diǎ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)化
      • 控制繪制順序
      • 警惕透明物體
      • 減少實(shí)時(shí)光照

    • CPU優(yōu)化
      • 減少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游戲制作都由模型制作開始。而在建模時(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)的,即我們看見的一個(gè)點(diǎn)就是一個(gè)。而Unity是站在GPU的角度上,去計(jì)算頂點(diǎn)數(shù)目的。而在GPU看來(lái),看起來(lái)是一個(gè)的很有可能它要分開處理,從而就產(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è)系列文章大家可以看看(需翻墻):
    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)非常良好,再嘗試開啟一些特效功能。

    減少實(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的很常見的一種優(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)行模擬。具體可以參見之前的文章。


    CPU優(yōu)化

    減少Draw Calls


    批處理(Batching)


    這方面的優(yōu)化教程想必是最多的了。最常見的就是通過(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ì)批處理。

    上述除了最常見的由于縮放導(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ù)樹模型的森林中使用靜態(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)一縮放。

    • 對(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ù)(最常見的就是頂點(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è)例子是,還是之前的森林,所有的樹使用了同一種材質(zhì),我們希望它們可以通過(guò)動(dòng)態(tài)批處理來(lái)實(shí)現(xiàn),但不同樹的顏色可能不同。這時(shí)我么可以利用網(wǎng)格的頂點(diǎn)數(shù)據(jù)來(lái)調(diào)整。具體方法,可以參見后面會(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ō)明可以參見文檔。其中和優(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)行壓縮,具體可以參見之前關(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)烈建議大家閱讀文章開頭給出的各種鏈接,寫得都很好。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    剛開始寫這篇文章的時(shí)候選了一個(gè)很土的題目。。。《Unity3D優(yōu)化全解析》。因?yàn)檫@是一篇臨時(shí)起意才寫的文章,而且陳述的都是既有的事實(shí),因而給自己“文(dou)學(xué)(bi)”加工留下的余地就少了很多。但又覺(jué)得這塊是不得不提的一個(gè)地方,平時(shí)見到很多人對(duì)此處也給予了忽略了事,需要時(shí)才去網(wǎng)上扒一些只言片語(yǔ)的資料。也恰逢年前,尋思著周末認(rèn)真寫點(diǎn)東西遇到節(jié)假日沒(méi)準(zhǔn)也沒(méi)什么人讀,所以索性就寫了這篇臨時(shí)的文章。題目很土,因?yàn)橛昧酥赶蛐院苊鞔_的“Unity3D”,讓人少了遐(瞎)想的空間,同時(shí)用了“高大全”這樣的構(gòu)詞法,也讓匹夫有成為眾矢之的的可能。。。所以最后還是改成了現(xiàn)在各位看到的題目。話不多說(shuō),下面就開始正文~正所謂“草蛇灰線,伏脈千里”。那咱們首先~~~~~~

    看看優(yōu)化需要從哪里著手?

    匹夫印象里遇到的童靴,提Unity3D項(xiàng)目?jī)?yōu)化則必提DrawCall,這自然沒(méi)錯(cuò),但也有很不好影響。因?yàn)檫@會(huì)給人一個(gè)錯(cuò)誤的認(rèn)識(shí):所謂的優(yōu)化就是把DrawCall弄的比較低就對(duì)了。

    對(duì)優(yōu)化有這種第一印象的人不在少數(shù),drawcall的確是一個(gè)很重要的指標(biāo),但絕非全部。為了讓各位和匹夫能達(dá)成盡可能多的共識(shí),匹夫首先介紹一下本文可能會(huì)涉及到的幾個(gè)概念,之后會(huì)提出優(yōu)化所涉及的三大方面:

    • drawcall是啥?其實(shí)就是對(duì)底層圖形程序(比如:OpenGL ES)接口的調(diào)用,以在屏幕上畫出東西。所以,是誰(shuí)去調(diào)用這些接口呢?CPU。
    • fragment是啥?經(jīng)常有人說(shuō)vf啥的,vertex我們都知道是頂點(diǎn),那fragment是啥呢?說(shuō)它之前需要先說(shuō)一下像素,像素各位應(yīng)該都知道吧?像素是構(gòu)成數(shù)碼影像的基本單元呀。那fragment呢?是有可能成為像素的東西。啥叫有可能?就是最終會(huì)不會(huì)被畫出來(lái)不一定,是潛在的像素。這會(huì)涉及到誰(shuí)呢?GPU。
    • batching是啥?都知道批處理是干嘛的吧?沒(méi)錯(cuò),將批處理之前需要很多次調(diào)用(drawcall)的物體合并,之后只需要調(diào)用一次底層圖形程序的接口就行。聽上去這簡(jiǎn)直就是優(yōu)化的終極方案啊!但是,理想是美好的,世界是殘酷的,一些不足之后我們?cè)偌?xì)聊。
    • 內(nèi)存的分配:記住,除了Unity3D自己的內(nèi)存損耗。我們可是還帶著Mono呢啊,還有托管的那一套東西呢。更別說(shuō)你一激動(dòng),又引入了自己的幾個(gè)dll。這些都是內(nèi)存開銷上需要考慮到的。

    好啦,文中的幾個(gè)概念提前講清楚了,其實(shí)各位也能看的出來(lái)匹夫接下來(lái)要說(shuō)的匹夫關(guān)注的優(yōu)化時(shí)需要注意的方面:

    • CPU方面
    • GPU方面
    • 內(nèi)存方面

    所以,這篇文章也會(huì)按照CPU—->GPU—->內(nèi)存的順序進(jìn)行。

    CPU的方面的優(yōu)化:

    上文中說(shuō)了,drawcall影響的是CPU的效率,而且也是最知名的一個(gè)優(yōu)化點(diǎn)。但是除了drawcall之外,還有哪些因素也會(huì)影響到CPU的效率呢?讓我們一一列出暫時(shí)能想得到的:

    • DrawCalls
    • 物理組件(Physics)
    • GC(什么?GC不是處理內(nèi)存問(wèn)題的嘛?匹夫你不要騙我啊!不過(guò),匹夫也要提醒一句,GC是用來(lái)處理內(nèi)存的,但是是誰(shuí)使用GC去處理內(nèi)存的呢?)
    • 當(dāng)然,還有代碼質(zhì)量

    DrawCalls:

    前面說(shuō)過(guò)了,DrawCall是CPU調(diào)用底層圖形接口。比如有上千個(gè)物體,每一個(gè)的渲染都需要去調(diào)用一次底層接口,而每一次的調(diào)用CPU都需要做很多工作,那么CPU必然不堪重負(fù)。但是對(duì)于GPU來(lái)說(shuō),圖形處理的工作量是一樣的。所以對(duì)DrawCall的優(yōu)化,主要就是為了盡量解放CPU在調(diào)用圖形接口上的開銷。所以針對(duì)drawcall我們主要的思路就是每個(gè)物體盡量減少渲染次數(shù),多個(gè)物體最好一起渲染。所以,按照這個(gè)思路就有了以下幾個(gè)方案:

  • 使用Draw Call Batching,也就是描繪調(diào)用批處理。Unity在運(yùn)行時(shí)可以將一些物體進(jìn)行合并,從而用一個(gè)描繪調(diào)用來(lái)渲染他們。具體下面會(huì)介紹。
  • 通過(guò)把紋理打包成圖集來(lái)盡量減少材質(zhì)的使用。
  • 盡量少的使用反光啦,陰影啦之類的,因?yàn)槟菚?huì)使物體多次渲染。
  • Draw Call Batching

    首先我們要先理解為何2個(gè)沒(méi)有使用相同材質(zhì)的物體即使使用批處理,也無(wú)法實(shí)現(xiàn)Draw Call數(shù)量的下降和性能上的提升。

    因?yàn)楸弧芭幚怼钡?個(gè)物體的網(wǎng)格模型需要使用相同材質(zhì)的目的,在于其紋理是相同的,這樣才可以實(shí)現(xiàn)同時(shí)渲染的目的。因而保證材質(zhì)相同,是為了保證被渲染的紋理相同。

    因此,為了將2個(gè)紋理不同的材質(zhì)合二為一,我們就需要進(jìn)行上面列出的第二步,將紋理打包成圖集。具體到合二為一這種情況,就是將2個(gè)紋理合成一個(gè)紋理。這樣我們就可以只用一個(gè)材質(zhì)來(lái)代替之前的2個(gè)材質(zhì)了。

    而Draw Call Batching本身,也還會(huì)細(xì)分為2種。

    Static Batching 靜態(tài)批處理

    看名字,猜使用的情景。

    靜態(tài)?那就是不動(dòng)的咯。還有呢?額,聽上去狀態(tài)也不會(huì)改變,沒(méi)有“生命”,比如山山石石,樓房校舍啥的。那和什么比較類似呢?嗯,聰明的各位一定覺(jué)得和場(chǎng)景的屬性很像吧!所以我們的場(chǎng)景似乎就可以采用這種方式來(lái)減少draw call了。

    那么寫個(gè)定義:只要這些物體不移動(dòng),并且擁有相同的材質(zhì),靜態(tài)批處理就允許引擎對(duì)任意大小的幾何物體進(jìn)行批處理操作來(lái)降低描繪調(diào)用。

    那要如何使用靜態(tài)批來(lái)減少Draw Call呢?你只需要明確指出哪些物體是靜止的,并且在游戲中永遠(yuǎn)不會(huì)移動(dòng)、旋轉(zhuǎn)和縮放。想完成這一步,你只需要在檢測(cè)器(Inspector)中將Static復(fù)選框打勾即可,如下圖所示:

    至于效果如何呢?

    舉個(gè)例子:新建4個(gè)物體,分別是Cube,Sphere, Capsule, Cylinder,它們有不同的網(wǎng)格模型,但是也有相同的材質(zhì)(Default-Diffuse)。

    首先,我們不指定它們是static的。Draw Call的次數(shù)是4次,如圖:

    我們現(xiàn)在將它們4個(gè)物體都設(shè)為static,在來(lái)運(yùn)行一下:

    如圖,Draw Call的次數(shù)變成了1,而Saved by batching的次數(shù)變成了3。

    靜態(tài)批處理的好處很多,其中之一就是與下面要說(shuō)的動(dòng)態(tài)批處理相比,約束要少很多。所以一般推薦的是draw call的靜態(tài)批處理來(lái)減少draw call的次數(shù)。那么接下來(lái),我們就繼續(xù)聊聊draw call的動(dòng)態(tài)批處理。

    Dynamic Batching 動(dòng)態(tài)批處理

    有陰就有陽(yáng),有靜就有動(dòng),所以聊完了靜態(tài)批處理,肯定跟著就要說(shuō)說(shuō)動(dòng)態(tài)批處理了。首先要明確一點(diǎn),Unity3D的draw call動(dòng)態(tài)批處理機(jī)制是引擎自動(dòng)進(jìn)行的,無(wú)需像靜態(tài)批處理那樣手動(dòng)設(shè)置static。我們舉一個(gè)動(dòng)態(tài)實(shí)例化prefab的例子,如果動(dòng)態(tài)物體共享相同的材質(zhì),則引擎會(huì)自動(dòng)對(duì)draw call優(yōu)化,也就是使用批處理。首先,我們將一個(gè)cube做成prefab,然后再實(shí)例化500次,看看draw call的數(shù)量。

    12345for(int i = 0; i < 500; i++){????GameObject cube;????cube = GameObject.Instantiate(prefab) as GameObject;}

    draw call的數(shù)量:

    可以看到draw call的數(shù)量為1,而 saved by batching的數(shù)量是499。而這個(gè)過(guò)程中,我們除了實(shí)例化創(chuàng)建物體之外什么都沒(méi)做。不錯(cuò),unity3d引擎為我們自動(dòng)處理了這種情況。

    但是有很多童靴也遇到這種情況,就是我也是從prefab實(shí)例化創(chuàng)建的物體,為何我的draw call依然很高呢?這就是匹夫上文說(shuō)的,draw call的動(dòng)態(tài)批處理存在著很多約束。下面匹夫就演示一下,針對(duì)cube這樣一個(gè)簡(jiǎn)單的物體的創(chuàng)建,如果稍有不慎就會(huì)造成draw call飛漲的情況吧。

    我們同樣是創(chuàng)建500個(gè)物體,不同的是其中的100個(gè)物體,每個(gè)物體的大小都不同,也就是Scale不同。

    1 2 3 4 5 6 7 8 9 for(int i = 0; i < 500; i++) { ????GameObject cube; ????cube = GameObject.Instantiate(prefab) as GameObject; ????if(i / 100 == 0) ????{ ????????cube.transform.localScale = new Vector3(2 + i, 2 + i, 2 + i); ????} }

    draw call的數(shù)量:

    我們看到draw call的數(shù)量上升到了101次,而saved by batching的數(shù)量也下降到了399。各位看官可以看到,僅僅是一個(gè)簡(jiǎn)單的cube的創(chuàng)建,如果scale不同,竟然也不會(huì)去做批處理優(yōu)化。這僅僅是動(dòng)態(tài)批處理機(jī)制的一種約束,那我們總結(jié)一下動(dòng)態(tài)批處理的約束,各位也許也能從中找到為何動(dòng)態(tài)批處理在自己的項(xiàng)目中不起作用的原因:

  • 批處理動(dòng)態(tài)物體需要在每個(gè)頂點(diǎn)上進(jìn)行一定的開銷,所以動(dòng)態(tài)批處理僅支持小于900頂點(diǎn)的網(wǎng)格物體。
  • 如果你的著色器使用頂點(diǎn)位置,法線和UV值三種屬性,那么你只能批處理300頂點(diǎn)以下的物體;如果你的著色器需要使用頂點(diǎn)位置,法線,UV0,UV1和切向量,那你只能批處理180頂點(diǎn)以下的物體。
  • 不要使用縮放。分別擁有縮放大小(1,1,1) 和(2,2,2)的兩個(gè)物體將不會(huì)進(jìn)行批處理。
  • 統(tǒng)一縮放的物體不會(huì)與非統(tǒng)一縮放的物體進(jìn)行批處理。
  • 使用縮放尺度(1,1,1) 和 (1,2,1)的兩個(gè)物體將不會(huì)進(jìn)行批處理,但是使用縮放尺度(1,2,1) 和(1,3,1)的兩個(gè)物體將可以進(jìn)行批處理。
  • 使用不同材質(zhì)的實(shí)例化物體(instance)將會(huì)導(dǎo)致批處理失敗。
  • 擁有l(wèi)ightmap的物體含有額外(隱藏)的材質(zhì)屬性,比如:lightmap的偏移和縮放系數(shù)等。所以,擁有l(wèi)ightmap的物體將不會(huì)進(jìn)行批處理(除非他們指向lightmap的同一部分)。
  • 多通道的shader會(huì)妨礙批處理操作。比如,幾乎unity中所有的著色器在前向渲染中都支持多個(gè)光源,并為它們有效地開辟多個(gè)通道。
  • 預(yù)設(shè)體的實(shí)例會(huì)自動(dòng)地使用相同的網(wǎng)格模型和材質(zhì)。
  • 所以,盡量使用靜態(tài)的批處理。

    物理組件

    曾幾何時(shí),匹夫在做一個(gè)策略類游戲的時(shí)候需要在單元格上排兵布陣,而要偵測(cè)到哪個(gè)兵站在哪個(gè)格子匹夫選擇使用了射線,由于士兵單位很多,而且為了精確每一幀都會(huì)執(zhí)行檢測(cè),那時(shí)候CPU的負(fù)擔(dān)叫一個(gè)慘不忍睹。后來(lái)匹夫果斷放棄了這種做法,并且對(duì)物理組件產(chǎn)生了心理的陰影。

    這里匹夫只提2點(diǎn)匹夫感覺(jué)比較重要的優(yōu)化措施:

    1.設(shè)置一個(gè)合適的Fixed Timestep。設(shè)置的位置如圖:

    那何謂“合適”呢?首先我們要搞明白Fixed Timestep和物理組件的關(guān)系。物理組件,或者說(shuō)游戲中模擬各種物理效果的組件,最重要的是什么呢?計(jì)算啊。對(duì),需要通過(guò)計(jì)算才能將真實(shí)的物理效果展現(xiàn)在虛擬的游戲中。那么Fixed Timestep這貨就是和物理計(jì)算有關(guān)的啦。所以,若計(jì)算的頻率太高,自然會(huì)影響到CPU的開銷。同時(shí),若計(jì)算頻率達(dá)不到游戲設(shè)計(jì)時(shí)的要求,有會(huì)影響到功能的實(shí)現(xiàn),所以如何抉擇需要各位具體分析,選擇一個(gè)合適的值。

    2.就是不要使用網(wǎng)格碰撞器(mesh collider):為啥?因?yàn)閷?shí)在是太復(fù)雜了。網(wǎng)格碰撞器利用一個(gè)網(wǎng)格資源并在其上構(gòu)建碰撞器。對(duì)于復(fù)雜網(wǎng)狀模型上的碰撞檢測(cè),它要比應(yīng)用原型碰撞器精確的多。標(biāo)記為凸起的(Convex )的網(wǎng)格碰撞器才能夠和其他網(wǎng)格碰撞器發(fā)生碰撞。各位上網(wǎng)搜一下mesh collider的圖片,自然就會(huì)明白了。我們的手機(jī)游戲自然無(wú)需這種性價(jià)比不高的東西。

    當(dāng)然,從性能優(yōu)化的角度考慮,物理組件能少用還是少用為好。

    處理內(nèi)存,卻讓CPU受傷的GC

    在CPU的部分聊GC,感覺(jué)是不是怪怪的?其實(shí)小匹夫不這么覺(jué)得,雖然GC是用來(lái)處理內(nèi)存的,但的確增加的是CPU的開銷。因此它的確能達(dá)到釋放內(nèi)存的效果,但代價(jià)更加沉重,會(huì)加重CPU的負(fù)擔(dān),因此對(duì)于GC的優(yōu)化目標(biāo)就是盡量少的觸發(fā)GC。

    首先我們要明確所謂的GC是Mono運(yùn)行時(shí)的機(jī)制,而非Unity3D游戲引擎的機(jī)制,所以GC也主要是針對(duì)Mono的對(duì)象來(lái)說(shuō)的,而它管理的也是Mono的托管堆。 搞清楚這一點(diǎn),你也就明白了GC不是用來(lái)處理引擎的assets(紋理啦,音效啦等等)的內(nèi)存釋放的,因?yàn)閁3D引擎也有自己的內(nèi)存堆而不是和Mono一起使用所謂的托管堆。

    其次我們要搞清楚什么東西會(huì)被分配到托管堆上?不錯(cuò)咯,就是引用類型咯。比如類的實(shí)例,字符串,數(shù)組等等。而作為int,float,包括結(jié)構(gòu)體struct其實(shí)都是值類型,它們會(huì)被分配在堆棧上而非堆上。所以我們關(guān)注的對(duì)象無(wú)外乎就是類實(shí)例,字符串,數(shù)組這些了。

    那么GC什么時(shí)候會(huì)觸發(fā)呢?兩種情況:

  • 首先當(dāng)然是我們的堆的內(nèi)存不足時(shí),會(huì)自動(dòng)調(diào)用GC。
  • 其次呢,作為編程人員,我們自己也可以手動(dòng)的調(diào)用GC。
  • 所以為了達(dá)到優(yōu)化CPU的目的,我們就不能頻繁的觸發(fā)GC。而上文也說(shuō)了GC處理的是托管堆,而不是Unity3D引擎的那些資源,所以GC的優(yōu)化說(shuō)白了也就是代碼的優(yōu)化。那么匹夫覺(jué)得有以下幾點(diǎn)是需要注意的:

  • 字符串連接的處理。因?yàn)閷蓚€(gè)字符串連接的過(guò)程,其實(shí)是生成一個(gè)新的字符串的過(guò)程。而之前的舊的字符串自然而然就成為了垃圾。而作為引用類型的字符串,其空間是在堆上分配的,被棄置的舊的字符串的空間會(huì)被GC當(dāng)做垃圾回收。
  • 盡量不要使用foreach,而是使用for。foreach其實(shí)會(huì)涉及到迭代器的使用,而據(jù)傳說(shuō)每一次循環(huán)所產(chǎn)生的迭代器會(huì)帶來(lái)24 Bytes的垃圾。那么循環(huán)10次就是240Bytes。
  • 不要直接訪問(wèn)gameobject的tag屬性。比如if (go.tag == “human”)最好換成if (go.CompareTag (“human”))。因?yàn)樵L問(wèn)物體的tag屬性會(huì)在堆上額外的分配空間。如果在循環(huán)中這么處理,留下的垃圾就可想而知了。
  • 使用“池”,以實(shí)現(xiàn)空間的重復(fù)利用。
  • 最好不用LINQ的命令,因?yàn)樗鼈儠?huì)分配臨時(shí)的空間,同樣也是GC收集的目標(biāo)。而且我很討厭LINQ的一點(diǎn)就是它有可能在某些情況下無(wú)法很好的進(jìn)行AOT編譯。比如“OrderBy”會(huì)生成內(nèi)部的泛型類“OrderedEnumerable”。這在AOT編譯時(shí)是無(wú)法進(jìn)行的,因?yàn)樗皇窃贠rderBy的方法中才使用。所以如果你使用了OrderBy,那么在IOS平臺(tái)上也許會(huì)報(bào)錯(cuò)。
  • 代碼?腳本?

    聊到代碼這個(gè)話題,也許有人會(huì)覺(jué)得匹夫多此一舉。因?yàn)榇a質(zhì)量因人而異,很難像上面提到的幾點(diǎn),有一個(gè)明確的評(píng)判標(biāo)準(zhǔn)。也是,公寫公有理,婆寫婆有理。但是匹夫這里要提到的所謂代碼質(zhì)量是基于一個(gè)前提的:Unity3D是用C++寫的,而我們的代碼是用C#作為腳本來(lái)寫的,那么問(wèn)題就來(lái)了~腳本和底層的交互開銷是否需要考慮呢?也就是說(shuō),我們用Unity3D寫游戲的“游戲腳本語(yǔ)言”,也就是C#是由mono運(yùn)行時(shí)托管的。而功能是底層引擎的C++實(shí)現(xiàn)的,“游戲腳本”中的功能實(shí)現(xiàn)都離不開對(duì)底層代碼的調(diào)用。那么這部分的開銷,我們應(yīng)該如何優(yōu)化呢?

    1.以物體的Transform組件為例,我們應(yīng)該只訪問(wèn)一次,之后就將它的引用保留,而非每次使用都去訪問(wèn)。這里有人做過(guò)一個(gè)小實(shí)驗(yàn),就是對(duì)比通過(guò)方法GetComponent<Transform>()獲取Transform組件, 通過(guò)MonoBehavor的transform屬性去取,以及保留引用之后再去訪問(wèn)所需要的時(shí)間:

    • GetComponent = 619ms
    • Monobehaviour = 60ms
    • CachedMB = 8ms
    • Manual Cache = 3ms

    2.如上所述,最好不要頻繁使用GetComponent,尤其是在循環(huán)中。

    3.善于使用OnBecameVisible()和OnBecameVisible(),來(lái)控制物體的update()函數(shù)的執(zhí)行以減少開銷。

    4.使用內(nèi)建的數(shù)組,比如用Vector3.zero而不是new Vector(0, 0, 0);

    5.對(duì)于方法的參數(shù)的優(yōu)化:善于使用ref關(guān)鍵字。值類型的參數(shù),是通過(guò)將實(shí)參的值復(fù)制到形參,來(lái)實(shí)現(xiàn)按值傳遞到方法,也就是我們通常說(shuō)的按值傳遞。復(fù)制嘛,總會(huì)讓人感覺(jué)很笨重。比如Matrix4x4這樣比較復(fù)雜的值類型,如果直接復(fù)制一份新的,反而不如將值類型的引用傳遞給方法作為參數(shù)。

    好啦,CPU的部分匹夫覺(jué)得到此就介紹的差不多了。下面就簡(jiǎn)單聊聊其實(shí)匹夫并不是十分熟悉的部分,GPU的優(yōu)化。

    GPU的優(yōu)化

    GPU與CPU不同,所以側(cè)重點(diǎn)自然也不一樣。GPU的瓶頸主要存在在如下的方面:

  • 填充率,可以簡(jiǎn)單的理解為圖形處理單元每秒渲染的像素?cái)?shù)量。
  • 像素的復(fù)雜度,比如動(dòng)態(tài)陰影,光照,復(fù)雜的shader等等
  • 幾何體的復(fù)雜度(頂點(diǎn)數(shù)量)
  • 當(dāng)然還有GPU的顯存帶寬
  • 那么針對(duì)以上4點(diǎn),其實(shí)仔細(xì)分析我們就可以發(fā)現(xiàn),影響的GPU性能的無(wú)非就是2大方面,一方面是頂點(diǎn)數(shù)量過(guò)多,像素計(jì)算過(guò)于復(fù)雜。另一方面就是GPU的顯存帶寬。那么針鋒相對(duì)的兩方面舉措也就十分明顯了。

  • 少頂點(diǎn)數(shù)量,簡(jiǎn)化計(jì)算復(fù)雜度。
  • 縮圖片,以適應(yīng)顯存帶寬。
  • 減少繪制的數(shù)目

    那么第一個(gè)方面的優(yōu)化也就是減少頂點(diǎn)數(shù)量,簡(jiǎn)化復(fù)雜度,具體的舉措就總結(jié)如下了:

    • 保持材質(zhì)的數(shù)目盡可能少。這使得Unity更容易進(jìn)行批處理。
    • 使用紋理圖集(一張大貼圖里包含了很多子貼圖)來(lái)代替一系列單獨(dú)的小貼圖。它們可以更快地被加載,具有很少的狀態(tài)轉(zhuǎn)換,而且批處理更友好。
    • 如果使用了紋理圖集和共享材質(zhì),使用Renderer.sharedMaterial?來(lái)代替Renderer.material?。
    • 使用光照紋理(lightmap)而非實(shí)時(shí)燈光。
    • 使用LOD,好處就是對(duì)那些離得遠(yuǎn),看不清的物體的細(xì)節(jié)可以忽略。
    • 遮擋剔除(Occlusion culling)
    • 使用mobile版的shader。因?yàn)楹?jiǎn)單。

    優(yōu)化顯存帶寬

    第二個(gè)方向呢?壓縮圖片,減小顯存帶寬的壓力。

    • OpenGL ES 2.0使用ETC1格式壓縮等等,在打包設(shè)置那里都有。
    • 使用mipmap。

    MipMap

    這里匹夫要著重介紹一下MipMap到底是啥。因?yàn)橛腥苏f(shuō)過(guò)MipMap會(huì)占用內(nèi)存呀,但為何又會(huì)優(yōu)化顯存帶寬呢?那就不得不從MipMap是什么開始聊起。一張圖其實(shí)就能解決這個(gè)疑問(wèn)。

    上面是一個(gè)mipmap 如何儲(chǔ)存的例子,左邊的主圖伴有一系列逐層縮小的備份小圖

    是不是很一目了然呢?Mipmap中每一個(gè)層級(jí)的小圖都是主圖的一個(gè)特定比例的縮小細(xì)節(jié)的復(fù)制品。因?yàn)榇媪酥鲌D和它的那些縮小的復(fù)制品,所以內(nèi)存占用會(huì)比之前大。但是為何又優(yōu)化了顯存帶寬呢?因?yàn)榭梢愿鶕?jù)實(shí)際情況,選擇適合的小圖來(lái)渲染。所以,雖然會(huì)消耗一些內(nèi)存,但是為了圖片渲染的質(zhì)量(比壓縮要好),這種方式也是推薦的。

    內(nèi)存的優(yōu)化

    既然要聊Unity3D運(yùn)行時(shí)候的內(nèi)存優(yōu)化,那我們自然首先要知道Unity3D游戲引擎是如何分配內(nèi)存的。大概可以分成三大部分:

  • Unity3D內(nèi)部的內(nèi)存
  • Mono的托管內(nèi)存
  • 若干我們自己引入的DLL或者第三方DLL所需要的內(nèi)存。
  • 第3類不是我們關(guān)注的重點(diǎn),所以接下來(lái)我們會(huì)分別來(lái)看一下Unity3D內(nèi)部?jī)?nèi)存和Mono托管內(nèi)存,最后還將分析一個(gè)官網(wǎng)上Assetbundle的案例來(lái)說(shuō)明內(nèi)存的管理。

    Unity3D內(nèi)部?jī)?nèi)存

    Unity3D的內(nèi)部?jī)?nèi)存都會(huì)存放一些什么呢?各位想一想,除了用代碼來(lái)驅(qū)動(dòng)邏輯,一個(gè)游戲還需要什么呢?對(duì),各種資源。所以簡(jiǎn)單總結(jié)一下Unity3D內(nèi)部?jī)?nèi)存存放的東西吧:

    • 資源:紋理、網(wǎng)格、音頻等等
    • GameObject和各種組件。
    • 引擎內(nèi)部邏輯需要的內(nèi)存:渲染器,物理系統(tǒng),粒子系統(tǒng)等等

    Mono托管內(nèi)存

    因?yàn)槲覀兊挠螒蚰_本是用C#寫的,同時(shí)還要跨平臺(tái),所以帶著一個(gè)Mono的托管環(huán)境顯然必須的。那么Mono的托管內(nèi)存自然就不得不放到內(nèi)存的優(yōu)化范疇中進(jìn)行考慮。那么我們所說(shuō)的Mono托管內(nèi)存中存放的東西和Unity3D內(nèi)部?jī)?nèi)存中存放的東西究竟有何不同呢?其實(shí)Mono的內(nèi)存分配就是很傳統(tǒng)的運(yùn)行時(shí)內(nèi)存的分配了:

    • 值類型:int型啦,float型啦,結(jié)構(gòu)體struct啦,bool啦之類的。它們都存放在堆棧上(注意額,不是堆所以不涉及GC)。
    • 引用類型:其實(shí)可以狹義的理解為各種類的實(shí)例。比如游戲腳本中對(duì)游戲引擎各種控件的封裝。其實(shí)很好理解,C#中肯定要有對(duì)應(yīng)的類去對(duì)應(yīng)游戲引擎中的控件。那么這部分就是C#中的封裝。由于是在堆上分配,所以會(huì)涉及到GC。

    而Mono托管堆中的那些封裝的對(duì)象,除了在在Mono托管堆上分配封裝類實(shí)例化之后所需要的內(nèi)存之外,還會(huì)牽扯到其背后對(duì)應(yīng)的游戲引擎內(nèi)部控件在Unity3D內(nèi)部?jī)?nèi)存上的分配。

    舉一個(gè)例子:

    一個(gè)在.cs腳本中聲明的WWW類型的對(duì)象www,Mono會(huì)在Mono托管堆上為www分配它所需要的內(nèi)存。同時(shí),這個(gè)實(shí)例對(duì)象背后的所代表的引擎資源所需要的內(nèi)存也需要被分配。

    一個(gè)WWW實(shí)例背后的資源:

    • 壓縮的文件
    • 解壓縮所需的緩存
    • 解壓縮之后的文件

    如圖:

    那么下面就舉一個(gè)AssetBundle的例子:

    Assetbundle的內(nèi)存處理

    以下載Assetbundle為例子,聊一下內(nèi)存的分配。匹夫從官網(wǎng)的手冊(cè)上找到了一個(gè)使用Assetbundle的情景如下:

    123456789101112131415161718192021IEnumerator DownloadAndCache (){????????// Wait for the Caching system to be ready????????while (!Caching.ready)????????????yield return null;????????// Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache????????using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){????????????yield return www; //WWW是第1部分????????????if (www.error != null)????????????????throw new Exception(&quot;WWW download had an error:&quot; + www.error);????????????AssetBundle bundle = www.assetBundle;//AssetBundle是第2部分????????????if (AssetName == &quot;&quot;)????????????????Instantiate(bundle.mainAsset);//實(shí)例化是第3部分????????????else????????????????Instantiate(bundle.Load(AssetName));????????????????????// Unload the AssetBundles compressed contents to conserve memory????????????????????bundle.Unload(false);????????} // memory is freed from the web stream (www.Dispose() gets called implicitly)????}}

    內(nèi)存分配的三個(gè)部分匹夫已經(jīng)在代碼中標(biāo)識(shí)了出來(lái):

  • Web Stream:包括了壓縮的文件,解壓所需的緩存,以及解壓后的文件。
  • AssetBundle:Web Stream中的文件的映射,或者說(shuō)引用。
  • 實(shí)例化之后的對(duì)象就是引擎的各種資源文件了,會(huì)在內(nèi)存中創(chuàng)建出來(lái)。
  • 那就分別解析一下:

    1 WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)

  • 將壓縮的文件讀入內(nèi)存中
  • 創(chuàng)建解壓所需的緩存
  • 將文件解壓,解壓后的文件進(jìn)入內(nèi)存
  • 關(guān)閉掉為解壓創(chuàng)建的緩存
  • 1AssetBundle bundle = www.assetBundle;

  • AssetBundle此時(shí)相當(dāng)于一個(gè)橋梁,從Web Stream解壓后的文件到最后實(shí)例化創(chuàng)建的對(duì)象之間的橋梁。
  • 所以AssetBundle實(shí)質(zhì)上是Web Stream解壓后的文件中各個(gè)對(duì)象的映射。而非真實(shí)的對(duì)象。
  • 實(shí)際的資源還存在Web Stream中,所以此時(shí)要保留Web Stream。
  • 1 Instantiate(bundle.mainAsset);

    通過(guò)AssetBundle獲取資源,實(shí)例化對(duì)象

    最后各位可能看到了官網(wǎng)中的這個(gè)例子使用了:

    12using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){}

    這種using的用法。這種用法其實(shí)就是為了在使用完Web Stream之后,將內(nèi)存釋放掉的。因?yàn)閃WW也繼承了idispose的接口,所以可以使用using的這種用法。其實(shí)相當(dāng)于最后執(zhí)行了:

    1 2 //刪除Web Stream www.Dispose();

    OK,Web Stream被刪除掉了。那還有誰(shuí)呢?對(duì)Assetbundle。那么使用

    1 2 //刪除AssetBundle bundle.Unload(false);

    ok,寫到這里就先打住啦。寫的有點(diǎn)超了。有點(diǎn)趕也有點(diǎn)臨時(shí),日后在補(bǔ)充編輯。



    總結(jié)

    以上是生活随笔為你收集整理的unity3d优化收集摘录的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。