Render Hell —— 史上最通俗易懂的GPU入門教程(四)

聲明:文本非原創,只是翻譯,原文鏈接如下:
https://simonschreibt.de/gat/renderhell-book4/

Render Hell – Book IV

在這裏插入圖片描述
這下越來越有趣了!本篇我將向大家介紹一些我在研究過程中發現的一些解決方法,希望大家能夠了解如何去優化資源,以使其具有良好的可渲染性。

1. 排序(Sorting)

首先,在填充命令緩衝區之前,你可以對所有的命令進行排序(例如按 Render State 進行排序),這樣就可以大大減少那些不必要的 Change State 命令,因爲在切換狀態之前,你已經遍歷了所有使用相同 Render State 的網格。

在這裏插入圖片描述
但是,如果挨個挨個的渲染每個網格,那麼你仍然會面臨大量的開銷。爲了減少這種開銷,有一種叫做 批處理 的技術似乎很有用。

2. 批處理(Batching)

在對網格進行排序時,你可以將它們堆成相同的堆,下一步便是立即告訴 GPU 去渲染這樣一個堆。以下是關於批處理的定義:

“批處理是指在調用 API 渲染網格之前,先將多個網格組合在一起。這就是爲什麼渲染一個大網格要比渲染多個小網格所花費的時間更少的原因” [a36]

因此,不要爲每個(使用相同 Render State 的)網格都添加一個 Draw Call …

在這裏插入圖片描述
… 而應該將它們組合起來並封裝成一個 Draw Call 來進行渲染。這是一個非常有趣的話題,因爲只要使用相同的 Render State(換句話說使用相同的材質參數),就可以同時渲染不同的網格(石頭、椅子或寶劍)。

在這裏插入圖片描述
值得一提的是,在系統內存(RAM)中合併網格,然後將合併後的大網格拷貝到顯存(VRAM)上,這是要花時間的!因此,批處理對於靜態對象(例如石頭、房屋等)是很有用的,這些靜態對象只需合併一次,就可以長時間保留在內存中了。
你也可以將動態對象(例如空間遊戲中的激光子彈)批處理在一起,但因爲它們是在不斷運動的,你就不得不爲每一幀創建子彈雲網格併發送到 GPU 顯存上!

另一個需要注意的地方(感謝 koyima 提醒):如果一個對象不在攝像機的視錐體範圍內,你可以直接將它剔除掉(即在渲染時忽略它)。但是,如果將多個對象批處理在一起,那麼在渲染的時候就不得不將整個大的新網格都考慮進去(即使其中只有一小部分是最終可見的),這在某些情況下這可能會降低 GPU 性能。

其實,有一種更好的方法用來處理動態對象,那就是實例化

3. 實例化(Instancing)

實例化的意思就是,你只需要發送一個網格(例如激光子彈)而不是多個,並讓 GPU 對其複製多次。但這樣我們只會得到位置完全相同、且採用相同旋轉或動畫的一組對象,那就沒啥意思了。因此你需再要提供一個額外的數據流,如變換矩陣,這樣就可以將那些副本渲染到不同的位置(和不同的姿勢)了。

“每個實例的典型屬性包括模型到世界的變換矩陣、實例的顏色以及一個骨骼蒙皮動畫播放器。”[a37]

別跟我說了,但據我所知,這個數據流只不過是 RAM 中的一個列表,GPU 也可以訪問這段內存。

這也使得每種類型的網格只對應一個 Draw Call!與批處理相比,它們的不同之處在於,所有實例化的網格看起來都長的一樣(因爲它們是同一網格的副本),而批處理的網格則由多個不同的網格組成,只要它們使用同一個 Render State 參數。

在這裏插入圖片描述
接下來開始變得更具創造性了,即使以下技巧只適用於某些特殊場景,我也覺得它們真的很酷:

4. 多材質着色器(Multi-Material-Shader)

因爲一個着色器可以訪問多個紋理,所以它不僅可以有一個漫反射 / 法線 / 鏡面反射 /… 貼圖,還可以有兩個(或更多)紋理 —— 換句話說,你可以在一個着色器中使用兩種材質,這些材質通過 混合紋理(blend-texture) 進行彼此混合。當然,這會增加 GPU 的功耗,因爲混合操作本身的功耗就很高,但它減少了 Draw Call 的次數,因爲含有兩種或兩種以上材質的網格將不再被大卸八塊(如之前在 “4. 網格與多種材質” 中的描述)。

點擊此處 閱讀更多關於多材質着色器的相關信息。
在這裏插入圖片描述該文章表明,與這種高代價的技術相比,使用相對較多的 Draw Call 仍然是一個不錯的選擇。不管怎樣,我覺得挺有意思的,如果你只是想要得到一些喜人的統計數字,那你可以反駁說多層材質降低了 Draw Call 的調用次數(即使這裏討論的並不是性能問題 … 但是,噓!)。

5. 骨骼蒙皮技術(Skinned Meshes)

你還記得我說過的激光子彈嗎?我說過這個網格必須每幀都更新一次,因爲子彈是在不斷運動的。但是將它們進行批處理然後每幀都發送新的網格,這代價確實有點大。
解決該問題的一個有趣方法是,自動爲每顆子彈添加一個 骨骼(bone),並提供 蒙皮信息(skinning information)。有了它,你將擁有一個大網格,該網格可以保留在內存中,而你只需要更新每一幀的骨骼數據即可。當然,如果發射了一顆新子彈或銷燬了一顆舊子彈,你就不得不創建一個新的網格。但對我來說這真的是一個很有趣的思路。

點擊此處 瞭解更多關於骨骼蒙皮技術的相關信息。

6. 減少過度渲染(Reduce Overdraw)

Alex 提到了一個很好的方法,他在他的 App 中使用該方法來避免渲染一個暗角效果圖時出現的過度渲染問題:

爲了減少全屏暗角效果(通常用全屏四邊形和半透明紋理來製作)的過度渲染問題,我們使用內部中空(即100%透明)的四邊形網格,並使用頂點顏色而不是紋理。這樣能產生更好看的暗角效果(沒有紋理壓縮僞影)和更好的性能(更少的過度渲染 —— 只會在不透明的區域進行繪製)。
—— Alex / Axiomworks

在這裏插入圖片描述
CryEngine 文檔 也談到了這一點,另外 這篇討論 也非常值得一讀。

7. 以及更多的魔法

圖形編程是在不斷變化的,API 和硬件都在不斷的發展,編寫高效的渲染引擎用於處理大量的用例已經不是一件容易的事了,它需要大量複雜的工程。幸運的是,許多遊戲開發人員樂於分享他們的經驗總結,人們可以在各種活動或開發網站上找到有關特效或渲染架構方面的有趣資料(siggraph 高級實時渲染(real-time rendering)課程、虛幻引擎(unreal engine)文檔和源碼、寒霜引擎(frostbite engine)的 PPT …),所有主流硬件廠商通常也有關於如何充分利用圖形 API 以及他們硬件的文檔、示例代碼或 PPT。

如今的好處是,多虧了大量的渲染中間件,許多繁重的工作都已經幫我們處理完了。因此,對於美術師來說,主要還是要清楚他們在製作內容時,實際需要關注哪些優化,因爲有些事情可能是在幕後完成的。

在這裏插入圖片描述請隨時給我發送更多關於減少 Draw Call 的創造性解決方案的鏈接!

差不多就這些了!現在,您應該對如何更快地渲染資源有一個大致的理解了。別擔心,下一篇文章會很短。


本篇到此結束。

在這裏插入圖片描述繼續閱讀下一篇:總結,或返回目錄索引

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章