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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容