1. 程式人生 > >Unity Shader實現運動模糊(二) : 物體運動產生模糊

Unity Shader實現運動模糊(二) : 物體運動產生模糊

上一篇 關於運動模糊的效果是根據VP矩陣重建世界空間座標,然後根據上一幀和當前幀的位置差作為速度方向,這種方式需要攝像機有位移才能產生效果,如果攝像機靜止不動,那麼VP矩陣也就不會有變化,所以上一幀和當前幀在NDC空間的位置也沒有變化,即speed==0,也就不會有運動模糊的效果,而想要在攝像機不動而物體移動時產生模糊效果,則需要另一種實現方式,這種方式更簡潔直觀。

大致思路是:

  1. C#中記錄運動物體的 當前幀的世界座標 和 當前幀的VP矩陣
  2. C#中記錄運動物體的 上一幀的世界座標 和 上一幀的VP矩陣
  3. Shader中根據上面的資訊求出 當前幀 和 上一幀的NDC座標
  4. NDC座標求差值得出速度方向,然後沿速度方向進行多次取樣後求平均值

這只是我自己的一個不成熟的實現思路,如果哪位大神有更合適的實現方式請一定賜教哈。

這種實現方式不需要對深度紋理進行取樣,因此攝像機也不用設定depthTextureMode。但是按照這個思路實現出來以後發現整個畫面都被模糊了,不只是運動的物體,連背後靜止的plane物體也被模糊了,原因在於使用了後處理,而後處理是對整個螢幕畫面進行的處理,所以想要只對運動的物體進行模糊,那麼需要單獨使用一個攝像機來看運動的物體,Shader中需要寫兩個pass,第一個pass實現運動物體的模糊,第二個pass把主攝像機的畫面和模糊後的畫面疊加到一起,思路如下:

  1. 呼叫Shader的第一個pass,實現運動的物體被模糊,結果渲染到一張RenderTexture上
  2. 把這張RenderTexture作為紋理引數傳遞給Shader
  3. 把主攝像機的畫面渲染到另一張RenderTexture上
  4. 呼叫Shader的第二個pass,把主攝像機的RT渲染到最終的RT上

第二個pass中會用到第一個pass處理後的結果。

攝像機相關的操作是:

  1. 把運動物體的layer設定到和靜止物體不同的層,比如可以新建一個MovingObject層
  2. 新建一個攝像機,用來只看運動物體,設定culling mask = MovingObject,Depth 比主攝像機小,其餘引數和主攝像機一致。
  3. 從主相機的culling mask中去除MovingObject層

效果圖:
使用一個攝像機時,運動的Cube和靜止的Plane都模糊了

使用兩個攝像機後,只有運動的Cube是模糊的,靜止的Plane是清晰的

那麼現在開始上程式碼,這樣看起來可能更清晰些。

C# 部分:

using UnityEngine;

// 運動模糊, 物體運動產生模糊效果 //
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;                              // 當前幀的世界空間座標 //
    private Vector3 m_lastWorldPos;                             // 上一幀的世界空間座標 //
    private Matrix4x4 m_curVP;                                       // 當前幀的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;

        // 運動模糊 //
        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;				// 運動模糊後的畫面 //

	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;				// 轉到ndc空間做速度計算 //
		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