在說那幾位之前,我們先聊點輕鬆的東西,在gles1.0時代,硬體渲染管線還是固定的,就像是一個已經設計好的流水線,旁邊有幾個開關,程式設計師能做的就是切換這一個個開關,修改紋理、渲染狀態、在幾個固定的光照模型之間切換。有限的開關越來越難滿足大家對效果無止境的需求。可編程渲染管線隨之而來,取代了這一個個固定的開關的是幾個坐在固定位置的工人(vertex stage/fragment stage),這些工人可以接受我們的指令進行繪製。vertex shader、geometry shader、fragment shader就是這幾個勤勞的工人,通過他們,我們就可以控制GPU是如何繪製物體的。
0x01 前向渲染藉助上面幾個勤勞工人的幫助,前向渲染閃亮登場。前向渲染是一種非常直接的渲染方式,我們提交的mesh,經過vs、gs、fs等shader,直接繪製到color buffer等待輸出到屏幕。在考慮光照的情況下,在場景中我們根據所有光源照亮一個物體,之後再渲染下一個物體,以此類推,很線性的Per-Object/Per-Light方式進行繪製。
貼一下前向渲染的偽代碼:
這位直接了當的老哥有如下特點:
正如最後一點特點,當場景複雜度上升,大量光源大量物件的時候,前向渲染O(n*m)的複雜度就是不能承受的。另外因為一般情況下只繪製color buffer,對需要法線和深度進行計算的後處理算法支持不好,這些後處理往往對性能影響很大。
此處呈上經典延遲渲染的偽代碼
目前看起來延遲渲染把複雜度降了下來,面對複雜場景+多光源的需求面前有了辦法。然而這也帶來了其他的問題。對比前向和延遲的流程圖可以發現,延遲管線下多了深度、法線、顏色等幾張G-Buffer。這些都是屏幕大小的尺寸的Render Target,所帶來的內存以及帶寬壓力很大。
延遲渲染有以下特點:
內存開銷較大。
讀寫G-buffer的內存帶寬用量是性能瓶頸。
對透明物體的渲染存在問題。在這點上需要結合前向渲染進行渲染。
對多重採樣抗鋸齒(MultiSampling Anti-Aliasing, MSAA)的支持不友好,主要因為需開啟MRT。
由於Deferred Shading的Deferred階段是在完全基於G-Buffer的屏幕空間進行,這也導致了物體材質信息的缺失,這樣在處理多變的渲染風格時就需要額外的操作。
針對上文的問題,業界有主流有兩種優化方式。
1.Light Pre-Pass即Deferred Lighting 延遲光照技術,通過減少經典延遲渲染所使用的G-Buffer數量來提高性能,最早由 Wolfgang Engel於2008年在他的博客中提到的。
2.分塊延遲渲染,tile-based Deferred Rendering(記得開篇提到的tile-base小兄弟沒,這會出場了)。該方法通過可以在一次繪製處理多個光源,以及來降低對G-Buffer的讀寫開銷。
延遲光照有對MSAA支持良好、可以使用更多材質等等優點,由於數據證明大量光源下性能不及分塊延遲渲染,該部分不會介紹,感興趣的同學可以在引用中的連結自行查閱。下面我們會重點說說分塊延遲渲染。
0x03 分塊延遲渲染經典延遲渲染的瓶頸在對G-Buffer的讀取上,在存在大量光源的前提下,每個光源繪製都需要讀取G-Buffer,並和color buffer進行混合。考慮到每個光源都有各自覆蓋的面積,不同光源覆蓋區域重疊的情況會非常多,被多光源覆蓋的像素還是需要在各自光源的繪製過程中被重複計算。
針對這一個問題,分塊延遲渲染把屏幕分成很多個tile,然後計算一下每個tile被哪些光源覆蓋到,把覆蓋tile的光源保存在tile的光源列表中,繪製tile時一次繪製完成所有覆蓋光源光照計算。tile以及光源列表見下圖。
分塊延遲渲染流程一般為:
1.生成G-Buffer。
2.將G-Buffer分tile,每個tile用compute shader(偶見在cpu中間計算的實現)計算出depth bound。
3.進行light cull,得到light index list。
4.color pass,使用G-Buffer信息,用該fragment所在tile的light index list進行繪製。
實驗數據證明分塊延遲渲染的在大量光源存在的情況下性能明顯好於延遲光照。
0x04 小結上文介紹了前向渲染和延遲渲染,在延遲渲染及其優化版本用了較大篇幅分析延遲渲染出現理由以及優缺點。而對兩種渲染方式的選擇也很簡單,就像是一個天枰,一邊放著複雜場景和多光源的需求,還有低代價使用依賴深度和法線的後處理,而另一邊就是內存開銷大以及頻繁讀寫G-Buffer的性能開銷。如何選擇已經看需求,也需考慮技術團隊的經驗能力。而前向渲染因為沒有G-Buffer以及light cull的存在,所以在簡單場景下效率頗高,光照多其實也有解,lightmap配合IBL的方案很成熟的。
只是目前對比3A大作頗多採用延遲渲染的現狀相比,手機端市場佔有率最高的兩款商業引擎unity3d和ue4陣營中也未見幾款延遲渲染渲染的作品。難道限於手機相對孱弱的性能大家都放棄了大場景多光源的需求麼?並不盡然,原因如何且看下文。
0x05 移動端GPU兩三事移動端的硬體設計最初要考慮的其實是功耗,功耗會影響耗電、發熱、晶片大小等等。移動端GPU肯定也必然優先考慮功耗,沒有人願意面對分分鐘沒電的烤手寶,過熱也必然帶來降頻影響本就不是很優秀的處理器性能。那麼問題來了,影響功耗的最大因素是什麼?帶寬。
看到多次出現的帶寬,讀者大人應該能感受到移動端針對延遲渲染深深的惡意。在得出某種結論之前,還是有必要在對移動端GPU的背景再多介紹一下。
目前移動GPU領域,高通的adreno、ARM的mali、Imagination PowerVR佔據了主要市場。在GPU設計上,這三家都沒有採用桌面GPU主流的IMR(Immediate Mode Rendering)架構。以PowVR為例,Imagination 設計採用了Tile Based Deferred Rendering架構,簡稱TBDR,為了和上面的分塊延遲渲染區分開來,這裡姑且先叫做硬體TBDR。要區分兩者,容我先介紹一下IMR架構。
IMR就像opengl等圖形API書籍中介紹的標準圖形渲染管線,頂點階段之後裁剪投影剔除,光柵化然後著色再之後像素操作。直來直去的有些像開篇介紹的forward render。因為桌面GPU的功耗、散熱限制條件較少,帶寬以及顯存也明顯高於移動端。GPU在沒有諸多限制的情況下,這種直接的方式效率是最高的。
移動端上處處限制,設計架構也不得不採用犧牲效率換取帶寬的方案,且看PowerVR的渲染流程。
TBDR該詞分兩部分來理解。tile-base講的是將需要渲染的畫面分成一個個tile,這裡一般是4x4或者8x4的矩形塊。模型的頂點經過Vertex Shader運算以後會組裝成一個個的triangle,這些triangle會被緩存在一個triangle cache裡面。如果某個triangle需要在某個tile裡面繪製,那麼就會在該tile的triangle list中存一個索引。等一幀裡面所有的渲染命令都經執行完Vertex Shader生成triangle以後,每個tile就會有一個triangle list,這list就包含了需要在該tile內部繪製的所有triangle。然後GPU再基於triangle list執行每個tile的raster和Per-fragment operation。是不是有點上文提到的軟體TBDR的感覺了?
DR實際上出現在兩個地方,一是在光柵化之前,繪製指令被緩存到Primitive List和Vertex Data中,所有指令處理完畢之後再進行光柵化。第二是在紋理採集和著色階段之前,像素會進行名為HSR(Hidden Surface Removal)的early-z階段,這一波會進行On-chip(片上)深度測試以及深度緩存寫入,著色再次延遲在深度測試之後。
PowerVR之外的另外兩家GPU架構也都是基於tile實現的,Mali同樣有類似HSR的early-z技術。Adreno自家的FlexRender技術會在TBR和IMR針對效率自行切換,小尺寸的rendertarget使用IMR提升速度,反之則使用TBR拯救帶寬。
大家在設計架構上用的努力折射出移動端上帶寬問題影響確實嚴重。誠然軟體TBDR在目前的中高端手機上運行幀率已經問題不大,不過發熱和耗電問題卻不易解決。另外light-cull等操作對圖形要求gles3.1以上,國內市場雖然問題不大,但是東南亞群體gles2.0比例卻依然不低。
問題雖然很多,不過大多時候我們依然要目光向前。架構某種程度反應所面對的問題,但更多的是體現解決問題的方法。
0x06 基於metal的TBDR渲染設計說到現在,我們已經說了兩種形式的TBDR了,一種是軟體TBDR,完全程序控制,劃分tile,計算光源列表再進行著色。另外一種是則是硬體TBDR,GPU架構實現。兩者的目的也不盡相同,前者為了解決多光源問題,同時帶來了廉價的後處理,問題是增加了帶寬開銷。後者用效率(HSR的存在未必見得效率影響很大)換帶寬。
Apple在自家的metal demo中提供了一種可能性,即利用on-chip內存實現的Single-Pass Deferred Rendering。這個示例在桌面端以及移動端使用了兩種渲染器,這兩種都基於傳統的延遲渲染。
即在第一個pass先生成G-Buffer,在第二個pass進行光照計算。
在非TBDR(Mac)的架構之下,第一個pass的G-Buffer會被緩存到系統內存中,第二個pass讀取G-Buffer,頻繁讀寫大圖片,在桌面端問題並不大。而在IOS上,Apple使用了Single-Pass Deferred Rendering渲染器,收益於TBDR的硬體架構,通過顯式的設置ColorAttachment的store&loadAction,gpu會將硬體拆分的tile的render target放在on-chip memory中,在lighting階段所有需要的render target都不需要在從系統內存中讀取,光照計算結束之後on-chip memory中的渲染結果再統一寫回系統內存。
流程如下圖。
至此,由硬體處理TB,軟體處理DR的渲染流程完成了。在這個基礎上,我們可以使用metal2的新特性Raster Order Group以及Image Blocks進一步優化Pipeline,有興趣的同學可以在引用連結中查找並自行實現。
在整個渲染流程中,依賴硬體TBDR框架,完成了軟體延遲渲染配合硬體分塊,使用on-chip memory減少帶寬壓力,在此基礎上,使用metal2的新特性Raster Order Group以及Image Blocks可以進一步優化Pipeline,有興趣的同學可以在引用連結中自行了解,這裡不再介紹了。
0x07 總結本文中介紹了前向渲染以及延遲渲染,結合移動端GPU架構進行分析,旨在幫助大家結合自身技術以及需求合理選擇,並展示了利用GPU架構以及現代圖形API優化渲染流程的方案。因為篇幅問題,對Clustered方法以及Forward Plus沒有介紹,望理解。
感謝閱讀,謝謝。
引用1. [forward-rendering-vs-deferred-rendering]
https://gamedevelopment.tutsplus.com/articles/forward-rendering-vs-deferred-rendering--gamedev-12342
2. [《Real-Time Rendering 3rd 第七章續 · 延遲渲染]
https://blog.csdn.net/poem_qianmo/article/details/77142101?utm_source=blogxgwz4
3. [Rendering a Scene with Deferred Lighting]
https://developer.apple.com/documentation/metal/rendering_a_scene_with_deferred_lighting
4. [針對移動端TBDR架構GPU特性的渲染優化]
https://gameinstitute.qq.com/community/detail/123220
5. [Apple - Understanding GPU Family 4]
https://developer.apple.com/documentation/metal/gpu_features/understanding_gpu_family_4
6. [Apple - About Raster Order Groups]
https://developer.apple.com/documentation/metal/gpu_features/understanding_gpu_family_4/about_raster_order_groups
7. [Deferred Lighting]
http://diaryofagraphicsprogrammer.blogspot.com/2008/03/light-pre-pass-renderer.html
8. [移動端gpu架構淺析]
https://gameinstitute.qq.com/community/detail/103959
9. [imgtec TBDR]
https://www.imgtec.com/blog/understanding-powervr-series5xt-powervr-tbdr-and-architecture-efficiency-part-4/
10.[Light Pre-Pass Renderer]
http://diaryofagraphicsprogrammer.blogspot.com/2008/03/light-pre-pass-renderer.html