2019年5月28日 星期二

如何使用Flow Map quads來實現互動水波

由於之前實現的水波系統,無法實現在無限大的水體,而且覺得目前美術使用特效的方式來處理航行特效,感覺非常生硬,完全跟水沒有互動感。最近看了flow map蠻流行的,看看有沒有實現的可能性,於是開啟了如何使用Flow Map quads來實現互動水波之路。

以下是大概的執行流程:
  1. 使用GPUInstance或Partilce system,時時噴射noise animation flow map quads,並處理好相關的alpha消失及scale漸漸變大的部份。
  2. 渲染到一張screen space的flow map。
  3. 在water渲染時,參考flow map,並依照flow方向及alpha權重,影響normal及泡沫的比重
結果如下:

2019年5月21日 星期二

Unity一些相關Shader Texture參數大坑及渲染過濾相關整理

Unity的shader Texture參數大坑相關整理:

  1. Blit時要將Input Texture Name改成"_MainTex",並切記在Properties加上
    _MainTex("Albedo", 2D) = "white" {},否則input會帶不進來…
  2. 用在非Blit,比方DrawMesh,切記不用使用_MainTex,否則會造成texture參數帶不進
    來,從LWRP的final pass的blit用的參數叫"_BlitTex",還有LWRP也把相關的Shader參數
    從"_MainTex"改成"_BaseMap"就可以略知一二…
Unity的渲染過濾相關整理:

  1. LayerMask:
    通常用在camera及physic的過濾,算是物件層級。
  2. RenderingLayerMask(SRP之後才支持):
    在物件過濾層級之後(Camera過濾),依渲染需求的不同,再過濾一次,比方可以用在
    即使用相同層級的LayerMask,也可以用程式動態設定RenderingLayerMask,來控制是否
    要進行渲染。 
  3. LightMode Tag(SRP之後開放給使用,之前是內部使用):用在渲染物件時挑方法來進
    行渲染,比方畫陰影挑ShadowCaster Pass、正常渲染則挑Forward Pass、畫額外時時光時
    挑Add Pass、畫Decal挑Decal Pass之類的,所以通常一個物件在同一個frame,有可能會
    被畫二次以上。
後記:話說Unity到目前還是不開放Camera剔除處理,雖然有人說在Camera做完之後用
RenderLayerMask再做自己的剔除,但我覺得這樣沒什麼用,因為他的Camera剔除處理早就已
經花了性能,還不如在他之前先把Renderer的enable設成false,但這樣其實他還是跑了
Renderer.enable != false的迴圈檢查,還是不好。

Dream continues in...

2019年5月7日 星期二

關於在2019LWRP下處理反射的問題

目前LWRP2019的release版,在處理Reflection時(自帶Relfection Camera,並有設定過Camera.targetTexture),且Depth Texture的flag也有打勾的話,會造成顯示不正常且一直噴Error log,後來使用不自帶RenderTarget的方式,並安插了一個在final pass之前拷貝渲染結果的pass,才把問題解決。目前這樣做,其實應該可以不用final pass,加個flag來判斷要不要做,如果還要走動態切換ScriptableRenderer的正式流程,覺得太麻煩,xd. 相關處理結果如下:

2019年5月3日 星期五

阿基拉的Unity世界大冒險之 VS ScriptableRenderer章 後記(相關SetRenderTaget的修改)

之前阿基拉曾經說對ScriptableRenderer動大刀,但實際的情況是沒這麼嚴重,算是做了一點小手術,不過由於已經不是原來的LWRP,所以還是得自行重新建立一份跟LWRP很像的海埔新生地(新大陸)到新的地方。

目前大概是做了這些手術:

  1. ScriptableRenderPass.cs:

    //add by akilar, append a flag for check need set render target
    public virtual bool IsSetRenderTarget() { return true; }
  2. ScriptableRenderer.cs:
    //add by akilar
    void ExecuteRenderPass(ScriptableRenderContext context, ScriptableRenderPass renderPass,
        ref RenderingData renderingData)
    {
        //add by akilar
        if (!renderPass.IsSetRenderTarget())
        {
            renderPass.Execute(context, ref renderingData);
            return;
        }
        ...
        //modify by akilar, disalbe first check setting, only check need reset render target
        /*if (passColorAttachment == m_CameraColorTarget &&
               !m_FirstCameraRenderPassExecuted)
        {
            m_FirstCameraRenderPassExecuted = true;
            ...
        }
        // Only setup render target if current render pass attachments are different from the active
            ones
        else*/

        if (passColorAttachment != m_ActiveColorAttachment || passDepthAttachment !=
            m_ActiveDepthAttachment)
        {
            SetRenderTarget(cmd, passColorAttachment, passDepthAttachment, renderPass.clearFlag,
                renderPass.clearColor);
        }
        ...
    }
狀況如下…


跟LWRP的恩怨情仇持續中…讓我們繼續看下去…

2019年4月30日 星期二

阿基拉的Unity世界大冒險之 VS ScriptableRenderer章最終回

雖然阿基拉已經讓ScriptableRenderer放下心房,讓出了可以調整SetRenderTarget的地方…但是後來深入到設定SetRenderTarget的地方,才發現ScriptableRenderer其實還留下了一道最後的防線,就是m_FirstCameraRenderPassExecuted並搭配GetCameraClearFlag的機關,等於之前做過任何的努力都會被這道機關清除,而且最致命的是這道機關完全沒辦法被調整…

後來阿基拉決定放棄以最小的變動調整這個堅持,打算直接對ScriptableRenderer動大刀了…不再讓它這麼囂張,打掉他對於SetRenderTarget的控制,但這樣一來整個LWRP這塊大陸都得重新移位到新的海域,為了跟之前的大陸有所區隔,只好重新建立一份跟LWRP很像的海埔新生地(新大陸),把原先的LWRP拷貝過來…真是一大工程…

也許這樣很難跟組織交待,但目前也只能這麼做了,誰叫LWRP這麼機車,又臭又硬,完全動不了…

跟ScriptableRenderer的恩怨情仇結束中…讓我們繼續看下去…

2019年4月27日 星期六

阿基拉的Unity世界大冒險之 VS ScriptableRenderer章初回

阿基拉,橫渡過FFT projected grid ocean的技術宅,這次被組織奉命調查Untiy世界發生了SRP板塊突然從海底升起的事件,尤其是LWRP這一塊,被要求以最小的變動在上面搭建渲染基地

然而在上面有一個叫ScriptableRenderer的魔獸,他擁有著堅硬無比的外殼駐紮在中間,完全無法觸碰到需要調整SetRenderTarget的地方,但好在附近有一個MainLightShadowCasterPass,他有機會可以跟ScriptableRenderer溝通,於是阿基拉靈機一動,想透過MainLightShadowCasterPass來告訴ScriptableRenderer不用擔心SetRenderTarget,因為沒人設過,但又被ScriptableRenderer的Configure防護給識破…

後來阿基拉決定跟MainLightShadowCasterPass勾結,開inteface這個後門來欺騙ScriptableRenderer,讓他誤以為阿基拉跟MainLightShadowCasterPass是同伙,於是才破解ScriptableRenderer的心防,讓出了可以調整SetRenderTarget的地方…

冒險持續中…讓我們繼續看下去…

2019年3月28日 星期四

如何客製化LWRP渲染流程

由於客製化LWRP渲染流程可以參考的例子並不多,目前是以BoatAttack為主,但這個案例過於複雜,在業界同好的盛情下,寫了一個以簡易水相關的案例,希望達拋專引玉的效果。BoatAttack

為什麼選擇水來當例子,主要是因為水的實作通常跟渲染流程有很大的關係,如同我前一篇文章介紹的反射、折射…等,比單一Shader效果複雜許多。

這裡我們要處理反射(倒影)及折射(水透到下面的地方),如下圖:


所以我們需要的素材有:
  1. 反射貼圖
  2. 水面深度貼圖
  3. 折射貼圖(也就是在畫水之前目前的背景圖)

因此我們需要一個射影機來拍射反射,而反射矩陣(拍射的角度及位置)需要根據目前的射影機來計算,進而拍射出反射貼圖。另外我們還需要在畫水之前,依照水面的深度來決定如何透出水面下的東西,所以也需要以目前的射影機拍射水面深度貼圖。至於折射貼圖,我們只要在畫水之前,將目前的畫面拷貝一份即可。

所以流程就是:
拍射反射貼圖=>拍射水面深度貼圖=>拷貝目前背景當折射貼圖=>渲染水體。

有了這個基本想法後,我們來看看如何在LWRP下,透過他提供的介面,不改其代碼來做到這些事情,另外所有的LWRP interface都要繼承MonoBehaviour,並放在要渲染的射影機下,讓他抓取這些介面跟我們溝通:
  1. IBeforeCameraRender:我寫了一個PlaneReflectionProcessor來繼承IBeforeCameraRender,這樣就可以透過ExecuteBeforeCameraRender這個事件來取得在Camera一開始渲染的時機,並在它傳入的Camera參數中取得目前射影機的矩陣,接下來就可以讓Reflection Camera依照目前的射影機設定好參數,並計算反射矩陣,之後就可以呼叫RenderSingleCamera來渲染反射貼圖,要記得設好過濾掉Water這個layer,因為完全不需要拍射到水。
  2. IRendererSetup:其實一開始並不打算實作這個介面,但因為想利用Multi Render Target的方式來達到渲染水面深度貼圖的目的(其實也可以擺在DepthOnlyPass來一起處理,讀者可以自行調整)。所以這裡寫了一個MySRPRenderSetup,用來處理客製化自己的渲染流程,不走LWRP的DefaultRendererSetup的流程。
  3. RenderOpaqueWithPlaneDepthPass:接下來在MySRPRenderSetup下,加了一個RenderOpaqueWithPlaneDepthPass,來取代RenderOpaqueForwardPass,用一個Pass同時渲染ColorBuffer渲染水面深度貼圖,為了這個目的,我們還要另外提供一個MRT的Shader來渲染物件,細節請參考MRTBase檔裡的CustomLightweight/MRTBase的代碼。
  4. DrawSkyAndCopyBackGroundPass:這裡用這個Pass取代了DrawSkyboxPass,主要的目的就是在畫水之前把目前的渲染的結果copy起來,以提供在畫水的時候可以依照深度攤提折射(透的程度)。
  5. 接下來就可以畫水了,Shader相當簡單,詳情請看代碼WaterPlane.shader。
  6. 水的渲染流程要在所有opaque物件畫完之後,於是擺在Transparent的RenderQueuqe,但為了能讓水也可以接受影子 ,目前DepthOnlyPass只能拍射出opaque的depth,所以寫了一個CaptureAllDepthOnlyPass來取代他。

由左而右,依序為反射,深度,水面深度,折射(copy畫水之前的背景圖)



到了這裡,應該差不多做完了,為了享受LWRP的multi light,特別加了一些點光源,來強化使用LWRP的價值。另外為了完整性,這裡加了一個Grayscale post Effect,讓整個客究化流程更完整,渲染結果如下: