前段時間本人轉戰unity手遊,因爲做者(Chwen)以前參與端遊開發,有些端遊的經驗能夠直接移植到手遊,好比項目框架架構、代碼設計、部分性能分析,而對於移動終端而言,CPU、內存、顯卡甚至電池等硬件因素,以及網絡等條件限制,對移動遊戲開發的優化帶來更大的挑戰。 html
這裏就以unity4.5x版本爲例,對Unity的優化方案作一個總結,有些是項目遇到的,也有些是看到別人寫的不錯拿來分享,算做一個整理,後期也會持續更新。本優化從CPU、GPU和內存三個方面着手總結,這一篇先從CPU提及,整理一些針對CPU相關的優化建議。緩存
對CPU的優化主要是從drawcall、物理組件、GC(垃圾回收)、腳本等幾個方面開展。性能優化
Drawcall是CPU向GPU發送繪製命令的接口調用。理論上每個不一樣材質的物件須要渲染在屏幕上時,CPU都會調用圖形API ( openGL or Diract3D ) 的Draw接口觸發顯卡進行繪製。網絡
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函數 異步調用原理解析架構
在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會對不一樣材質的對象進行排序,此時若是不一樣材質的對象進入到兩個相同材質的對象之間的話,會使相同材質的對象批次被打破。
批次先寫到這,其實不少網上都有,不過有些沒深刻講解,也有些沒給出解決辦法,我就使用每一個方案時遇到的困難給出了本身的解決方案。其實批次還有很多研究的地方,以後想到了會繼續更新。
延伸閱讀:
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官方給出的一些優化建議:
引用請註明出處,http://www.cnblogs.com/chwen/p/4396515.html 很是感謝,隨時交流。
後續更新,下一篇將是Unity優化——內存篇。