圖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;
}
{
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獨有的貼圖,沒有的話怎麼辦呢?兩個手法:
- 代入Deferred Reflection的算式,這邊使用的方法,其實就是把Internel-DeferredReflection.shader裡面的fragment拿過來而已,compose這一步剛好也是full screen的,所以直接呼叫是通用的。
- MRT的時候多一張RenderTexture存反射資訊,比起方法1我會比較想這樣,因為forward pass裡面就一次把反射也算好了,再用方法1就重複計算了,不過這個方法當然會再吃掉VRAM(多一張)。
沒有考慮到的地方
之所以會想用Forward Rendering,就是為了MSAA,而這邊的例子都沒有處理這個,但是要處理的方法也是有的:- RT建立成multisample的形式。
- 畫完之後Resolve這些Render Targets。
想耍賴在主畫面用MSAA,GBuffer不用AA也是不行的,至少MRT現在的規定是Render Targets都要相同Sample Count。