2019年12月29日 星期日

[札記] Forward Rendering SSR

圖1. Forward Rendering SSR

前言

SSR,利用GBuffer資訊,在Screen Space上做有限的Ray Tracing然後得到反射結果的一種技術。

它的最大好處是不需要設立動態cubemap就能捕捉畫面上大部分的反射內容,相當適合用來綴飾平面物件 (例如少數分散的水灘、平滑度高的表面等等),節省相當多的draw call,當然動態cubemap還是有它發揮的時候 (例如想反射畫面上沒有的東西)。

而常常聽到SSR最常出現的感嘆(或稱抱怨)是,它只能在Deferred Rendering上面使用。
沒錯,官方也明說了這一點,但你知道為什麼只能在Deferred Rendering上使用嗎?
而知道了以後有沒有機會修改成在Forward Rendering也能使用的形式?
當然是可以的,所以才有這篇

Hybrid Rendering Pipeline

混合RP在今天已經算是見怪不見的事情,例如Unity的Deferred Rendering,它也僅於Opaque部分使用Deferred Rendering,後面透明物件還是靠Forward Rendering Pass,這算是混合了兩種RP。

再舉別的例子,很多3A引擎甚至實作過Forward+ Rendering (一種tile-based技術),難道他們就沒有後製特效支援嗎?這方面DOOM就做了一個很好的示範,畫場景時主要的流程以Forward+為主,但同時搭配MRT生成normal、specular buffer,解決了後製的問題,誰說Rendering Pipeline只能寫死呢?

在Forward Rendering中加入必要資訊

SSR需要的資訊如下:
  • Depth Texture: 為了還原view space座標以便Ray Tracing
  • Color Texture: 未做任何後製前的Diffuse Color,計算最終結果需要用到
  • Normal Texture: Ray Tracing時要知道反射方向,計算最終結果也要用到
  • Specular Texture: 不用多解釋了吧!PBR公式的一環,計算最終結果時要考慮gloss

因此要給資訊的第一步,就是去建立color、normal、specular Render Texture
然後所有forward shader都要改寫成MRT輸出 (Multiple Render Target)

圖2. Shader改為MRT輸出

其實就是加入MRT,讓流程有辦法輸出額外資訊而已,的確Deferred Rendering屬於MRT的應用,但不代表MRT只能用於Deferred Rendering,改寫好了以後,就是流程裡面要SetRenderTargets()。

恰恰好,SSR這個東西只適用於Opaque物件,它在Transparent物件之前就完成反射計算,也就是說我們只要在計算完之前替換掉Unity設定的Render Target就可以了。

呼叫ComandBuffer.SetRenderTarget(),在CameraEvent.BeforeForwardOpaque插入這個指令就好了...........才怪!!只單純這樣使用的話,最後還是會被Unity強制蓋回他們的Target。

因此,Native Plugin出動的時候又到了:


圖3. 呼叫Native Plugin設定MRT(上) Plugin內容(下)


底層其實就是Clear & Set而已,但歸功於Unity的無能我們必須多做這一步。




圖4. Forward Rendering With GBuffer

妥善設定MRT後,要在Forward Rendering生成GBuffer沒有問題,並且在最後把結果給取代掉Unity的_CameraGBuffer0~2號。

順便在Script端把內建的DepthTextureMode.DepthNormals給強硬關掉了,因為這會造成Unity畫一次多餘的Pass來生成Normal Texture。
 
            if (forwardGBuffer)
            {
                m_Camera.depthTextureMode &= ~DepthTextureMode.DepthNormals;
            }

Shader需要改的地方

圖5. Reflection Probe修改

內建SSR還有針對Reflection Probe去做Blending,總不能兩種結果直接相加吧?
這邊會用到前面ray tracing以及其他設定的結果來blending,詳細內容就不提了。

總之會需要用到_CameraReflectionsTexture,這張貼圖其實就是indirect specluar的部分(reflection probe、baked skybox等等),這邊是Deferred Rendering獨有的貼圖,沒有的話怎麼辦呢?兩個手法:
  1. 代入Deferred Reflection的算式,這邊使用的方法,其實就是把Internel-DeferredReflection.shader裡面的fragment拿過來而已,compose這一步剛好也是full screen的,所以直接呼叫是通用的。
  2. MRT的時候多一張RenderTexture存反射資訊,比起方法1我會比較想這樣,因為forward pass裡面就一次把反射也算好了,再用方法1就重複計算了,不過這個方法當然會再吃掉VRAM(多一張)。
看需求選擇,這邊選方法一是方便展示。

沒有考慮到的地方

之所以會想用Forward Rendering,就是為了MSAA,而這邊的例子都沒有處理這個,但是要處理的方法也是有的:
  1. RT建立成multisample的形式。
  2. 畫完之後Resolve這些Render Targets。
但是這樣很直觀地就會導致VRAM損耗上升,因為Resolve的Source跟Destination都必須存在於GPU的記憶體上,而這種每個frame都要做的操作又不建議做串流, 吃VRAM會變成問題,建議4GB以上顯卡再來考慮處理MSAA,不然就盡量只開2X。

想耍賴在主畫面用MSAA,GBuffer不用AA也是不行的,至少MRT現在的規定是Render Targets都要相同Sample Count。

參考

Github Code: https://github.com/SquallLiu99/Forward-Rendering-SSR