Unity 2017 Game Optimization 讀書筆記 Dynamic Graphics(2)

Lighting and Shadowing

現代的遊戲中,基本沒有物體能在一步就完成渲染,這是因爲有光照和陰影的關係。光照和陰影的渲染在Fragment Shader中需要額外的pass。

首先要設置場景中的Shadow Casters和Shadow Receivers,Shadow Casters投射陰影,Shadow Receivers接收陰影。之後每一個Shadow Receiver渲染時,GPU就會在燈光的位置和角度渲染Shadow Caster物體,將深度信息存儲到一張貼圖中。這張貼圖就是常說的ShadowMap,它用來實時的渲染陰影。光照渲染和陰影渲染在渲染管線中開銷非常大,需要每個頂點都要提供法線信息以及額外的頂點顏色信息等。因爲Fragment Shader需要多個passes完成最終的渲染,Backe End在Fill Rate(許許多多的像素都需要繪製,重新繪製和合並)和 Memory BandWidth(額外的texture需要來回獲取,比如LightMaps和ShadowMaps)兩方面都會壓力很大,這也是爲什麼實時陰影是非常昂貴的,也會給Draw Call帶來很大開銷。

但是光照渲染和陰影渲染應該是現在遊戲中最重要的兩部分,的的確確能給遊戲帶來效果上質的提升,出色的光照和陰影效果能使得平庸的場景搖身一變。有兩種渲染管線,Forward Rendering 和 Deferred Rendering,在 Rendering  Edit |Project Settings | Player | Other Settings | Rendering 下可以進行設置,接下來介紹下這兩部分。

Forward Rendering

同樣的Shader多個Pass渲染,一共有多少個pass取決於光源的數量和光源的距離和光源的亮度。在Unity中,前向渲染規則如下:一個場景中多光照情形,按光源的重要程度排序,比如ABCD四盞燈用效果最好的逐像素方式渲染光照,DEFG用逐頂點方式渲染光照,GH則用球諧光照方式計算光照。(關於球諧光的一些知識,可以參考我的這篇文章https://blog.csdn.net/yinfourever/article/details/90205890)

Unity中正向渲染分爲一個Base Pass和多個Additional Passes.

image

image

 

Base Pass: 用逐像素的方式渲染一個directional light, 所有球諧光和逐頂點光。在這個pass裏也會計算lightmap等,directional light可以計算陰影,要注意如果使用了lightmap照亮的物體不會被球諧光照亮。

Additional Passes:每個其他需要用逐像素渲染的光源將會用一個Addtional pass渲染,默認情況下這些燈光不會有陰影除非使用了 multi_compile_fwdadd_fullshadows variant shortcut 更詳細的信息可以參考Unity官方文檔中關於Forward Rendering的介紹https://docs.unity3d.com/2019.2/Documentation/Manual/RenderTech-ForwardRendering.html
可以通過Edit | Project Settings| Quality | Pixel Light Count 設置像素光的數量,但是會被每個Render Mode設置爲Important的Lights覆蓋。

ForwardRendering當場景中有大量點光源時由於Render States需要不斷的設置會產生大量Draw Call,跟場景光源數量正相關。

Deferred Rendering

Deferred Rendering通過G-Buffer工作,是將光照計算延後進行處理的一種渲染方法。

延遲渲染的優點
Deferred Rendering 的最大的優勢就是將光源的數目和場景中物體的數目在複雜度層面上完全分開。也就是說場景中不管是一個三角形還是一百萬個三角形,最後的複雜度不會隨光源數目變化而產生巨大變化。

複雜度僅O(n+m)。
只渲染可見的像素,節省計算量。
用更少的shader。
對後處理支持良好。
在大量光源的場景優勢尤其明顯。
 
延遲渲染的缺點

內存開銷較大。
讀寫G-buffer的內存帶寬用量是性能瓶頸。
對透明物體的渲染存在問題。在這點上需要結合正向渲染進行渲染。
對多重採樣抗鋸齒(MultiSampling Anti-Aliasing, MSAA)的支持不友好,主要因爲需開啓MRT。
由於Deferred Shading的Deferred階段是在完全基於G-Buffer的屏幕空間進行,這也導致了物體材質信息的缺失,這樣在處理多變的渲染風格時就需要額外的操作。

更詳細的有關渲染模式的知識請查看我的這篇文章(牆裂建議看)

https://blog.csdn.net/yinfourever/article/details/90263638

Vertex Lit Shading (legacy)

已被遺棄

Global Illumination

全局光,是烘焙光照貼圖的一部分。光照貼圖是提前烘焙好的,因此可以有足夠的時間生成高質量的光照貼圖,會節省大量Draw Call。也正是因爲提前烘焙好的,所以它不是實時的,所以它對Static物體生效,動態物體需要通過LightProbe才能受到影響。LightProbe並不是像素級精準的(使用球諧光),並且會生成額外的LightProbe Maps,對Memory BandWitdh有開銷,但是確實會對渲染效果帶來很大的提升。

對於全局光,不僅計算直接光照的影響,還會將物體間反彈的光考慮進去。老版本的GI系統是Enlighten,它不僅提供了靜態的GI,還提供了一種Pre-computed Real-time GI,可以用來製造實時渲染的假象,比如晝夜更替系統。目前Unity最新的GI系統是Progressive LightMapper。

關於這部分知識,我寫過另一篇文章幫助理解https://blog.csdn.net/yinfourever/article/details/105151596

Multithreaded Rendering

多線程渲染在大多數平臺中是被默認開啓的,比如PC或者其他CPU支持多核的。對於Android,Edit | Project Settings | Player | Other Settings |Multithreaded Rendering可以進行設置,對於IOS可以在Edit | Project Settings | Player| Other Settings | Graphics API設置。

對於場景中的一個物體,要進行渲染的話有三件事需要處理。決定這個物體是否需要渲染,生成渲染指令,通過相關圖形API發送指令到GPU。如果沒有Multithreaded Rendering,這些工作都要由CPU主線程承擔,開啓Multithreaded Rendering,把渲染指令推送給GPU的工作將由render線程承擔,其他例如剪裁等工作將由其他一些worker 線程承擔。這給CPU主線程大大減壓,可以給物理運算以及腳本邏輯運算留出更多空間。

開啓Multithreaded Rendering後,關於CPU-Bound會有一些影響,沒開啓的時候,所有工作都是由CPU主線程做,因此無論那部分性能的優化都會對CPU-Bound帶來改善。開啓Multithreaded Rendering後,因爲工作被分散到各個線程,即使對主線程做出一些優化,對CPU-Bound的減輕程度可能也會變得很小。

是否開啓Multithreaded Rendering對GPU沒影響,GPU已經總是用多線程渲染。

Low-level rendering APIs

通過CommandBuffer可以使用高級別的API對渲染管線進行一些控制,但是可用的內容和範圍還是太少,因此可以用C++代碼直接控制圖形API,將C++代碼打成一個Plugin,hook到Unity的渲染管線中。

當然,現在Unity已經推出了SRP(可編程渲染管線),已經可以完全自定義渲染管線,十分方便,因此這一小節提到的方法可能已經基本不會使用了。關於SRP,可以看我這篇文章https://blog.csdn.net/yinfourever/article/details/89364090

Detecting performance issues

Profiling rendering issues

通過Unity的Profiler 可以快速定位是瓶頸是在CPU和是GPU.

CPU-bound

下圖是一個CPU瓶頸的例子,這個場景有成千的簡單Cube物體,但是沒有使用batching並且有陰影的渲染,這導致CPU需要生成大量的Draw Call,但是GPU實際上渲染任務很少。

如何定位問題的呢?下圖中CPU平均需要25ms處理一個循環,而GPU只需要4ms以內,這就說明了瓶頸在CPU端,應該想辦法使用一些優化CPU端的技術。

 GPU-bound

下圖的測試條件爲很少的Draw Call,但是使用非常複雜的Shader進行渲染。如果想測試是否是GPU端的瓶頸,需要注意的是要關掉V Sync(垂直同步),不然會影響Profiler。

從圖中我們可以看到CPU和GPU都消耗29ms左右,但是再深層次查看一下,CPU的時間都花在了 Gfx.WaitForPresent函數上,這是在CPU在等待GPU完成這幀操作而浪費掉的時間,即使開啓多線程渲染,CPU也必須是等待渲染管線完成後才能開始下一幀。Gfx.WaitForPresent也同時會被Vertical Sync使用,因此要關掉Vertical Sync來避免干擾。

Brute-force testing 

 如果從Profiler中沒辦法定位問題,我們可能就得使用蠻力測試。比如從場景中去除一些物體,看看性能有沒有很大改善,如果很小的改動會帶來很大提升,那其就定位了問題。

降低屏幕分辯率和降低貼圖的分辯率來進行測試是兩種定位瓶頸是FillRate還是Memory Bandwidth的好方法。

降低屏幕分辨率可以有效降低Frill Rate的消耗(比如2560 x 1440 降爲 800 x 600大概會降低8倍),如果性能有很大的提升,說明Fill Rate有可能是我們應該首要考慮優化的地方。

類似的,如果降低貼圖的分辨率後性能提升非常大,則說明Memory BandWidth可能是瓶頸。

GPU的瓶頸通常是FillRate和Memory Bandwidth,很少是Front End的原因,Vertex Shader如果有問題,只能是因爲傳入了太多幾何體需要處理或者使用了很複雜的Geometry Shader。

 

 

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