天天看點

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

Cocos Creator 性能優化:DrawCall(全面!)

title: Cocos Creator 性能優化:DrawCall

前言

在遊戲開發中,DrawCall 作為一個非常重要的性能名額,直接影響遊戲的整體性能表現。

無論是 Cocos Creator、Unity、Unreal 還是其他遊戲引擎,隻要說到遊戲性能優化,DrawCall 都是絕對少不了的一項。

本文将會介紹什麼是 DrawCall,為什麼要減少 DrawCall 以及在 Cocos Creator 項目中如何減少 DrawCall 來提升遊戲性能。

正文

什麼是 DrawCall?

DrawCall 中文譯為“繪制調用”或“繪圖指令”。

DrawCall 是一種行為(指令),即 CPU 調用圖形 API,指令 GPU 進行圖形繪制。

DrawCall 一般可以簡稱為“DC”,當然此“DC”非彼“DC”…

為什麼要減少 DrawCall?

發生了什麼

當我們在讨論減少 DrawCall 時我們在讨論什麼?

其實我們真正需要減少的并不是 DrawCall 這個行為本身,而是減少每個 DrawCall 前置的一些消耗性能和時間的行為。

看不懂?其實我也不知道我在說些什麼,還是接着看下面的内容吧 : p

舉個栗子

問:嘗試在兩個硬碟之間傳輸檔案,傳輸 1 個 1MB 的檔案和傳輸 1024 個 1KB 的檔案,同樣是傳輸了共 1MB 的檔案,哪個更快?

答:傳輸 1 個 1MB 的檔案要比傳輸 1024 個 1KB 的檔案要快得多得多。因為在每一個檔案傳輸前,CPU 都需要做許多額外的工作來保證檔案能夠正确地被傳輸,而這些額外工作造成了大量額外的性能和時間開銷,導緻傳輸速度下降。

回到渲染

圖形渲染管線的大緻流程如下:

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文
上圖隻是對渲染管線的部分概括,友善大家了解,實際的圖形渲染管線比較複雜,不在本文讨論範圍内。

從圖中可以看到在渲染管線中,在每一次 DrawCall 前,CPU 都需要做一系列準備工作,才能讓 GPU 正确渲染出圖像。

而 CPU 的每一次記憶體顯存讀寫、資料處理和渲染狀态切換都會帶來一定的性能和時間消耗。

到底是誰的鍋?

一般來說 GPU 渲染圖像的速度其實是非常快的,繪制 100 個三角形和繪制 1000 個三角形所消耗的時間沒差多少。

但是 CPU 的記憶體顯存讀寫、資料處理和渲染狀态切換相對于 GPU 渲染來說是 非常非常慢 的。

實際的瓶頸在于 CPU 這邊,大量的 DrawCall 會讓 CPU 忙到焦頭爛額暈頭轉向不可開交,而 GPU 大部分時間都在摸魚,是導緻遊戲性能下降的主要原因。

是以 DrawCall 這玩意越少越好~

如何減少 DrawCall?

在遊戲運作時引擎是按照節點層級順序從上往下由淺到深進行渲染的,理論上每渲染一張圖像(文本最終也是圖像)都需要一次 DrawCall。

既然如此,隻要我們想辦法将盡可能多的圖像在一次 DrawCall 中渲染出來(也就是“渲染合批”),就可以盡量少去調用 CPU,進而減少 DrawCall。

簡單點,就是減少讓 CPU 工作的次數,但是每次都多給點活,不就可以省去一些“CPU 準備工具然後工作”和“工作結束叫 GPU 加工”的步驟了嘛,代價就是每次工作的時間會變長~

明白了這個原理之後,下面讓我們看看在實際遊戲開發中應該如何操作吧。

靜态合圖

靜态合圖就是在開發時 将一系列碎圖整合成一張大圖。

圖集對于 DrawCall 優化來說非常重要,但是并不是說我們把所有圖檔統統打成圖集就萬事大吉了,這裡面也有它的門道,胡亂打圖集的話說不定還會變成負優化。

最重要的是 盡量将處于同一界面(UI)下的相鄰且渲染狀态相同的碎圖打包成圖集,才能達到減少 DrawCall 的目的。

還記得遊戲渲染時是按順序渲染的嗎,是以“相鄰”很關鍵!要考,做筆記!

改變渲染狀态會打斷渲染合批,例如改變紋理狀态(預乘、循環模式和過濾模式)或改變 Material(材質)、Blend(混合模式)等等,是以使用自定義 Shader 也會打斷合批。

舉個栗子,我這裡有一個由 10 張碎圖和 1 個文本所組成的彈窗(假設都使用同樣的渲染方式):

  1. 在不做任何優化且未開啟動态合圖的情況下,渲染這個彈窗需要 11 個 DrawCall。
  2. 将所有碎圖打成一個圖集,文本節點夾在精靈節點之間的情況下需要 3 個 DrawCall,在頂部最外層或者底部最外層的情況下需要 2 個 DrawCall。
  3. 文本使用 BMFont,将所有碎圖和 BMFont 打成一個圖集的話隻需要 1 個 DrawCall,如果碎圖不和 BMFont 打成一個圖集的情況則參考第 2 項。
  4. 碎圖不打包圖集,開啟動态合圖,在理想情況下,文本使用 BMFont 最少隻需要 1 個 DrawCall,不使用 BMFont 的情況同樣參考第 2 項。

如果上面的例子你不太能了解的話,那請接着看下面的内容,相信你閱讀完本篇文章的全部内容後再來看這個例子将會茅塞頓開哈哈哈~

動态合圖和 BMFont 會在後面說到。

當然上面這個例子算是比較理想的情況,實際上的情況可能會比例子更為複雜,精靈和文本可能會更多,也不一定能将所有圖像資源都打包進一個圖集。是以我們隻能是盡量合理地去優化,避免出現“撿了芝麻,丢了西瓜”的情況。

不建議任何圖像資源的尺寸超過 2048 * 2048,否則在小遊戲和原生平台可能會出現問題;

而且圖像尺寸越大,加載的時間也越長,而且是非線性的那種增長,例如加載一張圖像比加載兩張圖像所消耗的時間還長,得不償失。

下面介紹兩種打包靜态圖集的方式:

自動圖集資源(Auto Atlas)

利用 Cocos Creator 内置的自動圖集資源來将碎圖打包成圖集。

在項目建構時,編輯器會将所有自動圖集資源所在檔案夾下的所有符合要求的圖像分别根據配置打包成一個或多個圖集。

自動圖集資源使用起來很靈活,編輯器在打包圖集時會自動遞歸子目錄,若子目錄下也有自動圖集資源(即 

.pac

 檔案)則會跳過該目錄,是以我們可以對同一目錄下的不同部分的碎圖配置不同的參數。

建立自動圖集配置

在 資料總管 中右鍵,點選 [ 建立 -> 自動圖集配置 ] 就會建立一個名為 

AutoAtlas.pac

 的資源。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

配置屬性

在 資料總管 中點選自動圖集資源檔案就可以在 屬性檢查器 面闆中看到自動圖集資源可配置的屬性,點選 Preview 按鈕即可預覽圖集。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

關于自動圖集的幾點建議

  1. 合理控制圖集最大尺寸,避免單個圖像加載時間過長。
  2. 尺寸太大的圖像沒有必要打進圖集(如背景圖)。
  3. 善用九宮格(Sliced)可以節省很多空間(這一點需要美術大佬配合)。
  4. 間距保持預設的 2 并保持勾選擴邊選項,避免圖像裁剪錯誤和出現黑邊的情況。
  5. 勾選不包含未被引用資源選項,自動排除沒有用到的圖像以節省空間(該選項預覽時無效)。
  6. 開發時預覽圖集,根據結果進行調整,以達到最好的優化效果。

關于每個屬性具體的作用請參考官方文檔。

自動圖集資源官方文檔:http://docs.cocos.com/creator/manual/zh/asset-workflow/auto-atlas.html#配置自動圖集資源

TexturePacker

我們也可以使用第三方軟體 TexturePacker 來預先将圖像打包成圖集再導入項目中。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

TexturePacker 是收費軟體,但是一般情況下免費功能就已經夠用了。

另外使用 TexturePacker 打包圖集時需要注意配置 形狀填充(Shape Padding,對應 Auto Atlas 中的間距),避免某張圖像出現相鄰圖像的像素的情況。

TexturePacker 官網位址:https://www.codeandweb.com/texturepacker

Auto Atlas 和 TexturePacker 的對比

Auto Atlas

  • Cocos Creator 内置,友善到家了
  • 功能不多但是該有的都有,免費
  • 項目建構時才生成圖集,開發時任意修改無壓力
  • 圖集尺寸在生成時自适應,節省空間
  • 支援自動紋理壓縮

TexturePacker

  • 第三方軟體需自行安裝,不夠友善
  • 收費功能很多很專業但是用不着,免費功能也夠用
  • 先生成圖集再使用,更換圖像又要重新生成圖集
  • 尺寸固定需要自己設定
  • 自己壓縮去
總結:Auto Atlas 真香!

動态合圖(Dynamic Atlas)

這裡引用官方文檔中對于動态合圖的介紹:

Cocos Creator 提供了在項目建構時的靜态合圖方法 —— 自動合圖(Auto Atlas)。但是當項目日益壯大的時候貼圖會變得非常多,很難将貼圖打包到一張大貼圖中,這時靜态合圖就比較難以滿足降低 DrawCall 的需求。是以 Cocos Creator 在 v2.0 中加入了 動态合圖(Dynamic Atlas)的功能,它能在項目運作時動态的将貼圖合并到一張大貼圖中。當渲染一張貼圖的時候,動态合圖系統會自動檢測這張貼圖是否已經被合并到了圖集(圖檔集合)中,如果沒有,并且此貼圖又符合動态合圖的條件,就會将此貼圖合并到圖集中。

動态合圖官方文檔:https://docs.cocos.com/creator/manual/zh/advanced-topics/dynamic-atlas.html

簡單來說,開啟動态合圖之後,引擎會在運作時幫我們對符合條件(即尺寸小于碎圖限制的最大尺寸)的精靈進行合圖,達到和提前打包圖集一樣的效果。

引擎的 動态圖集尺寸最大是 2048 * 2048,可合并的 碎圖限制的最大尺寸是 512,使用者可以通過下面的 API 進行修改:

cc.dynamicAtlasManager.maxFrameSize = 512;
           

啟用動态合圖會占用額外的記憶體,不同平台占用的記憶體大小不一樣。小遊戲和原生平台上預設會禁用動态合圖,但如果你的項目記憶體空間仍有富餘的話建議強制開啟:

cc.macro.CLEANUP_IMAGE_CACHE = false;
cc.dynamicAtlasManager.enabled = true;
           

另外還需要保證紋理的 Premulyiply Alpha(預乘)、Wrap Mode(循環模式) 和 Filter Mode(過濾模式) 等資訊與動态圖集一緻才能夠動态合批。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

靜态圖集也可以參與動态合圖

在動态合圖的官方文檔中有提到:

當渲染一張貼圖的時候,動态合圖系統會自動檢測這張貼圖是否已經被合并到了圖集(圖檔集合)中,如果沒有,并且此貼圖又符合動态合圖的條件,就會将此貼圖合并到圖集中。

但其實 隻要靜态圖集滿足動态合圖的要求(即尺寸小于碎圖限制的最大尺寸),也是可以參與動态合圖的。

注意:自動圖集資源(Auto Atlas)需要在其屬性檢查器面闆中開啟 Texture 欄下的 Packable 選項,該選項預設是禁用的。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

額外補充

隻有紋理開啟了 Packable 選項的精靈才能夠參與動态合圖,該選項預設開啟。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

紋理參與動态合圖後會修改原始貼圖的 UV 坐标,是以在 Shader 中的無法正确計算 UV 坐标,導緻 Shader 無效。

如果需要對精靈使用自定義 Shader,需要禁用其紋理的 Packable 選項。

也可以在代碼中禁用該選項:

let sprite = this.node.getComponent(cc.Sprite);
let texture = sprite.spriteFrame.getTexture();
texture.packable = false;
           
Packable 官方文檔:https://docs.cocos.com/creator/manual/zh/asset-workflow/sprite.html?h=packable

位圖字型(BMFont)

在場景中使用系統字型或 TTF 字型的 Label 會打斷渲染合批,特别是 Label 和 Sprite 層疊交錯的情況,每一個 Label 都會打斷合批增加一個 DrawCall,文本多的場景下輕輕松松 100+。

對于遊戲中的文本,特别是數字、字母和符号,都建議 使用 BMFont 來代替 TTF 或系統字型,并且 将 BMFont 與 UI 碎圖打包到同一圖集中(或 開啟動态合圖),可以免除大部分文本導緻的 DrawCall。

舉個栗子

例如一個場景中有 80 張精靈和 80 個文本(系統字型)互相交錯,節點層級如下圖:

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

運作起來之後可以看到左下角的 Profile 顯示 DrawCall 已經高達 161 個,也就是說每一個精靈和文本都增加一個 DrawCall,這種情況即使精靈打了圖集也一樣無濟于事。

不要問明明隻有 80 張精靈和 80 個文本不應該是 160 個 DrawCall 嗎為什麼是 161 個…

因為左下角的 Profile 也要占一個 : (

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

對比栗子

還是上面的場景,嘗試将 Label 的系統字型換成 BMFont 并且與精靈打包到同一個圖集之後,同樣是 80 個精靈和 80 個文本。

但是 DrawCall 隻有 2 個,同時幀時間降低到了 1ms,幀率提升了 10 FPS,渲染耗時降低到了 0.6ms。

實際上場景隻占了 1 個 DrawCall,另一個 DrawCall 是左下角的 Profile 占的…
Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

另外,對于漢字可以嘗試使用 Label 元件的 Cache Mode 來優化。

文本緩存模式(Cache Mode)

Cocos Creator 2.0.9 版本在 Label 元件上增加了 Cache Mode 選項,來解決系統字型和 TTF 字型帶來的性能問題。

Cache Mode 官方文檔:https://docs.cocos.com/creator/manual/zh/components/label.html#文本緩存類型(cache-mode)

Cache Mode 有以下3 種選擇:

NONE(預設)

每一個 Label 都會生成為一張單獨的位圖,且不會參與動态合圖,是以每一個 Label 都會打斷渲染合批。

BITMAP

當 Label 元件開啟 BITMAP 模式後,文本同樣會生成為一張位圖,但是 隻要符合動态合圖要求就可以參與動态合圖,和周圍的精靈合并 DrawCall。

一定要注意 BITMAP 模式隻适用于不頻繁更改的文本,否則記憶體爆炸了後果自負!

舉個栗子

同樣是上文提到的 精靈和文本互相交錯 的例子,文本使用 BITMAP 模式,精靈不打包成圖集,開啟動态合圖。

結果是所有精靈(包括背景)和文本都成功動态合圖,實際 DrawCall 降至 1 個。

如果精靈打包成了圖集則會變成 160 個,因為圖集預設不參與動态合圖。

是以目前這種情況(少精靈多文本)不打圖集反而是比較好的選擇。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

CHAR

當 Label 元件開啟 CHAR 模式後,引擎會将該 Label 中出現的所有字元緩存到一張全局共享的位圖中,相當于是生成了一個 BMFont。

适用于文本頻繁更改的情況,對性能和記憶體最友好。

注意:該模式隻能用于字型樣式和字号固定,并且不會頻繁出現巨量未使用過的字元的 Label。因為共享位圖的最大尺寸為 20482048,占滿了之後就沒辦法再渲染新的字元,需要切換場景才會清除共享位圖。*

開啟了 CHAR 模式的 Label 無法參與動态合圖,但是可以和相鄰的同樣是 CHAR 模式的 Label 合并 DrawCall(相當于是一張未打包進圖集的 BMFont)。

舉個栗子

還是是上文提到的 精靈和文本互相交錯 的例子,為了更好展現 CHAR 模式的優勢,我更改了場景節點的結構,将精靈和文本進行 分離(關于這點可以看下面的 UI層級調整)。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

所有 Label 開啟 CHAR 模式,并在腳本中每過 0.2 秒就将文本更改成新的随機數。

在這個例子中,引擎會在運作時生成一張包含數字 0 到 9 的 BMFont 存在記憶體中,另外由于我将所有 Label 都聚合在一起,是以所有 Label 的渲染合并成了 1 個 DrawCall,另外請特别關注左下角的幀時間、幀率和渲染耗時。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

光看上面的圖似乎看不出個是以然來,那我們增加一個對照組,将所有文本的 Cache Mode 選項設為預設的 NONE 模式。

此時可以發現 幀時間最高達到了 2 ms,平均幀率下降了大概 6 FPS,渲染耗時更是翻了 4 倍最高達到了 1.8 ms。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

總結

結論已經很明顯了,對于大量頻繁更改的文本,使用 CHAR 模式帶來的性能提升是非常明顯的。

同時 CHAR 模式的局限也很明顯,一般用于場景中出現大量數字文本,類似于經驗值增加、血量減少之類的特效的情況。

UI 層級調整

除了以上的優化方案,我們還可以在遊戲場景中下功夫,将性能優化做到極緻。

其實上文也有提到,我們可以通過 優化節點層級,分離圖像節點和文本節點,文本使用 BMFont 或 Cache Mode 選項,盡量出現避免文本打斷渲染合批的情況。

Cocos Creator 性能優化:DrawCallCocos Creator 性能優化:DrawCall(全面!)前言正文

特别是對于戰鬥場景中大量的文本提示(傷害值、血量值和法力值等等)或合成遊戲中大量的經驗值文本,因為這些文本基本都是數字,使用這種方式即使再多文本也隻需要 1 個 DrawCall 就可以全部渲染出來。

舉個栗子

下面的場景中,文本開啟 CHAR 模式,使用腳本每秒生成 50 個左右的随機數字,文本節點統一放在 labelLayer 節點下,讓所有文本可以共享 1 個 DrawCall,另外背景和椰子頭占 1 個,左下角 Profile 占 1 個。

可以看到即使場景中瞬間出現這麼多文本,整體性能也還是比較可觀的。

在這個例子中,引擎在運作時為我們生成了一份包含數字 0 到 9 的全局共享位圖(BMFont)。

當然如果可以在 Label 中直接使用 BMFont 的話那就更好了。

補充

再次提醒

  1. 改變渲染狀态會打斷渲染合批,例如改變紋理狀态(預乘、循環模式和過濾模式)或改變 Material(材質)、Blend(混合模式)等等,是以使用自定義 Shader 也會打斷合批。
  2. 圖集預設不參與動态合圖,手動開啟自動圖集資源的 Packable 選項後如果最終圖集符合動态合圖要求也可以參與動态合圖。
  3. 紋理開啟 Packable 選項參與動态合圖後無法使用自定義 Shader,因為動态合圖會修改原始貼圖的 UV 坐标。
  4. 使用 Cache Mode 的 BITMAP 模式需要注意記憶體情況,CHAR 模式需要注意文本内容是否多且不重複。

最後還需要注意

在 Cocos Creator 2.0.7 之前的版本中,改變節點的顔色或透明度、Sprite 元件使用九宮格(Sliced)都會打斷渲染合批。

繼續閱讀