[Unity優化] Unity CPU性能優化 (難度3 推薦4)
原文地址:
http://www.cnblogs.com/chwen/p/4396515.html
前段時間本人轉戰unity手遊,由於作者(Chwen)之前參與端遊開發,有些端遊的經驗可以直接移植到手遊,比如項目框架架構、代碼設計、部分性能分析,而對於移動終端而言,CPU、內存、顯卡甚至電池等硬件因素,以及網絡等條件限制,對移動遊戲開發的優化帶來更大的挑戰。
這裏就以unity4.5x版本為例,對Unity的優化方案做一個總結,有些是項目遇到的,也有些是看到別人寫的不錯拿來分享,算作一個整理,後期也會持續更新。本優化從CPU、GPU和內存三個方面著手總結,這一篇先從CPU說起,整理一些針對CPU相關的優化建議。
對CPU的優化主要是從drawcall、物理組件、GC(垃圾回收)、腳本等幾個方面開展。
- Drawcall 的優化
- 什麽是Drawcall?
Drawcall是CPU向GPU發送繪制命令的接口調用。理論上每一個不同材質的物件需要渲染在屏幕上時,CPU都會調用圖形API ( openGL or Diract3D ) 的Draw接口觸發顯卡進行繪制。
- 為什麽優化Drawcall?
Drawcall對硬件和驅動而言,要求大量設置狀態(使用哪些頂點、哪些shader等)和狀態轉換。而Drawcall最大的消耗在於:如果每次drawcall只提交少量的數據將導致CPU瓶頸,CPU無法將GPU填滿。Drawcall對GPU的耗費在於硬件一直等待CPU提交數據,而無法得到有效利用。GPU大量的時間耗費在不斷切換狀態和正確性檢測上。 GPU在Draw Call之間,為了防止前後Draw的依賴關系造成繪制錯誤或者資源競用,一般會在Draw Call後Flush整個流水線,小粒度的Draw Call對GPU流水線來說是個很大的浪費。(這個問題在D3D老版本存在,在新版D3D11中得到改善。)實際上unity官方指出,Drawcall數量的降低並非重點,重點是減少批次的數量,Drawcall優化實際上是對批次數量的優化。
延伸閱讀:
· why are draw calls expensive? — Stack Overflow
· Direct3D Draw函數 異步調用原理解析
- 如何優化Drawcall?
在Unity中對Drawcall的優化有以下幾個策略:Drawcall batching,合並打包圖集,減少光照和陰影以及遮擋剔除和視錐剔除等。以下分別談一下各個策略的優缺點。
1.1. Drawcall Batching
Unity中對Drawcall的批次有兩種:靜態批次(static batching)和動態批次(dynamic batching)。但不論靜態批次還是動態批次都要求對象的材質是共享的,即不同材質的對象是無法進行批次的。而且要註意的一點:如果在腳本中調用材質時,使用Renderer.material會造成材質的拷貝,而使用Renderer.sharedMaterial來調用則不會拷貝材質。
1.1.1. 靜態批次 Drawcall static batching
場景中的多個物件如果是不移動的(包括位置、縮放、旋轉等),並且共享同一材質,比如地形、建築、花盆等,那麽可以選擇采用靜態批次。靜態批次只需要在Inspector勾選static選項即可。靜態批次需要註意的是,unity會將進行批次的多個對象合並成一個大的對象,也會導致內存損耗,有時候要避免太多對象靜態批次造成的內存過高。這也表明,優化並非絕對做好某一方面,而是平衡各個硬件的瓶頸和效率,選擇相對適中的方案。
1.1.2. 動態批次 Drawcall dynamic batching
動態批次是運動的物件在unity中也可以進行批次渲染,動態批次不需要手動設置,是unity自動進行的,但是這裏有諸多陷阱和約束,開發者需要遵守一定的限制條件才能享受動態批次的好處。
根據unity官方文檔描述:
1) . 動態批次是逐頂點處理的,因此僅對少於900個頂點的mesh有效。如果shader使用了頂點位置,法線和UV那麽僅支持低於300頂點的mesh,而如果shader使用了頂點位置,法線、UV0、UV1和切向量,則之多僅支持180頂點。
2) . 縮放問題
縮放對於批次是有影響的,這裏涉及到一個統一縮放和非統一縮放的概念。統一縮放即為三軸同比例縮放,比如(1,1,1),(2,2,2)(5,5,5)... 非統一縮放即為三軸不同比例縮放,如(1,2,1)(2,1,1)(1,2,3)等等。
Unity對統一縮放的對象是不進行動態批次的,而對非同一縮放的對象是可以進行動態批次的。這裏有點詭異,查閱了一些資料,解釋如下:
對於非同一縮放的物件,unity將其mesh進行了復制,因此即便是從相同物件進行的非同一縮放的兩個對象是兩份mesh;對統一縮放的對象來說,unity不對mesh進行復制,而是使用同一mesh進行縮放,此時復制mesh來進行批次渲染是不值得的,但是對於非統一縮放的對象,既然已經復制了mesh(不是為了批次,而是其他原因決定復制mesh),那麽進行批次是順帶實現的。
(參考 Dynamic Batching and Scale ——unity3d answers )
3) . 使用了不同的材質,即便實質上是相同的(比如兩個一模一樣的材質),也不會進行批次。
4) . 擁有lightmap的物件含有額外的材質屬性,比如lightmap偏移和縮放系數等,所以擁有lightmap的物件不能批次。
5) . 多通道的shader會妨礙批處理操作,接受實時陰影的物件無法批次。
註意: unity渲染是有順序的,渲染排序有可能打斷動態批次。
例如:
場景中有物件ABC,假設AB使用同一材質1,C使用材質2.那麽drawcall有可能是2個,也有可能是3個。
如果順序為:
1.渲染A,使用材質1
2.渲染B,使用材質1
3.渲染C,使用材質2
那麽drawcall是2個,AB進行了動態批次。
如果順序為:
1.渲染A,使用材質1
2.渲染C,使用材質2
3.渲染B,使用材質1
那麽drawcall就是3個,AB的批次被C打斷了。
渲染順序跟什麽有關呢?
首先根據物件到攝像機的距離,進行遠處物件先渲染近處物件後渲染。相同材質的物件盡量在一層,不要讓不同材質的物件進入這一層。如果無法保證這一點,那麽還有一種方法:修改shader中渲染隊列值。即打開shader 將subshader中的tag{}中queue 修改為小於2500的值。
渲染隊列小於等於2500時,unity認為其是不透明的,對於不同材質但z值相同對象,unity不對其進行排序,這樣能保證相同材質的多個對象能是一個批次,不同材質的對象如果進入兩個相同材質的對象之間,不會打破批次;
渲染隊列大於2500時,unity會對不同材質的對象進行排序,此時如果不同材質的對象進入到兩個相同材質的對象之間的話,會使相同材質的對象批次被打破。
批次先寫到這,其實很多網上都有,不過有些沒深入講解,也有些沒給出解決辦法,我就使用每個方案時遇到的困難給出了自己的解決方案。其實批次還有不少研究的地方,之後想到了會繼續更新。
延伸閱讀:
· Unity -Draw Call Batching
· Unity Drawcall 優化手記
1.2. 合並圖集
其實合並圖集也是利用了Unity的Drawcall batching。將多個紋理進行打包成圖集是為了減少材質,這樣多個對象共享一個材質,並進而使用同一個紋理和shader,觸發unity的動態批次。圖集打包工具有很多,Asset store中也可以搜到不少,比如Texture Packer Free 、 DrawCall Optimizer(收費) Mesh Baker Free 等等都可以將貼圖打包合並。
但是合並圖集也有缺點,合並貼圖時應該註意選擇同時出現在屏幕的對象貼圖進行合並。如果不能做到這一點,那麽合並圖集可能起到反作用,即渲染一個對象需要加載過多無用貼圖,造成內存占用率升高。我的項目這個方案也是采用之後又棄用的,因為歸類同時出現在屏幕的貼圖並非易事!
1.3. 光照和陰影
實時光照和陰影可能增加Drawcall,帶有光源計算的shader材質會因為光照產生多個Drawcall。使用燈光會打斷Drawcall batching,盡量使用烘焙燈光貼圖等技巧來實現燈光效果。
延伸閱讀:
· Forward Rendering Path Details
· Light Troubleshooting and Performance
1.4. 遮擋剔除、視錐剔除
這兩個Unity提供的剔除方案,出視野之後應剔除對象渲染。
2. 物理組件
3. GC
GC是unity自動回收內存垃圾的回收器,這雖沒有內存泄漏的風險,但是過多的垃圾回收會讓CPU高負荷。這裏就要避免不必要的內存申請和釋放。可以在某個腳本定時清理垃圾,如void update(){if(Time.framecount %5 ==0)System.GC.collect();}
有以下幾點需要註意:
3.1. 字符串的拼接會產生臨時字符串內存,移除代碼中的字符串拼接,改用string.format,或stringbuilder,這沒測。
3.2. 用for代替foreach,foreach每次叠代產生24字節垃圾內存。100次循環就是2.4kB.
3.3. 對象標簽tag比較采用comparetag,不要用tag=="mytag"這樣。
3.4. 使用對象池。對象克隆也是調用new,因此對於可以循環利用的對象要采用對象池,比如子彈、特效、寶石等等。
對象池使用時要註意一點:如果對象上掛了腳本,那麽數據需要每次進行初始化或對象回收的時候進行重置,否則下次再利用的對象腳本可能存留上次的數據,那極有可能出bug。
3.5. 盡量使用struct而非class,因為struct是棧區,class是堆區。
GC涉及到內存,詳細內容會在內存篇展開。
4. 腳本
腳本中如果在update 函數中調用了Getcomponent等接口,最好將組件緩存。
在update中的處理如果允許,盡量隔多幀處理一次。
如:將update() {DoSth();} 修改為:update() { if(Time.framecount %5 == 0) DoSth();}
其實腳本的優化主要就是針對update中復雜和耗費的邏輯進行優化,其次對於遊戲進行中的卡頓,可以修改邏輯,使用預加載方式,將遊戲進行中的對象在開始前一次性加載到內存。
Unity官方給出的一些優化建議:
1.PC平臺的話保持場景中顯示的頂點數少於200K~3M,移動設備的話少於10W,一切取決於你的目標GPU與CPU。 2.如果你用U3D自帶的SHADER,在表現不差的情況下選擇Mobile或Unlit目錄下的。它們更高效。 3.盡可能共用材質。 4.將不需要移動的物體設為Static,讓引擎可以進行其批處理。 5.盡可能不用燈光。 6.動態燈光更加不要了。 7.嘗試用壓縮貼圖格式,或用16位代替32位。 8.如果不需要別用霧效(fog) 9.嘗試用OcclusionCulling,在房間過道多遮擋物體多的場景非常有用。若不當反而會增加負擔。 10.用天空盒去“褪去”遠處的物體。 11.shader中用貼圖混合的方式去代替多重通道計算。 12.shader中註意float/half/fixed的使用。 13.shader中不要用復雜的計算pow,sin,cos,tan,log等。 14.shader中越少Fragment越好。 15.註意是否有多余的動畫腳本,模型自動導入到U3D會有動畫腳本,大量的話會嚴重影響消耗CPU計算。 16.註意碰撞體的碰撞層,不必要的碰撞檢測請舍去。[Unity優化] Unity CPU性能優化 (難度3 推薦4)