您好,登錄后才能下訂單哦!
我們在做游戲的時候,應該了解哪些性能因素影響游戲,才能對癥下藥。對于一個游戲來說,主要有兩種計算資源:CPU和GPU。這兩者會互相合作,來讓游戲可以在預期的幀率和分辨率下工作。CPU負責幀率,GPU主要負責與分辨率相關的一些東西。
總結起來,主要有以下性能瓶頸:
CPU:過多的Draw Calls;復雜的腳本或者物理模擬。
頂點處理:過多的頂點;過多的逐頂點計算。
像素(Fragment)處理(GPU):過多的fragment,overdraws;過多的逐像素計算。
寬帶:尺寸很大且未壓縮的紋理;分辨率過高的framebuffer。
對于CPU來說,限制它的主要是游戲中的Draw Calls。那么什么是Draw Call呢?在OpenGL中,在每次繪圖前,我們都需要先準備好頂點數據(位置、法線、顏色、紋理坐標等),然后調用一系列API把它們放到GPU可以訪問到的指定位置,最后,我們需要調用_glDraw命令,來告訴GPU進行渲染,而調用_glDraw命令的時候,就是一次Draw Call。為什么Draw Call會成為性能瓶頸呢(而且是CPU的瓶頸)?我們想要繪制圖像時,就一定需要調用Draw Call。例如,一個場景里有水有樹,我們渲染水的時候使用的是一個material以及一個shader,但渲染樹的時候就需要一個完全不同的material和shader,那么就需要CPU重新準備頂點數據、重新設置shader,而這種工作實際是非常耗時的。如果場景中,每一個物體都使用不同的material、不同的紋理,那么就會產生太多Draw Call,影響幀率,游戲性能就會下降。
如何減少DrawCalls?
主要介紹批處理(Batching)
最常見的就是通過批處理(Batching)了。從名字上來理解,就是一塊處理多個物體的意思。那么什么樣的物體可以一起處理呢?答案就是使用同一個材質的物體。這是因此,對于使用同一個材質的物體,它們之間的不同僅僅在于頂點數據的差別,即使用的網格不同而已。我們可以把這些頂點數據合并在一起,再一起發送給GPU,就可以完成一次批處理。
Unity中有兩種批處理方式:一種是動態批處理,一種是靜態批處理。對于動態批處理來說,好消息是一切處理都是自動的,不需要我們自己做任何操作,而且物體是可以移動的,但壞消息是,限制很多,可能一不小心我們就會破壞了這種機制,導致Unity無法批處理一些使用了相同材質的物體。對于靜態批處理來說,好消息是自由度很高,限制很少,壞消息是可能會占用更多的內存,而且經過靜態批處理后的所有物體都不可以再移動了。
首先來說動態批處理。Unity進行動態批處理的條件是,物體使用同一個材質并且滿足一些特定條件。Unity總是在不知不覺中就為我們做了動態批處理。例如下面的場景:這個場景共包含了4個物體,其中兩個箱子使用了同一個材質。可以看到,它的Draw Calls現在是3,并且顯示Save by batching是1,也就是說,Unity靠Batching為我們節省了1個Draw Call。下面,我們來把其中一個箱子的大小隨便改動一下,看看會發生什么:可以發現,Draw Calls變成了4,Save by batching的數目也變成了0。這是為什么呢?它們明明還是只使用了一個材質啊。原因就是前面提到的那些需要滿足的其他條件。動態批處理雖然自動得令人感動,但它對模型的要求很多:頂點屬性的最大限制為900,而且未來有可能會變。不要依賴這個數據。
一般來說,那么所有對象都必須需要使用同一個縮放尺度(可以是(1, 1, 1)、(1, 2, 3)、(1.5, 1.4, 1.3)等等,但必須都一樣)。但如果是非統一縮放(即每個維度的縮放尺度不一樣,例如(1, 2, 1)),那么如果所有的物體都使用不同的非統一縮放也是可以批處理的。這個要求很怪異,為什么批處理會和縮放有關呢?這和Unity背后的技術有關系。
使用lightmap的物體不會批處理。多passes的shader會中斷批處理。接受實時陰影的物體也不會批處理。
上述除了最常見的由于縮放導致破壞批處理的情況,還有就是頂點屬性的限制。例如,在上面的場景中我們添加之前未優化后的箱子模型:可以看到Draw Calls一下子變成了5。這是因為新添加的箱子模型中,包含了474個頂點,而它使用的頂點屬性有位置、UV坐標、法線等信息,使用的總和超過了900。
動態批處理的條件這么多,一不小心它就不干了,因此Unity提供了另一個方法,靜態批處理。接著上面的例子,我們保持修改后的縮放,但把四個物體的“Static Flag”勾選上:
點擊Static后面的三角下拉框,我們會看到其實這一步設置了很多東西,這里我們想要的只是“Batching static”一項。這時我們再看Draw Calls,恩,還是沒有變化。但是不要急,我們點擊運行,變化出現了:
Draw Calls又回到了3,并且顯示Save by batching是1。這就是得利于靜態批處理。而且,如果我們在運行時刻查看模型的網格,會發現它們都變成了一個名為Combined Mesh (roo: scene)的東西。這個網格是Unity合并了所有標識為“Static”的物體的結果,在我們的例子里,就是四個物體:
你可以要問了,這四個對象明明不是都使用了一個材質,為什么可以合并成一個呢?如果你仔細觀察上圖的話,會發現里面標明了“4 submeshes”,也就是說,這個合并后的網格其實包含了4個子網格,也就是我們的四個對象。對于合并后后的網格,Unity會判斷其中使用同一個材質的子網格,然后對它們進行批處理。
但是,我們再細心點可以發現,我們的箱子使用的其實是同一個網格,但合并后卻變成了兩個。而且,我們觀察運行前后Stats窗口中的“VBO total”,它的大小由241.6KB變成了286.2KB,變大了!
這里就體現了靜態批處理的缺點,如果在靜態批處理前有一些物體共享了相同的網格(例如這里的兩個箱子),那么每一個物體都會有一個該網格的復制品,即一個網格會變成多個網格被發送給GPU。在上面的例子看來,就是VBO的大小明顯增大了。如果這類使用同一網格的對象很多,那么這就是一個問題了,這種時候我們可能需要避免使用靜態批處理,這意味著犧牲一定的渲染性能。例如,如果在一個使用了1000個重復樹模型的森林中使用靜態批處理,那么結果就會產生1000倍的內存,這會造成嚴重的內存影響。這種時候,解決方法要么我們可以忍受這種犧牲內存換取性能的方法,要么不要使用靜態批處理,而使用動態批處理(前提是大家使用相同的縮放大小,或者大家都使用不同的非統一縮放大小),或者自己編寫批處理的方法。當然,我認為最好的還是使用動態批處理來解決。
有一些小提示可以使用:
盡可能選擇靜態批處理,但得時刻小心對內存的消耗。
如果無法進行靜態批處理,而要使用動態批處理的話,那么請小心上面提到的各種注意事項。例如:
盡可能讓這樣的物體少并且盡可能讓這些物體包含少量的頂點屬性。
不要使用統一縮放,或者都使用不同的非統一縮放。
對于游戲中的小道具,例如可以撿拾的金幣等,可以使用動態批處理。
對于包含動畫的這類物體,我們無法全部使用靜態批處理,但其中如果有不動的部分,可以把這部分標識成“Static”。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。