GPU 最佳化
前言
對於新圖形功能與進步的需求,幾乎可以確定你會遇到圖形瓶頸。有些瓶頸可能發生在 CPU 端,例如 Godot 引擎內部為算繪物件所做的計算。有時瓶頸也可能出現在圖形驅動程式的 CPU 部分,負責整理並傳遞指令給 GPU,或是指令傳輸過程中。最終,瓶頸也會發生在 GPU 本身。
算繪時瓶頸出現的位置高度依賴於硬體。特別是行動裝置上的 GPU,可能會對於桌面端能輕鬆運作的場景感到吃力。
理解並分析 GPU 瓶頸與 CPU 上的情況略有不同。這是因為你通常只能透過改變給 GPU 的指令來間接調整效能。此外,量測起來也較困難。在許多情況下,唯一能衡量效能的方式就是觀察每個影格的算繪時間變化。
繪圖呼叫、狀態切換與 API
備註
以下內容雖然與終端使用者無直接關聯,但能協助理解後續章節所需的背景知識。
Godot 透過圖形 API(Vulkan、OpenGL、OpenGL ES 或 WebGL)向 GPU 發送指令。這些溝通與驅動處理會消耗不少資源,特別是在 OpenGL、OpenGL ES 與 WebGL 上。如果我們能以驅動程式和 GPU 最佳化的方式給予指令,則可以大幅提升效能。
在 OpenGL 中,幾乎每個 API 指令都必須做一定的驗證,以確保 GPU 處於正確狀態。即使是看似簡單的指令,也會引發大量幕後的管理作業。因此,我們的目標是將這些指令減至最低,並盡可能將相似的物件分組,以便一起算繪,或以最少的昂貴狀態切換完成。
2D 批次處理(Batching)
在 2D 算繪中,逐一處理每個物件的成本可能極高——畫面上很容易就有上千個物件。這也是為什麼在基於 OpenGL 的算繪中會使用 2D 批次處理(Batching)。多個相似物件會合併成一個批次,僅用一次繪圖呼叫(draw call)一起算繪,而不是每個物件都單獨呼叫一次。此外,這也能將狀態切換、材質與紋理變更降至最低。
基於 Vulkan 的算繪目前尚未採用 2D 批次處理。由於 Vulkan 的繪圖呼叫成本遠低於 OpenGL,因此對於 2D 批次處理的需求較低(但在某些情境下仍可能帶來效益)。
3D 批次處理(Batching)
在 3D 算繪中,我們同樣希望減少繪圖呼叫與狀態切換。不過,將多個 3D 物件合併成單一繪圖呼叫會更困難。3D 網格通常包含數百到數千個三角形,實時合併大型網格的成本極高。隨著每個網格三角形數增加,合併的效益很快就被成本抵銷。一個更好的做法是**事先將網格合併**(即靜態物件之間的合併),可以由美術人員在 DCC 工具中完成,也可在 Godot 內透過外掛程式自動化。
3D 批次處理也有其代價。多個物件合併算繪後,無法單獨做遮蔽剔除(culling)。例如如果一整座在螢幕外的城市被合併到一片在螢幕上的草地,全部都會被算繪。因此,在批次處理 3D 物件時,務必考慮物件位置與遮蔽剔除。儘管如此,靜態物件事先合併在多數情況下帶來的效益仍大於缺點,尤其適用於大量遠距或低多邊形物件。
如需更多 3D 特定最佳化資訊,請參考 3D 效能最佳化。
重複利用著色器與材質
Godot 的算繪器設計與其他引擎略有不同,目標是盡量減少 GPU 狀態切換。 StandardMaterial3D 能在需要相似著色器時有效重複利用材質。如果你使用自訂著色器,也請儘可能進行重複利用。Godot 的優化優先順序如下:
重複利用材質: 場景中材質種類越少,算繪速度就越快。如果場景中物件數量龐大(數百到數千),應盡可能共用材質。最糟情況下,可使用圖集(atlas)來減少紋理切換次數。
重複利用著色器: 如果材質無法共用,至少要嘗試共用著色器。注意:只要 StandardMaterial3D 設定相同(例如核取方塊開關的功能),即使參數不同,Godot 也會自動共用同一份著色器。
假設一個場景有 20,000 個物件、每個物件都用不同材質,算繪效能會非常低。如果同樣的場景只有 100 種材質共用,算繪就會快許多。
像素成本 vs 頂點成本
你可能聽說過模型的多邊形數越少,算繪速度就越快。這事實上只是 相對而言,還牽涉許多其他因素。
在現代 PC 與主機上,頂點成本其實很低。早期的 GPU 只算繪三角形,這意謂著每個影格:
所有頂點都必須由 CPU 處理(包括裁切/clipping)。
所有頂點都必須從主記憶體傳送到 GPU 記憶體。
現在這一切都在 GPU 內部處理,效能大幅提升。3D 美術常誤以為多邊形數會嚴重拖慢效能,是因為 3D 建模軟體(如 Blender、3ds Max 等)為了編輯方便,必須將幾何資料留在 CPU 記憶體,導致效能較差。遊戲引擎則大多將運算交由 GPU,因此可更高效地算繪大量三角形。
在行動裝置上則不同。PC 和主機的 GPU 可以無限制地從電網取得電力,屬於高效能型;而行動 GPU 受限於小型電池,必須極度講究效能與耗能。
為了提升效率,行動 GPU 會盡量避免 重複繪製(overdraw)。所謂 overdraw,是指螢幕上同一像素被多次算繪。例如一個小鎮有多棟建築,GPU 無法預先知道哪些遮擋哪些,必須先算繪後判斷。假設一棟房子先被畫出來,後又有另一棟房子在前方遮住,導致同一像素被算繪兩次。PC GPU 通常不太在意這點,只要加更多像素處理器來彌補(但會消耗更多電力)。
在行動裝置上無法無限制用電,因此大多採用 圖塊式算繪(Tile-based rendering) 技術,將螢幕劃分為網格,每個格子會記錄哪些三角形要繪製,並根據深度排序以減少 overdraw。這種技術提升了效能並降低功耗,但會犧牲頂點處理效能,導致每次可算繪的頂點與三角形數量變少。
此外,圖塊式算繪在螢幕局部有大量幾何細節的小物件時效率會大幅下降。這會造成單一圖塊負載過重,其他區域必須等待該圖塊算繪完成才能顯示影格,導致整體效能降低。
總結來說,在行動裝置上不用太擔心頂點總數,而是要**避免頂點過度集中在螢幕某一小區域**。若角色、NPC、車輛等距離很遠(在螢幕上很小),應降低 LOD(細節層級)。即使在桌面端,也避免讓三角形小於螢幕一個像素的大小。
使用以下功能時,請留意額外的頂點運算成本:
蒙皮(骨骼動畫)
變形(形狀鍵)
頂點光照物件(行動裝置常見)
像素/片元著色器與填充速率(Fill Rate)
相較於頂點運算,片元(像素)著色的成本近年來大幅提升。螢幕解析度從 640×480 VGA(僅 307,200 像素)躍升到 4K(8,294,400 像素),面積暴增 27 倍!加上現代物理式算繪(PBR)導致片元著色器極度複雜,每個像素都需繁複運算。
你可以很容易測試專案是否受填充速率限制。請先關閉 V-Sync(避免 FPS 被上限限制),然後比較大視窗與極小視窗下的 FPS。如果有用到陰影,試著減小陰影貼圖尺寸。通常你會發現小視窗時 FPS 大幅提升,代表你受限於填充速率;若提升有限,瓶頸則在其他地方。
若專案受限於填充速率,可透過減少 GPU 負擔來提升效能,比如簡化著色器(若是 StandardMaterial3D,可關閉高成本選項)、減少紋理數量與尺寸。若粒子不是 Unshaded,建議在材質中強制使用頂點著色模式(vertex shading),以降低著色成本。
也參考
在支援的硬體上,可利用 可變速率著色 降低著色運算成本,同時又不會影響最終影像的邊緣銳利度。
針對行動裝置開發時,建議盡可能使用最簡單的著色器。
紋理讀取
片元著色器的另一個成本在於紋理讀取。讀取紋理本身就很耗效能,若在單一著色器中讀取多張紋理則成本更高。另外,紋理過濾(如 mipmap 間的三線性過濾與平均)也會進一步拖慢速度。對於行動裝置,紋理讀取除了效能外,也會顯著增加耗電。
如果你使用第三方著色器或自行撰寫著色器,請盡量選擇需要最少紋理讀取的演算法。
紋理壓縮
Godot 預設在匯入 3D 紋理時會採用顯示記憶體(VRAM)壓縮。這種壓縮儲存體積雖然不如 PNG 或 JPG 有效,但在繪製大型紋理時能大幅提升效能。
這是因為紋理壓縮的主要目標是減少記憶體與 GPU 間的頻寬消耗。
在 3D 算繪中,物件的形狀主要取決於幾何形狀,因此壓縮對畫面影響較小;而在 2D,紋理內的細節與形狀較重要,所以壓縮產生的失真與雜訊會更明顯。
請注意,大多數 Android 裝置不支援帶透明度的紋理壓縮(僅支援不透明),請務必留意。
備註
即使在 3D 場景下,「像素美術」紋理也應停用 VRAM 壓縮,因為壓縮會明顯影響畫面品質,而低解析度本身並不會帶來顯著效能提升。
後製處理與陰影
後製特效與陰影在片元著色階段同樣非常耗資源。務必在不同硬體上測試這些效果對效能的影響。
降低陰影貼圖(shadowmap)大小能明顯提升效能,無論是寫入還是讀取都一樣。此外,最佳化陰影效能最有效的方法,就是盡量減少有陰影的燈光與物件。對於較小或較遠的全域光(OmniLight)/聚光燈(SpotLight),通常可以直接關閉陰影而對畫面影響極小。
透明與混合
透明物件對算繪效能有特殊挑戰。不透明物件(特別在 3D 裡)可以用任何順序算繪,Z 緩衝區會自動保證最前面的物件被正確著色;但透明或混合物件則無法用 Z 緩衝排序,必須改用「畫家演算法」(painter's order,從後往前繪製)才能正確顯示。
透明物件對填充速率的影響也特別嚴重,因為每個圖層都要被算繪,即使後面還會被其他透明物件覆蓋。
不透明物件則不會有這個問題,因為可以利用 Z 緩衝區,先寫入 Z 值,再只對最前方(勝出)的像素進行片元著色器運算。
當多個透明物件重疊時,效能成本尤其高。通常,應盡量縮小透明區域以減低填充速率需求,這點在行動裝置上尤其重要。許多情況下,直接算繪較複雜的不透明幾何體,反而比用透明材質「取巧」來得有效率。
跨平台建議
如果你計畫跨平台發行,請務必 及早 、 頻繁 地在所有目標平台進行測試,特別是行動裝置。只在桌面端開發,最後才臨時移植到行動裝置,幾乎注定會遇到災難。
一般來說,應以最低通用規格設計遊戲,再針對高階平台額外加入進階特效。例如,若同時支援桌面與行動平台,可考慮都採用「相容性算繪模式」來確保效能。
行動裝置/圖塊式算繪器
如前所述,行動裝置的 GPU 運作機制與桌機大不相同。多數行動裝置採用圖塊式算繪器(Tile Renderer),將螢幕劃分為固定大小的區塊並存入快取,以減少對主記憶體的讀寫次數。
但圖塊式算繪也有缺點,某些技術會變得更複雜且低效。若圖塊之間需要互相依賴(如跨圖塊運算)或必須保留前一輪算繪結果,效能會大幅降低。因此,請務必仔細測試著色器、視窗紋理與後製效果在行動裝置上的效能表現。