前言
遊戲中物體的加載和物體的渲染很多時候是獨立又相關的兩件事,兩者既有很多完全不同的關注點,又都需要對最終呈現的效果負責。出現性能問題時,也需要單獨分析加載和渲染兩個方面。
加載更多是一個面向CPU和內存的課題,因為內存大小限制和一定程度緩解渲染的壓力,所以需要流式加載(Streaming)。流式加載也在不斷優化和演變,例如演變出分塊加載、按需加載等策略(例如《黑暗靈魂》系列遊戲中祖傳的按攝像機範圍加載)。
而渲染一定程度上是一個CPU和GPU結合的課題(隨著硬件發展越來越是一個GPU的課題)。傳統渲染過程中先會計算一下每個物體的多邊形的矩陣變換,後續又會計算每個像素的著色,最後可能還會對整體畫面進行各種後處理;這個過程的各個步驟都可以進行一些分佈式的計算,充分利用硬件多線程的優勢。(最新的渲染流程會把物體打碎成三角面進行各種處理,但沒有廣泛應用,本人也沒有實際研究過這種管線,就還是介紹偏傳統的)
細節層級(LOD——Level of Details)是一個既涉及加載也涉及渲染的課題,這個方案更關注物體在遠近不同時的顯示狀態,但是理解上不復雜,篇幅原因本文裡不展開了。
在遊戲引擎中,判斷物體是否需要顯示的方法就被稱為剔除(Culling)。剔除算法既可以影響按需加載,也可以影響渲染(本文主要以影響渲染的方面進行展開)。
(圖片展示了製作草地剔除的一個經典學習案例。左邊是可見性預覽,不是實際加載的物體數量)
1 視錐體剔除(Frustum Culling)
最初能想到的肯定是按照攝像機可視範圍進行剔除。遊戲引擎中攝像機主要包含錐體和長方體兩種視口,分別被稱為透視攝像機(Perspective)和正交攝像機(Orthographic)。(某種意義上視錐體剔除這個翻譯不那麼準確,知道意思就好)
(圖中是一個透視攝像機的六面範圍)
比較物體時肯定不能和每個三角面都進行計算,為此引擎引入了包圍盒(Bounding Box)
和軸向包圍盒(AABB—— Axially Aligned Bounding Box)這2種用於物體比較計算的中間結構,犧牲精度以大幅提高性能。前者可以在模型導入時就預計算生成,後者——如果是靜態物體(Static)可以在模型編輯入場景時按照其當時的旋轉與縮放進行計算。軸向包圍盒主要用於減少乘除計算步驟,以提升性能。
(圖中展示了幾種不同的包圍盒)
動態物體(Dynamic)可以有不同的策略,最省心的就是不去剔除動態物體,最精確的就是每幀實時更新包圍盒。介於兩者之間的,如果物體不會頻繁的變化其旋轉與縮放值,則可以在發生變化時再更新包圍盒。(事實上由於包圍盒往往要放到一個層級加速架構裡計算,所以早期這種更新也不是那麼容易,計算量比想象的會大很多)
(圖中演示了Unity中視錐體透視的效果。左邊是可見性預覽,不是實際加載的物體數量)
有了包圍盒就可以從進行射線檢測(Ray Casting)和平面比較了。簡單來說,射線檢測可以判斷攝像機發出的一根射線與某個包圍盒的相交情況;平面比較可以判斷一個點與一個平面的位置關係,判斷6個端點則可以判斷出一個物體的包圍盒與目標平面的相交關係(還有別的方法,這個是相對好理解的)。
通過計算攝像機視錐體的六個平面(包含近平面和遠平面)與包圍盒的位置關係,就可以判斷物體是否在攝像機範圍內,以實現視錐體剔除了。
這裡面有各種細節例如怎麼計算、怎麼設計加速數據結構(主要是把大量物體分層組織起來,避免很多不必要的查詢),有興趣可以去看B站Games101課程,這裡就不展開了。
現在的主流引擎會自帶視錐體剔除功能,基本不需要做什麼配置在運行時就會自動剔除了。
2 遮擋剔除(Occlusion Culling)
(圖中演示了Unity 中的遮擋剔除效果)
在視錐體範圍內的物體,對於類似開放世界追尾視角的攝像機還往往需要進行遮擋剔除,即排除那些被完全擋住的物體不進行渲染。
遮擋剔除的核心肯定就是可見性檢測算法了,單個物體肯定還是基於射線檢測與平面比較的;對於多個大量物體,有很多優化方案,傳統的例如物體劃分、空間劃分等等——整體思路都是預計算出一個加速結構,把部分固定計算結果存儲下來,用來減少運行時的計算步驟;運行時直接查詢這個加速結構,從樹狀數據中儘量上層的部分開始剔除。
比較老牌的遮擋剔除中間件(例如Umbra)需要定義遮擋物與被遮擋物,以及前面提到的——物體是靜態物體還是動態物體。如果都是靜態物體,則其相對遮擋關係可以預計算並存儲下來,這一過程往往被稱為烘培(Bake)了遮擋數據;運行時在確認了攝像機位置後,算法就可以依據遮擋數據返回剔除結果了。另外需要說明,剔除的都是被遮擋物,所以場景編輯時需要一定人工來保證劃分正確。
(圖中展示了遮擋烘焙數據的一種組織方式,本身也是一種空間劃分方式)
越新的遮擋剔除算法對動態物體的支持就越好,畢竟整體算力也提升了,很多東西可以放到GPU實時計算(誕生了叫做ComputeShader的東西,現在都有AI了多少都不難理解GPU純運算這回事了)。
另外,被檢測物體包圍盒也不一定都是和其它物體的包圍盒比較了,有些可以和物體在深度緩衝(下一個話題會提到)中的深度值(也叫Z值)進行換算比較,以提高精度。想象一個大山中間有一個小洞,如果是包圍盒模式會額外剔除小洞能看到的一些東西——當然也可以通過拼接包圍盒的方式來規避這個問題,這裡只是舉個例子。
遮擋剔除對於性能的貢獻是因時而異的。如果畫面前有一座山,山後面的物體都是被遮擋的小物體,那麼這類剔除肯定益處很大;但如果畫面上主要是平地和很多稀疏的植物和草地,則在性能範圍內幾乎不必要進行遮擋剔除(可能算了也白算,性能搞不定時往往用地表景觀範圍之類別的方案來替代了)。當然這裡的例子也不是絕對的,由於距離的影響,靠近攝像機的很小的物體也可能成為巨大的阻擋物(一葉障目)。
在很長的時間裡,把物體遮擋數據配置對、烘焙好,都是場景美術和技術美術的一項繁重的工作。大家熟知的《巫師3》中就是運用的預計算好的遮擋數據,用Umbra3中間件進行剔除(參考標題圖片)。
在物體級別進行了剔除後,其結果往往還是偏保守的(寧可放過不能錯殺)。要進一步提升性能,在具體繪製每一個像素時,需要在繪製前排除不必繪製的物體片元(Fragment ),這就引入了後面一個話題——深度的緩衝與比較。
3 像素繪製與深度緩衝
(對照圖中下圖即為深度緩衝可視化的結果,把距離歸一化後0-1的值換算為灰度預覽出來)
1)像素著色的預判斷
把遊戲一幀畫面想象成一個畫布,可以想象的渲染順序無非是兩種——從遠到近或從近到遠(從遠到近也被成為畫家算法)。這裡的遠近可以只依賴物體的中心點或錨點來判斷,那麼依據其距離攝像機的位置就可以進行排序了。
在沒有任何優化的年代,很多遊戲繪製物體就是這麼從遠到近一個一個來繪製的。繪製時需要計算並著色物體在屏幕對應的每一個像素的顏色。
可以想象,如果物體是不透明(Opaque)的,當物體的一個像素被距離攝像機更近的物體的另一個像素覆蓋時,前一個像素的繪製開銷就是純浪費——物體疊在一起的部分越多繪製就越浪費。(透明裁剪和半透明離今天的話題更遠,就不展開了。簡單理解就是透明裁剪和半透物體在這一步驟沒辦法用深度緩衝來優化)
在圖形學和硬件共同發展的過程中,幀緩衝(FrameBuffer)這一方案被提出了,其中就包含深度緩衝(Depth Buffer或Z-Buffer)。其基本思想是緩存下一幀中每一個像素對應的深度,如果下一個需要繪製的像素深度比較大(以歸一化之後近處0遠處1來算),則可以不進行繪製。
2)深度緩衝與物體遮擋剔除
前面也提到,物體包圍盒在計算剔除時可以和深度緩衝來比對。包圍盒可大可小,往往不需要和包圍盒範圍覆蓋的每個像素的深度值都進行比對,此時就需要生成不同精度範圍的深度緩衝。例如,一個包圍盒覆蓋原始尺寸約400像素範圍的深度緩衝區,其實只用和其中最大深度比較即可;查詢時,先用屏幕分辨率和物體像素範圍換算一個合適的緩衝圖層級,再遍歷其中覆蓋的所有格子的深度值。
(多層深度緩衝的效果,比較時以被檢測物體的尺寸選擇一個合適的分辨率進行比對,性能最好。這種多層結構也被稱為mip,這裡不展開了)
(圖中說明了多層深度緩衝其實存儲的是最大深度值,數字只是示意便於理解)
有了這個多層結構,只需要選取合適的分辨率,比對一定範圍的深度最大值即可決定物體是否要剔除。這裡有個小問題,就是第一幀渲染沒有深度緩衝用來剔除,是否需要特殊處理一下以提高性能,這個有一些辦法解決,對這個課題做原理上的把握不需要關注這個點。
4 景深效果(Depth of Field)
由於深度緩衝可以計算像素的遠近,傳統渲染管線裡還可以用來實現景深效果。
(圖中的遠景部分通過分層製作了景深效果)
景深其實是攝影設備拍攝才會有的現象,表現為一種基於距離的清晰和模糊範圍。由於攝影是光線經過透鏡折射後記錄到傳感器的結果,光圈、焦距、及焦平面到拍攝物的距離是都影響景深的重要因素。
(景深原理一圖流。簡單來說就是景深範圍外的物體無法聚焦到光傳感器所在平面上的一點裡)
由於人眼比較接近一類可變透鏡結構,又是雙眼同時定位的模式,所以正常的人眼看物體感官上是不會有景深效果的。遊戲中製作景深效果可以提高類似影片的質感,也能讓畫面的主體更突出。
結合前面提到的深度緩衝,如果給定某一個距離作為聚焦範圍,一個想法就是距離這個聚焦點越遠的深度值就越模糊,越近就越清晰;沿著這個思路就已經能實現一個基於屏幕空間後處理的方式渲染的景深效果了(指整個畫幅都渲染完之後,對整體進行一些額外渲染,比如模糊等)。
(圖中展示了基於Unity後處理的景深效果)
比較前沿的圖形學方向一直考慮以更接近物理學原理的方式來設計渲染流程。對於透鏡折射光線產生景深這個模式來說,在離線渲染中也完全可以用光線追蹤的方式來進行,一切計算都體現為光能量的傳播(只是加了一層透鏡)。當然這一過程展開過於複雜,也超出了我個人能說明白的範圍,有興趣的仍然是可以去看B站Games101系列課。
結語
在有限的運行資源內讓遊戲場景及其物體渲染得儘量多而精細,是一個不斷發展又一直很困難的事。即使主機和PC性能逐步解放了,這些技術下沉到移動端和低性能設備,仍然有著持久的生命力。
一些參考資料:
*這裡的鏈接都會盡量找英文原址(Wiki、GDC、論文等),主要是想讓大家感受到很多資料的年代感。拿著關鍵字在知乎之類的可以搜出很多翻譯版和二手知識版。
《巫師3》在GDC上分享關於Umbra3中間件的使用
虛幻引擎中的剔除
深度緩衝(也稱為Z-Buffering)
動態剔除算法(Hi-Z)