Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

最佳化

前言

對新的圖形功能和進步的需求幾乎可以保證你必會遇到圖形瓶頸. 有些瓶頸可能出現在CPU端, 例如在Godot引擎內部的計算中, 為渲染準備對象. 瓶頸也可能發生在CPU上的圖形驅動中, 它將指令分類傳遞給GPU, 以及這些指令的傳輸過程. 最後, 瓶頸也會發生在GPU本身.

渲染中的瓶頸發生在哪裡, 高度依賴於硬體. 特別是移動GPU可能會在桌面上輕鬆運作的場景中掙扎.

瞭解和調查GPU瓶頸與CPU上的情況略有不同. 這是因為, 通常情況下, 你只能通過改變你給GPU的指令來間接改變性能. 另外, 測量起來可能更困難. 在許多情況下, 衡量性能的唯一方法是通過檢查每影格渲染時間的變化.

繪製呼叫、狀態更變、API

備註

以下部分與最終使用者無關, 但對於提供與後面章節相關的背景資訊是有用的.

Godot通過圖形API(OpenGL, OpenGL ES或Vulkan)向GPU發送指令. 所涉及的通信和驅動活動可能非常昂貴, 尤其是在OpenGL和OpenGL ES中. 如果我們能以驅動和GPU喜歡的方式提供這些指令, 就能大大提高性能.

OpenGL中幾乎每一個API命令都需要一定的驗證, 以確保GPU處於正確的狀態. 即使是看似簡單的命令, 也會導致一連串的幕後工作. 因此, 我們的目標是將這些指令減少到最低限度, 並盡可能地將相似的物件群組, 以便它們可以一起渲染, 或者以最少的數量進行這些昂貴的狀態變化.

2D 批次處理

在2D中, 單獨處理每個專案的成本可能會非常高--螢幕上很容易有成千上萬的專案. 這就是為什麼使用2D 批次處理 的原因. 多個類似的專案被歸為一組, 並通過一個單一的繪製呼叫進行批量渲染, 而不是對每個專案進行單獨的繪製呼叫. 此外, 這意味著狀態變化, 材質和紋理變化可以保持在最低限度.

基於 Vulkan 的渲染方法尚不使用 2D 批次。由於與 OpenGL 相比,Vulkan 的繪製呼叫要便宜得多,因此不太需要 2D 批次(儘管在某些情況下它仍然是有益的)。

3D 批次處理

在3D中, 我們的目標仍然是儘量減少繪製呼叫和狀態變化. 然而, 將多個物件批量合併到一個繪圖呼叫中可能比較困難.3D網格往往由數百個或數千個三角形組成, 而即時組合大型網格的成本非常高. 隨著每個網格的三角形數量的增加, 加入它們的成本很快就超過了帶來的好處. 一個更好的選擇是 提前加入網格 (靜態網格之間的關係). 這可以由設計師完成, 或者在Godot中以程式設計方式完成.

在3D中把物體批次處理在一起也是有成本的. 幾個物件渲染成一個, 就不能單獨剔除. 如果將螢幕外的整座城市與螢幕上的一片草地連接在一起, 那麼它仍然會被渲染. 因此, 當試圖將3D物件批量連接在一起時, 應該始終考慮到對象的位置和剔除. 儘管如此, 加入靜態物件的好處往往大於其他考慮因素, 特別是對於大量的遠距離或低多邊形物體.

更多有關光照烘焙的資訊,請參考 doc_baked_lightmaps

重複使用著色器和材質

Godot 渲染器和其它的渲染器不同,是以儘量減少 GPU 狀態更改為目標的。 SpatialMaterial 可以在所需著色器相似時很好地複用材質。如果是用自訂著色器,那麼請儘量進行複用。Godot 的優先順序是:

  • **複用材質:**場景中不同的材質越少, 渲染的速度就越快. 如果一個場景有大量的物體(數以百計或數以千計), 可以嘗試重複使用這些材質. 在最壞的情況下, 使用合集來減少紋理變化的數量.

  • Reusing Shaders: If materials can't be reused, at least try to reuse shaders. Note: shaders are automatically reused between StandardMaterial3Ds that share the same configuration (features that are enabled or disabled with a check box) even if they have different parameters.

例如, 如果一個場景有 20,000 個物體, 每個物體有 20,000 種不同的材質, 渲染會很慢. 如果同一個場景有 20,000 個物體, 但只使用 100 種材料, 渲染就會快很多.

像素成本與頂點成本

你可能聽說過, 一個模型中的多邊形數量越少, 它的渲染速度就越快. 這其實是 相對的 , 取決於許多因素.

在現代PC和控制台, 頂點成本很低.GPU最初只渲染三角形. 這意味著每一影格:

  1. 所有頂點都必須由 CPU 進行轉換(包括剪裁)。

  2. 所有頂點都必須從主 RAM 發送到 GPU 記憶體。

現在, 所有這些都在GPU內部處理, 大大提高了性能. 三維藝術家通常對多維性能有錯誤的感覺, 因為三維DCC(如Blender, Max等)需要將幾何圖形保存在CPU記憶體中進行編輯, 從而降低了實際性能. 遊戲引擎更依賴GPU, 所以它們可以更有效地渲染許多三角形.

在移動裝置上, 情況則不同. 個人電腦和控制台的GPU是粗暴的怪物, 可以從電網中獲取所需的電力. 移動GPU被限制在一個很小的電池裡, 所以它們需要更高的功率效率.

為了提高工作效率, 移動GPU試圖避免 overdraw . 當螢幕上的同一個像素被渲染了不止一次時, 就會出現Overdraw. 想像一下, 一個有幾座建築的小鎮. 在繪製之前,GPU不知道哪些是可見的, 哪些是隱藏的. 例如, 一棟房子可能被畫出來, 然後在它前面又畫了一棟房子(這意味著同一像素的渲染發生了兩次).PC GPU通常不怎麼關心這個問題, 只是把更多的像素處理扔給硬體以提高性能(這也會增加功耗).

在移動裝置上使用更多的電力是不可能的,所以移動裝置使用一種叫做*基於圖塊的渲染*的技術,將螢幕劃分為一個網格。每個儲存格都保存著繪製的三角形列表,並按深度進行排序,以儘量減少*過度繪製*。這種技術提高了性能,降低了功耗,但對頂點性能造成了影響。因此,可以處理更少的頂點和三角形進行繪製。

一般來說, 這並不是那麼糟糕, 但在移動裝置上有一個必須避免的特殊情況, 即在螢幕的一小部分內具有大量幾何形狀的小物體. 這迫使移動GPU在單個螢幕單元上用很大的力氣, 大大降低了性能(因為所有其他單元必須等待它完成才能顯示該影格).

總而言之, 在移動端不要擔心頂點數量, 但 避免頂點集中在螢幕的一小部分 . 如果一個角色, NPC, 車輛等離得很遠(這意味著它看起來很小), 就使用一個較小的細節級別模型(LOD). 即使在桌面GPU上, 最好也不要讓三角形小於螢幕上一個像素的大小.

使用時要注意額外的頂點處理:

  • 蒙皮(骨骼動畫)

  • 變形(形態鍵)

像素/片段著色器和填充速率

與頂點處理相比, 片段著色器(每像素)的成本在這些年裡急劇增加. 螢幕解析度提高了(4K螢幕的面積是829400像素, 而老式640×480 VGA螢幕的面積是307200, 是27倍), 但片段著色器的複雜度也爆炸式增長. 基於物理的渲染需要對每個片段進行複雜的計算.

你可以很容易地測試一個專案是否受到填充率限制. 關閉V-Sync以防止每秒影格數的上限, 然後比較使用大視窗運作時的每秒影格數和使用非常小的視窗運作時的影格數. 如果使用陰影, 你也可以從同樣減少陰影貼圖大小中獲益. 通常, 你會發現使用小視窗的FPS會增加不少, 這說明你在某種程度上受到了填充率的限制. 另一方面, 如果FPS幾乎沒有增加, 那麼你的瓶頸就在其他地方.

你可以通過減少 GPU 的工作量來提高填充率限制專案的性能。你可以通過簡化著色器(如果你使用的是 SpatialMaterial,也許可以關閉昂貴的選項),或者減少使用的紋理數量和大小來實作。

也參考

在支援的硬體上,可以使用 變數型別轉換 降低著色過程的損耗,並且不影響最終圖片邊緣的銳度。

在針對移動裝置時, 考慮使用你能合理負擔得起的最簡單的著色器.

匯入紋理

片段著色器的另一個因素是讀取紋理的成本。讀取紋理是一項昂貴的操作,尤其是在一個片段著色器中從多個紋理中讀取時。另外,考慮到篩選可能會進一步減慢它的速度(mipmap 之間的三線性篩選,以及平均)。讀取紋理在功耗方面也很昂貴,這在手機上是個大問題。

如果您使用協力廠商著色器或編寫自己的著色器, 請儘量使用需要盡可能少的紋理讀取的演算法.

紋理壓縮

預設情況下,Godot在匯入3D模型時使用影片RAM(VRAM)壓縮來壓縮紋理. 影片RAM壓縮在儲存時不如PNG或JPG有效, 但在繪製足夠大的紋理時, 會極大地提高性能.

這是因為紋理壓縮的主要目標是在記憶體和GPU之間減少頻寬.

在3D中, 物體的形狀更多地取決於幾何體而不是紋理, 所以壓縮一般不明顯. 在2D中, 壓縮更多的是取決於紋理內部的形狀, 所以2D壓縮產生的偽影比較明顯.

作為警告, 大多數Android裝置不支援具有透明度的紋理的紋理壓縮(僅不透明), 因此請記住這一點.

備註

即便在 3D 中,“像素畫”紋理也應該禁用 VRAM 壓縮,因為壓縮會對外觀產生負面影響,較低的解析度也無法得到顯著的性能提升。

後處理:

就片段著色活動而言, 後期處理效果和陰影也可能很昂貴. 始終測試這些對不同硬體的影響.

減少陰影圖的大小可以提高性能 , 無論是在寫還是讀取陰影貼圖方面. 除此之外, 提高陰影性能的最好方法是關閉盡可能多的燈光和物體的陰影. 較小或較遠的OmniLights/SpotLights通常可以禁用它們的陰影, 而對視覺影響很小.

透明度和混合

透明物體對渲染效率帶來了特殊的問題. 不透明的物件(尤其是在3D中)基本上可以以任意順序渲染,Z-緩衝區將確保只有最前面的物件得到陰影. 透明或混合物件則不同, 在大多數情況下, 它們不能依賴Z-緩衝區, 必須以 "畫家順序"(即從後到前)渲染才能看起來正確.

透明物件的填充率也特別差, 因為每一個專案都要繪製, 即使之面會在上面繪製其他透明物件.

不透明的物件不需要這樣做. 它們通常可以利用Z-緩衝區, 只先向Z-緩衝區寫入資料, 然後只在 "勝利" 的片段上執行片段著色器, 也就是在某一像素處處於前面的物件.

在多個透明物件重疊的情況下, 透明度特別昂貴. 通常情況下, 使用透明區域越小越好, 以儘量降低這些填充率要求, 尤其是在移動端. 事實上, 在很多情況下, 渲染更複雜的不透明幾何體最終可能比使用透明度來 "作弊" 更快.

多執行緒

如果您的目標是在多個平臺上發行, 請在您的所有平臺上(尤其是移動平臺)上進行 早期經常 性測試. 在桌面上開發遊戲, 但試圖在最後一刻將其移植到移動裝置, 這是災難的根源.

一般來說, 你應該從最底的共性設計你的遊戲, 然後為更強大的平臺新增可選的增強功能. 例如, 你可能希望在同時針對桌面和移動平臺的情況下, 同時使用GLES2後臺.

移動端和圖塊渲染

如上所述, 移動裝置上的GPU與桌面上的GPU工作方式有很大不同. 大多數移動裝置都使用圖塊渲染器. 圖塊渲染器將螢幕分割成規則大小的圖塊, 這些圖塊可以放入超快的快取中, 從而減少了對主記憶體的讀和寫操作次數.

不過也有一些缺點. 圖塊渲染會讓某些技術變得更加複雜, 執行起來也更加昂貴. 依賴於不同圖塊渲染的結果, 或者依賴於早期操作的結果被保存的圖塊可能會非常慢. 要非常小心地測試著色器, 視圖紋理和後期處理的性能.