為什麼選擇水來當例子,主要是因為水的實作通常跟渲染流程有很大的關係,如同我前一篇文章介紹的反射、折射…等,比單一Shader效果複雜許多。
這裡我們要處理反射(倒影)及折射(水透到下面的地方),如下圖:
所以我們需要的素材有:
- 反射貼圖
- 水面深度貼圖
- 折射貼圖(也就是在畫水之前目前的背景圖)
因此我們需要一個射影機來拍射反射,而反射矩陣(拍射的角度及位置)需要根據目前的射影機來計算,進而拍射出反射貼圖。另外我們還需要在畫水之前,依照水面的深度來決定如何透出水面下的東西,所以也需要以目前的射影機拍射水面深度貼圖。至於折射貼圖,我們只要在畫水之前,將目前的畫面拷貝一份即可。
所以流程就是:
拍射反射貼圖=>拍射水面深度貼圖=>拷貝目前背景當折射貼圖=>渲染水體。
有了這個基本想法後,我們來看看如何在LWRP下,透過他提供的介面,不改其代碼來做到這些事情,另外所有的LWRP interface都要繼承MonoBehaviour,並放在要渲染的射影機下,讓他抓取這些介面跟我們溝通:
- IBeforeCameraRender:我寫了一個PlaneReflectionProcessor來繼承IBeforeCameraRender,這樣就可以透過ExecuteBeforeCameraRender這個事件來取得在Camera一開始渲染的時機,並在它傳入的Camera參數中取得目前射影機的矩陣,接下來就可以讓Reflection Camera依照目前的射影機設定好參數,並計算反射矩陣,之後就可以呼叫RenderSingleCamera來渲染反射貼圖,要記得設好過濾掉Water這個layer,因為完全不需要拍射到水。
- IRendererSetup:其實一開始並不打算實作這個介面,但因為想利用Multi Render Target的方式來達到渲染水面深度貼圖的目的(其實也可以擺在DepthOnlyPass來一起處理,讀者可以自行調整)。所以這裡寫了一個MySRPRenderSetup,用來處理客製化自己的渲染流程,不走LWRP的DefaultRendererSetup的流程。
- RenderOpaqueWithPlaneDepthPass:接下來在MySRPRenderSetup下,加了一個RenderOpaqueWithPlaneDepthPass,來取代RenderOpaqueForwardPass,用一個Pass同時渲染ColorBuffer及渲染水面深度貼圖,為了這個目的,我們還要另外提供一個MRT的Shader來渲染物件,細節請參考MRTBase檔裡的CustomLightweight/MRTBase的代碼。
- DrawSkyAndCopyBackGroundPass:這裡用這個Pass取代了DrawSkyboxPass,主要的目的就是在畫水之前把目前的渲染的結果copy起來,以提供在畫水的時候可以依照深度攤提折射(透的程度)。
- 接下來就可以畫水了,Shader相當簡單,詳情請看代碼WaterPlane.shader。
- 水的渲染流程要在所有opaque物件畫完之後,於是擺在Transparent的RenderQueuqe,但為了能讓水也可以接受影子 ,目前DepthOnlyPass只能拍射出opaque的depth,所以寫了一個CaptureAllDepthOnlyPass來取代他。
由左而右,依序為反射,深度,水面深度,折射(copy畫水之前的背景圖)
到了這裡,應該差不多做完了,為了享受LWRP的multi light,特別加了一些點光源,來強化使用LWRP的價值。另外為了完整性,這裡加了一個Grayscale post Effect,讓整個客究化流程更完整,渲染結果如下:
相關GitHub專案連結:
HelloLWRP
Dream continues in...
沒有留言:
張貼留言