主題

【Shader 學習筆記:畫面模糊特效】 關於OnRenderImage 無法在SRP上被呼叫的替代作法

趴趴鼠Loading | 2020-12-26 20:30:39

這次的參考影片是這個:

如果是用SRP (scriptable render pipeline),還沒打到5行程式碼就會卡住,原因是SRP 不支援 OnRenderImage這方法。
SRP又分成URP和HDRP, URP比較輕量,HDRP效果好但吃效能。

如影片內容,在原本的渲染系統上,只需簡單幾行就能做到:


void OnRenderImage(RenderTexture src, RenderTexture dst)
    {
        Graphics.Blit(src, dst, effectMaterial);  
    }

OnRenderImage(RenderTexture src , RenderTexture dst )
在camera結束render後,傳入畫面結果(src),進行操作後將結果傳給dst輸出。

Graphics.Blit(Texture src, RenderTexture dest, Material mat)
Blit sets dest as the render target, sets source _MainTex property on the material, and draws a full-screen quad.  (把src設成mat的mainTexture,並套用在dest上,然後畫成full-screen quad。)

想要簡單弄個後製效果所以選了URP,但又想額外打些shader,就不得不想個辦法取得螢幕結果。


最終結果:



問題來了,URP少了OnRenderImage,那要如何後製整個畫面的結果?

我一開始參考了這個影片和這串討論:

但不是我想要的,這些作法是將特定圖層的物件shader用別的取代上去,而非後製整個螢幕結果。

比較類似的是這部教學,但進入影片中的官方repo時發現腳本都不同了,且單包抓下來又一堆error要除。

後來爬到這篇,裡面說了以下這些srp都不再支援:

  • OnRenderImage
  • OnPostRender
  • OnPreRender
  • OnPreCull

而類似的作法是將方法註冊在RenderPipeline.beginCameraRendering 或 RenderPipeline.beginFrameRendering 事件上。 其中RenderPipeline已經是舊的語法,現在改為RenderPipelineManager。
在官方blit的文件上也有提到:
《If you are using a Scriptable Render Pipeline (like HDRP or Universal RP), to blit to the screen backbuffer using Graphics.Blit, you have to call Graphics.Blit from inside a method that you register as the RenderPipelineManager.endFrameRendering callback.》

我的作法有些粗暴,在main camera下面放另一個camera,當成原本OnRenderImage方法的src,負責傳入當前的畫面Texture。

程式只貼重要的:
    RenderTexture render_Tex;
    public Material effectMaterial;
    public Camera renderProviderCamera;
    public Camera mainCamera;
    public int facter = 4;

    public float magnitude = 0.25f;
    readonly string _magnitude_name = "_Magnitude";

void OnEnable()
    {
        mainCamera = Camera.main;
        render_Tex = new RenderTexture(mainCamera.pixelWidth >> facter, mainCamera.pixelHeight >> facter, 16);
        renderProviderCamera.targetTexture = render_Tex;
        effectMaterial.mainTexture = render_Tex;

        Shader.SetGlobalFloat(_magnitude_name, magnitude);
        RenderPipelineManager.endCameraRendering += EndCameraRendering;
    }

void EndCameraRendering(ScriptableRenderContext context, Camera camera)
    {
        if (camera == mainCamera)
        {
            Graphics.Blit(render_Tex, mainCamera.targetTexture, effectMaterial);
        }
    }

官方文件是建議掛在 void EndFrameRendering(ScriptableRenderContext context, Camera[] cameras)事件下,但這方法會把所有camera都覆蓋掉,所以我改在EndCameraRendering操作。

意思是把renderProviderCamera 的輸出目標設成render_Tex並down res做南部畫質特效,(位移符號可以看上篇)。最後用Blit,將render_Tex套用effectMaterial材質 給mainCamera輸出。


*補充,在update裡面Blit是無用的,必須等camera結束render再取texture才是有意義的。


影片中的shader
fixed4 frag (v2f i) : SV_Target
            {
                float2 dis=tex2D(_DisplaceTexture , i.uv + _Time.x ).xy;
                dis=((dis*2)-1  ) * _Magnitude;  //(*2-1) -> 把uv範圍[0,1] 改成 [-1,1]
                fixed4 col = tex2D(_MainTex, i.uv + dis );
                return col;
            }

備註: 影片不小心用到_SinTime,正確應該是_Time就好。 (shader time簡介)


雜談:
SRP出來後,現在unity渲染系統都在內鬥,原本的Stander Surface Shader到了urp直接下去再見掰掰(阿不就好險我還沒學),還有一堆方法例如GrabPass等等...
不禁感嘆自己身在亂世(?
159 巴幣: 8

更多創作