Unity Shader實(shí)現(xiàn)運(yùn)動(dòng)模糊(二) : 物體運(yùn)動(dòng)產(chǎn)生模糊

上一篇 關(guān)于運(yùn)動(dòng)模糊的效果是根據(jù)VP矩陣重建世界空間坐標(biāo),然后根據(jù)上一幀和當(dāng)前幀的位置差作為速度方向,這種方式需要攝像機(jī)有位移才能產(chǎn)生效果,如果攝像機(jī)靜止不動(dòng),那么VP矩陣也就不會(huì)有變化,所以上一幀和當(dāng)前幀在NDC空間的位置也沒有變化,即speed==0,也就不會(huì)有運(yùn)動(dòng)模糊的效果,而想要在攝像機(jī)不動(dòng)而物體移動(dòng)時(shí)產(chǎn)生模糊效果,則需要另一種實(shí)現(xiàn)方式,這種方式更簡潔直觀。

大致思路是:

  1. C#中記錄運(yùn)動(dòng)物體的 當(dāng)前幀的世界坐標(biāo) 和 當(dāng)前幀的VP矩陣
  2. C#中記錄運(yùn)動(dòng)物體的 上一幀的世界坐標(biāo) 和 上一幀的VP矩陣
  3. Shader中根據(jù)上面的信息求出 當(dāng)前幀 和 上一幀的NDC坐標(biāo)
  4. NDC坐標(biāo)求差值得出速度方向,然后沿速度方向進(jìn)行多次采樣后求平均值

這只是我自己的一個(gè)不成熟的實(shí)現(xiàn)思路,如果哪位大神有更合適的實(shí)現(xiàn)方式請一定賜教哈。

這種實(shí)現(xiàn)方式不需要對深度紋理進(jìn)行采樣,因此攝像機(jī)也不用設(shè)置depthTextureMode。但是按照這個(gè)思路實(shí)現(xiàn)出來以后發(fā)現(xiàn)整個(gè)畫面都被模糊了,不只是運(yùn)動(dòng)的物體,連背后靜止的plane物體也被模糊了,原因在于使用了后處理,而后處理是對整個(gè)屏幕畫面進(jìn)行的處理,所以想要只對運(yùn)動(dòng)的物體進(jìn)行模糊,那么需要單獨(dú)使用一個(gè)攝像機(jī)來看運(yùn)動(dòng)的物體,Shader中需要寫兩個(gè)pass,第一個(gè)pass實(shí)現(xiàn)運(yùn)動(dòng)物體的模糊,第二個(gè)pass把主攝像機(jī)的畫面和模糊后的畫面疊加到一起,思路如下:

  1. 調(diào)用Shader的第一個(gè)pass,實(shí)現(xiàn)運(yùn)動(dòng)的物體被模糊,結(jié)果渲染到一張RenderTexture上
  2. 把這張RenderTexture作為紋理參數(shù)傳遞給Shader
  3. 把主攝像機(jī)的畫面渲染到另一張RenderTexture上
  4. 調(diào)用Shader的第二個(gè)pass,把主攝像機(jī)的RT渲染到最終的RT上

第二個(gè)pass中會(huì)用到第一個(gè)pass處理后的結(jié)果。

攝像機(jī)相關(guān)的操作是:

  1. 把運(yùn)動(dòng)物體的layer設(shè)置到和靜止物體不同的層,比如可以新建一個(gè)MovingObject層
  2. 新建一個(gè)攝像機(jī),用來只看運(yùn)動(dòng)物體,設(shè)置culling mask = MovingObject,Depth 比主攝像機(jī)小,其余參數(shù)和主攝像機(jī)一致。
  3. 從主相機(jī)的culling mask中去除MovingObject層

效果圖:


使用一個(gè)攝像機(jī)時(shí),運(yùn)動(dòng)的Cube和靜止的Plane都模糊了
使用兩個(gè)攝像機(jī)后,只有運(yùn)動(dòng)的Cube是模糊的,靜止的Plane是清晰的

那么現(xiàn)在開始上代碼,這樣看起來可能更清晰些。

C# 部分:

using UnityEngine;

// 運(yùn)動(dòng)模糊, 物體運(yùn)動(dòng)產(chǎn)生模糊效果 //
public class MotionBlur_ObjectMove : MonoBehaviour
{
    public Transform target;
    [Range(0, 2)]
    public float BlurSize;

    private Material m_mat;
    private const string ShaderName = "MJ/PostEffect/MotionBlur_ObjectMove";
    private Vector3 m_curWorldPos;                              // 當(dāng)前幀的世界空間坐標(biāo) //
    private Vector3 m_lastWorldPos;                             // 上一幀的世界空間坐標(biāo) //
    private Matrix4x4 m_curVP;                                       // 當(dāng)前幀的Vp矩陣 // 
    private Matrix4x4 m_lastVP;                                      // 上一幀的Vp矩陣 // 
    private Camera m_cam;
    private Camera m_mainCam;

    void Start()
    {
        if (target == null)
        {
            enabled = false;
            return;
        }

        Shader shader = Shader.Find(ShaderName);
        if (shader == null)
        {
            enabled = false;
            return;
        }

        m_mat = new Material(shader);

        m_cam = GetComponent<Camera>();
        if (m_cam == null)
        {
            enabled = false;
            return;
        }

        m_mainCam = Camera.main;
        if (m_mainCam == null)
        {
            enabled = false;
            return;
        }
    }

    void OnRenderImage(RenderTexture srcRT, RenderTexture dstRT)
    {
        if (m_mat == null || m_cam == null || target == null)
        {
            return;
        }

        RenderTexture mainCamRT = RenderTexture.GetTemporary(srcRT.width, srcRT.height, 24);
        RenderTexture blurRT = RenderTexture.GetTemporary(srcRT.width, srcRT.height, 24);
        
        m_mainCam.targetTexture = mainCamRT;

        m_curVP = m_cam.projectionMatrix * m_cam.worldToCameraMatrix;
        m_curWorldPos = target.position;

        m_mat.SetFloat("_BlurSize", BlurSize);
        m_mat.SetVector("_CurWorldPos", m_curWorldPos);
        m_mat.SetMatrix("_CurVP", m_curVP);
        m_mat.SetVector("_LastWorldPos", m_lastWorldPos);
        m_mat.SetMatrix("_LastVP", m_lastVP);

        m_lastWorldPos = m_curWorldPos;
        m_lastVP = m_curVP;

        // 運(yùn)動(dòng)模糊 //
        Graphics.Blit(srcRT, blurRT, m_mat, 0);

        m_mat.SetTexture("_BlurTex", blurRT);
        // 合并畫面 //
        Graphics.Blit(mainCamRT, dstRT, m_mat, 1);

        RenderTexture.ReleaseTemporary(mainCamRT);
        RenderTexture.ReleaseTemporary(blurRT);
    }
}

Shader部分:

Shader "MJ/PostEffect/MotionBlur_ObjectMove"
{
    Properties
    {
        _MainTex ("Main Texture", 2D) = "white" {}
        _BlurSize("Blur Size", Range(0, 10)) = 1
    }

    CGINCLUDE
    #include "UnityCG.cginc"
    struct appdata
    {
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
    };

    struct v2f
    {
        float4 vertex : SV_POSITION;
        float2 uv : TEXCOORD0;
    };

    sampler2D _MainTex;
    float2 _MainTex_TexelSize;
    float4 _MainTex_ST;
    
    uniform float _BlurSize;
    uniform float4 _CurWorldPos;
    uniform float4 _LastWorldPos;
    uniform float4x4 _CurVP;
    uniform float4x4 _LastVP;
    uniform sampler2D _BlurTex;             // 運(yùn)動(dòng)模糊后的畫面 //

    v2f vert (appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        return o;
    }

    float4 frag1 (v2f i) : SV_Target
    {
        _CurWorldPos.w = 1;
        float4 curClipPos = mul(_CurVP, _CurWorldPos);
        float3 curNDCPos = curClipPos.rgb/curClipPos.w;

        _LastWorldPos.w = 1;
        float4 lastClipPos = mul(_LastVP, _LastWorldPos);
        float3 lastNDCPos = lastClipPos.rgb/lastClipPos.w;

        float2 speed = (curNDCPos.xy - lastNDCPos.xy)*0.5;              // 轉(zhuǎn)到ndc空間做速度計(jì)算 //
        float4 finalColor = float4(0,0,0,0);                            // 有顏色的部分透明度大于0, 其余部分透明度等于0 //
        for(int j=0; j<4; j++)
        {
            float2 tempUV = i.uv+j*speed*_BlurSize;
            finalColor += tex2D(_MainTex, tempUV);
        }
        finalColor *= 0.25;
        return finalColor;
    }

    float4 frag2 (v2f i) : SV_Target
    {
        float4 mainTex = tex2D(_MainTex, i.uv);
        float4 blurTex = tex2D(_BlurTex, i.uv);
        float4 finalColor = float4(0,0,0,1);
        if(blurTex.a > 0)
        {
            finalColor.rgb = lerp(mainTex.rgb, blurTex.rgb, blurTex.a);
        }
        else
        {
            finalColor.rgb = mainTex.rgb;
        }
        return finalColor;
    }
    ENDCG

    SubShader
    {
        Tags { "Queue"="Geometry" "RenderType"="Opaque" "IgnoreProjector"="True" }


        Cull Off
        ZWrite Off
        ZTest Always
        
        LOD 100

        // #0 //
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag1
            ENDCG
        }

        // #1 //
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag2          
            ENDCG
        }
    }
    
    Fallback Off
}

package文件
提取碼:j1ld

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容