69.运动模糊具体实现

  1. 69.屏幕后期处理效果-效果实现-运动模糊-具体实现
    1. 69.1 知识点
      1. 知识回顾:运动模糊基本原理
      2. 准备工作:搭建测试场景
      3. 实现运动模糊屏幕后期处理效果(Shader 实现)
        1. 主要步骤
      4. 实现运动模糊效果的主要步骤
        1. 新建Shader 名为运动模糊(MotionBlur) 删除其中无用代码
        2. 声明属性,包括主纹理和模糊程度
        3. SubShader语句块声明共享CG代码 CGINCLUDE…ENDCG,引用内置文件UnityCG.cginc并进行属性映射。声明结构体(顶点和UV)和顶点着色器函数(裁剪空间转换 uv坐标赋值)。
        4. 应用屏幕后处理效果标配指令
        5. 生成第一个 Pass ,用于混合 RGB 通道,设置了混合因子 Blend SrcAlpha OneMinusSrcAlpha(按 ((源颜色 * SrcAlpha) + (目标颜色 * (1 - SrcAlpha))) 混合)和颜色蒙版 ColorMask RGB(只改颜色缓冲区 RGB 通道)。其片元着色器是先对主纹理采样,再利用模糊程度作为A通道与颜色缓冲区颜色进行混合。
        6. 生成第二个Pass,用于混合A通道,设置了混合因子Blend One Zero(最终颜色按(源颜色 * 1) + (目标颜色 * 0)计算)和颜色蒙版ColorMask A(只改颜色缓冲区A通道),其片元着色器是对主纹理采样。
        7. 关闭回退,不使用默认的备用Shader
      5. 实现运动模糊屏幕后期处理效果(C# 实现)
        1. 主要步骤
        2. 创建C#脚本,名为运动模糊MotionBlur。继承屏幕后处理基类PostEffectBase。声明成员属性,包括公共的模糊程度和私有的堆积纹理 accumulation Texture(用于存储上一次渲染结果)。
        3. 重写OnRenderImage函数。若堆积纹理为空或宽高变化,则初始化渲染纹理,设置其hideFlags为HideFlags.HideAndDontSave(让其不保存)。往shader中设置模糊程度属性,将源纹理利用材质写入到堆积纹理中(相当于记录本次渲染结果),将堆积纹理写入目标纹理中。
        4. 组件失活时销毁堆积纹理
        5. 挂载C#脚本到摄像机并关联shader,移动立方体可以看到模糊效果
    2. 69.2 知识点代码
      1. Lesson69_屏幕后期处理效果_效果实现_运动模糊_具体实现.cs
      2. Lesson69_MotionBlur.shader
      3. Lesson69_MotionBlur.cs

69.屏幕后期处理效果-效果实现-运动模糊-具体实现


69.1 知识点

知识回顾:运动模糊基本原理

  • 保存之前的渲染结果,不断将当前的渲染图像叠加到之前的渲染图像中。

  • 通过 RenderTexture 进行保存,利用两个 Pass 实现混合叠加。

  • Pass 1: 混合 RGB 通道

    • 根据模糊程度,由两张图片决定最终混合效果。
    • 混合因子:Blend SrcAlpha OneMinusSrcAlpha
      公式:(源颜色 * SrcAlpha) + (目标颜色 * (1 - SrcAlpha))
    • 颜色蒙版:ColorMask RGB
      作用:仅改变颜色缓冲区中的 RGB 通道。
  • Pass 2: 混合 A 通道

    • 使用当前屏幕图像的 A 通道决定混合效果。
    • 混合因子:Blend One Zero
      公式:(源颜色 * 1) + (目标颜色 * 0)
    • 颜色蒙版:ColorMask A
      作用:仅改变颜色缓冲区中的 A 通道。

准备工作:搭建测试场景

实现运动模糊屏幕后期处理效果(Shader 实现)

主要步骤

实现运动模糊效果的主要步骤

  1. 新建 Shader
    创建一个名为运动模糊(MotionBlur)的 Shader,删除其中无用代码。

  2. 属性声明

    • 主纹理 _MainTex
    • 模糊程度 _BlurAmount
  3. 共享 CG 代码
    使用 CGINCLUDEENDCG 包围共享代码块:

    • 引用内置文件 UnityCG.cginc
    • 进行属性映射
    • 定义结构体(顶点和 UV)
    • 实现顶点着色器(裁剪空间转换,UV 坐标赋值)
  4. 屏幕后处理效果的标配设置

    • ZTest Always(始终进行深度测试)
    • Cull Off(关闭背面剔除)
    • ZWrite Off(关闭深度写入)
  5. 第一个 Pass(混合 RGB 通道)

    • 设置混合因子和颜色蒙版:
      • Blend SrcAlpha OneMinusSrcAlpha(公式:(源颜色 * SrcAlpha) + (目标颜色 * (1 - SrcAlpha))
      • ColorMask RGB(只改变颜色缓冲区中的 RGB 通道)
    • 编写片元着色器:
      • 对主纹理采样,并利用模糊程度作为 A 通道与颜色缓冲区的颜色进行混合。
  6. 第二个 Pass(混合 A 通道)

    • 设置混合因子和颜色蒙版:
      • Blend One Zero(公式:最终颜色 = (源颜色 * 1) + (目标颜色 * 0)
      • ColorMask A(只改变颜色缓冲区中的 A 通道)
    • 编写片元着色器:
      • 对主纹理采样。
  7. 关闭 Fallback
    使用 Fallback Off,禁用默认的备用 Shader。

新建Shader 名为运动模糊(MotionBlur) 删除其中无用代码

Shader "Unlit/Lesson69_MotionBlur"
{
    Properties
    {
    }
    SubShader
    {
        
    
    }
    
}

声明属性,包括主纹理和模糊程度

Properties
{
    // 主纹理属性
    _MainTex ("Texture", 2D) = "white" {}
    // 模糊程度属性,用于控制模糊效果的强度
    _BlurAmount("BlurAmount", Float) = 0.5
}

SubShader语句块声明共享CG代码 CGINCLUDE…ENDCG,引用内置文件UnityCG.cginc并进行属性映射。声明结构体(顶点和UV)和顶点着色器函数(裁剪空间转换 uv坐标赋值)。

CGINCLUDE
#include "UnityCG.cginc"

// 声明主纹理变量
sampler2D _MainTex;

// 声明模糊程度变量
fixed _BlurAmount;

// 定义顶点着色器输出结构体
struct v2f
{
    // 纹理坐标,用于在片段着色器中采样纹理
    float2 uv : TEXCOORD0;
    // 顶点在裁剪空间的位置,用于后续的渲染处理
    float4 vertex : SV_POSITION;
};

// 顶点着色器函数,将顶点从模型空间转换到裁剪空间,并传递纹理坐标
v2f vert(appdata_base appdata_base)
{
    v2f v2f;
    // 将顶点从模型空间转换到裁剪空间
    v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
    // 传递纹理坐标
    v2f.uv = appdata_base.texcoord;
    return v2f;
}
ENDCG

应用屏幕后处理效果标配指令

// 屏幕后处理效果的标准设置,始终进行深度测试,关闭背面剔除,不写入深度信息
ZTest Always
Cull Off
ZWrite Off

生成第一个 Pass ,用于混合 RGB 通道,设置了混合因子 Blend SrcAlpha OneMinusSrcAlpha(按 ((源颜色 * SrcAlpha) + (目标颜色 * (1 - SrcAlpha))) 混合)和颜色蒙版 ColorMask RGB(只改颜色缓冲区 RGB 通道)。其片元着色器是先对主纹理采样,再利用模糊程度作为A通道与颜色缓冲区颜色进行混合。

// 第一个Pass,用于混合RGB通道
Pass
{
    // 设置混合模式,根据公式((源颜色 * _BlurAmount) + (目标颜色 * (1 - _BlurAmount)))进行混合
    Blend SrcAlpha OneMinusSrcAlpha
    // 只改变颜色缓冲区中的RGB通道,不影响透明度通道
    ColorMask RGB

    CGPROGRAM
    // 指定顶点着色器函数
    #pragma vertex vert
    // 指定片段着色器函数用于处理RGB通道
    #pragma fragment fragRGB

    // 片段着色器函数,用于处理RGB通道,返回带有模糊程度的RGB颜色值
    fixed4 fragRGB(v2f v2f) : SV_Target
    {
        // 采样主纹理的RGB颜色值,并结合模糊程度返回
        return fixed4(tex2D(_MainTex, v2f.uv).rgb, _BlurAmount);
    }
    ENDCG
}

生成第二个Pass,用于混合A通道,设置了混合因子Blend One Zero(最终颜色按(源颜色 * 1) + (目标颜色 * 0)计算)和颜色蒙版ColorMask A(只改颜色缓冲区A通道),其片元着色器是对主纹理采样。

// 第二个Pass,用于混合A通道
Pass
{
    // 设置混合模式,根据公式(最终颜色 = (源颜色 * 1) + (目标颜色 * 0))进行混合,即只使用源颜色
    Blend One Zero
    // 只改变颜色缓冲区中的A通道,不影响RGB通道
    ColorMask A

    CGPROGRAM
    // 指定顶点着色器函数
    #pragma vertex vert
    // 指定片段着色器函数用于处理A通道
    #pragma fragment fragA

    // 片段着色器函数,用于处理A通道,返回采样主纹理的颜色值
    fixed4 fragA(v2f v2f) : SV_Target
    {
        return fixed4(tex2D(_MainTex, v2f.uv));
    }
    ENDCG
}

关闭回退,不使用默认的备用Shader

// 关闭回退,即不使用默认的备用Shader
Fallback Off

实现运动模糊屏幕后期处理效果(C# 实现)

主要步骤

创建C#脚本,名为运动模糊MotionBlur。继承屏幕后处理基类PostEffectBase。声明成员属性,包括公共的模糊程度和私有的堆积纹理 accumulation Texture(用于存储上一次渲染结果)。

using UnityEngine;

public class Lesson69_MotionBlur : PostEffectBase
{
    // 模糊程度
    [Range(0, 0.9f)] public float blurAmount = 0.5f;

    //堆积纹理 用于存储之前渲染的结果的 渲染纹理
    private RenderTexture accumulationTex;

    protected override void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
        
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
}

重写OnRenderImage函数。若堆积纹理为空或宽高变化,则初始化渲染纹理,设置其hideFlags为HideFlags.HideAndDontSave(让其不保存)。往shader中设置模糊程度属性,将源纹理利用材质写入到堆积纹理中(相当于记录本次渲染结果),将堆积纹理写入目标纹理中。

protected override void OnRenderImage(RenderTexture source, RenderTexture destination)
{
    if (material != null)
    {
        //初始化堆积纹理 如果为空 或者 屏幕宽高变化了 都需要重新初始化
        if (accumulationTex == null ||
            accumulationTex.width != source.width ||
            accumulationTex.height != source.height)
        {
            // 立刻删除之前的堆积纹理
            DestroyImmediate(accumulationTex);
            
            //初始化且不让他保存
            accumulationTex = new RenderTexture(source.width, source.height, 0);
            accumulationTex.hideFlags = HideFlags.HideAndDontSave;
            
            //保证第一次 累积纹理中也是有内容 因为之后 它的颜色 会作为颜色缓冲区中的颜色
            Graphics.Blit(source, accumulationTex);
        }

        //1 - 模糊程度的目的 是因为 希望大到的效果是 模糊程度值越大 越模糊
        //因为Shader中的混合因子的计算方式决定的 因此 我们需要1 - 它
        material.SetFloat("_BlurAmount", 1.0f - blurAmount);

        //利用我们的材质 进行混合处理
        //第二个参数 accumulationTex 有内容时  它会作为颜色缓冲区的颜色来进行处理
        //没有直接写入目标中的目的 也是可以通过accumulationTex记录当前渲染结果
        //那么在下一次时 它就相当于是上一次的结果了
        //这样accumulationTex就不会为空 可以一直叠加 达到运动模糊的效果
        Graphics.Blit(source, accumulationTex, material);

        Graphics.Blit(accumulationTex, destination);
    }
    else
    {
        Graphics.Blit(source, destination);
    }
}

组件失活时销毁堆积纹理

/// <summary>
/// 如果脚本失活 那么把累积纹理删除掉
/// </summary>
private void OnDisable()
{
    DestroyImmediate(accumulationTex);
}

挂载C#脚本到摄像机并关联shader,移动立方体可以看到模糊效果



69.2 知识点代码

Lesson69_屏幕后期处理效果_效果实现_运动模糊_具体实现.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson69_屏幕后期处理效果_效果实现_运动模糊_具体实现 : MonoBehaviour
{
    void Start()
    {
        #region 知识回顾 运动模糊基本原理

        //保存之前的渲染结果,不断把当前的渲染图像叠加到之前的渲染图像中
        //通过RenderTexture来进行保存,用2个Pass来进行混合叠加

        //一个Pass混合RGB通道,由两张图片根据模糊程度决定最终混合效果
        //Blend SrcAlpha OneMinusSrcAlpha((源颜色 * SrcAlpha) + (目标颜色 * (1 - SrcAlpha)))
        //ColorMask RGB (只改变颜色缓冲区中的RGB通道)

        //一个Pass混合A通道,由当前屏幕图像的A通道来决定
        //Blend One Zero(最终颜色 = (源颜色 * 1) + (目标颜色 * 0))
        //ColorMask A(只改变颜色缓冲区中的A通道)

        #endregion

        #region 准备工作 搭建测试场景

        #endregion

        #region 知识点一 实现 运动模糊屏幕后期处理效果 对应 Shader

        //1.新建Shader 名为运动模糊(MotionBlur) 删除其中无用代码
        
        //2.属性声明
        //  主纹理 _MainTex
        //  模糊程度 _BlurAmount
        
        //3.共享CG代码 CGINCLUDE...ENDCG
        //  内置文件UnityCG.cginc引用
        //  属性映射
        //  结构体(顶点和UV)
        //  顶点着色器(裁剪空间转换 uv坐标赋值)
        
        //4.屏幕后处理效果标配
        //  ZTest Always
        //  Cull Off
        //  ZWrite Off
        
        //5.第一个Pass(用于混合RGB通道)
        //  混合因子 和 颜色蒙版设置
        //      Blend SrcAlpha OneMinusSrcAlpha((源颜色 * SrcAlpha) + (目标颜色 * (1 - SrcAlpha)))
        //      ColorMask RGB (只改变颜色缓冲区中的RGB通道)
        //  片元着色器
        //      对主纹理采样后利用模糊程度作为A通道与颜色缓冲区颜色进行混合
        
        //6.第二个Pass(用户混合A通道)
        //  混合因子 和 颜色蒙版设置
        //      Blend One Zero(最终颜色 = (源颜色 * 1) + (目标颜色 * 0))
        //      ColorMask A(只改变颜色缓冲区中的A通道)
        //  片元着色器
        //      对主纹理采样
        
        //7.FallBack Off

        #endregion

        #region 知识点二 实现 运动模糊屏幕后期处理效果 对应 C#

        //1.创建C#脚本,名为运动模糊MotionBlur
        
        //2.继承屏幕后处理基类PostEffectBase
        
        //3.声明成员属性
        //  公共的模糊程度
        //  私有的堆积纹理 accumulation Texture(用于存储上一次渲染结果)
        
        //4.重写OnRenderImage函数
        //  4-1.若堆积纹理为空或宽高变化 则初始化渲染纹理
        //      设置其hideFlags为HideFlags.HideAndDontSave(让其不保存)
        //  4-2.设置模糊程度属性
        //  4-3.将源纹理利用材质写入到堆积纹理中(相当于记录本次渲染结果)
        //  4-4.将堆积纹理写入目标纹理中
        
        //5.组件失活时销毁堆积纹理

        #endregion
    }
}

Lesson69_MotionBlur.shader

Shader "Unlit/Lesson69_MotionBlur"
{
    Properties
    {
        // 主纹理属性
        _MainTex ("Texture", 2D) = "white" {}
        // 模糊程度属性,用于控制模糊效果的强度
        _BlurAmount("BlurAmount", Float) = 0.5
    }

    SubShader
    {
        CGINCLUDE
        #include "UnityCG.cginc"

        // 声明主纹理变量
        sampler2D _MainTex;
        
        // 声明模糊程度变量
        fixed _BlurAmount;

        // 定义顶点着色器输出结构体
        struct v2f
        {
            // 纹理坐标,用于在片段着色器中采样纹理
            float2 uv : TEXCOORD0;
            // 顶点在裁剪空间的位置,用于后续的渲染处理
            float4 vertex : SV_POSITION;
        };

        // 顶点着色器函数,将顶点从模型空间转换到裁剪空间,并传递纹理坐标
        v2f vert(appdata_base appdata_base)
        {
            v2f v2f;
            // 将顶点从模型空间转换到裁剪空间
            v2f.vertex = UnityObjectToClipPos(appdata_base.vertex);
            // 传递纹理坐标
            v2f.uv = appdata_base.texcoord;
            return v2f;
        }
        ENDCG

        // 屏幕后处理效果的标准设置,始终进行深度测试,关闭背面剔除,不写入深度信息
        ZTest Always
        Cull Off
        ZWrite Off

        // 第一个Pass,用于混合RGB通道
        Pass
        {
            // 设置混合模式,根据公式((源颜色 * _BlurAmount) + (目标颜色 * (1 - _BlurAmount)))进行混合
            Blend SrcAlpha OneMinusSrcAlpha
            // 只改变颜色缓冲区中的RGB通道,不影响透明度通道
            ColorMask RGB

            CGPROGRAM
            // 指定顶点着色器函数
            #pragma vertex vert
            // 指定片段着色器函数用于处理RGB通道
            #pragma fragment fragRGB

            // 片段着色器函数,用于处理RGB通道,返回带有模糊程度的RGB颜色值
            fixed4 fragRGB(v2f v2f) : SV_Target
            {
                // 采样主纹理的RGB颜色值,并结合模糊程度返回
                return fixed4(tex2D(_MainTex, v2f.uv).rgb, _BlurAmount);
            }
            ENDCG
        }

        // 第二个Pass,用于混合A通道
        Pass
        {
            // 设置混合模式,根据公式(最终颜色 = (源颜色 * 1) + (目标颜色 * 0))进行混合,即只使用源颜色
            Blend One Zero
            // 只改变颜色缓冲区中的A通道,不影响RGB通道
            ColorMask A

            CGPROGRAM
            // 指定顶点着色器函数
            #pragma vertex vert
            // 指定片段着色器函数用于处理A通道
            #pragma fragment fragA

            // 片段着色器函数,用于处理A通道,返回采样主纹理的颜色值
            fixed4 fragA(v2f v2f) : SV_Target
            {
                return fixed4(tex2D(_MainTex, v2f.uv));
            }
            ENDCG
        }
    }
    // 关闭回退,即不使用默认的备用Shader
    Fallback Off
}

Lesson69_MotionBlur.cs

using UnityEngine;

public class Lesson69_MotionBlur : PostEffectBase
{
    // 模糊程度
    [Range(0, 0.9f)] public float blurAmount = 0.5f;

    //堆积纹理 用于存储之前渲染的结果的 渲染纹理
    private RenderTexture accumulationTex;

    protected override void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            //初始化堆积纹理 如果为空 或者 屏幕宽高变化了 都需要重新初始化
            if (accumulationTex == null ||
                accumulationTex.width != source.width ||
                accumulationTex.height != source.height)
            {
                // 立刻删除之前的堆积纹理
                DestroyImmediate(accumulationTex);
                
                //初始化且不让他保存
                accumulationTex = new RenderTexture(source.width, source.height, 0);
                accumulationTex.hideFlags = HideFlags.HideAndDontSave;
                
                //保证第一次 累积纹理中也是有内容 因为之后 它的颜色 会作为颜色缓冲区中的颜色
                Graphics.Blit(source, accumulationTex);
            }

            //1 - 模糊程度的目的 是因为 希望大到的效果是 模糊程度值越大 越模糊
            //因为Shader中的混合因子的计算方式决定的 因此 我们需要1 - 它
            material.SetFloat("_BlurAmount", 1.0f - blurAmount);

            //利用我们的材质 进行混合处理
            //第二个参数 accumulationTex 有内容时  它会作为颜色缓冲区的颜色来进行处理
            //没有直接写入目标中的目的 也是可以通过accumulationTex记录当前渲染结果
            //那么在下一次时 它就相当于是上一次的结果了
            //这样accumulationTex就不会为空 可以一直叠加 达到运动模糊的效果
            Graphics.Blit(source, accumulationTex, material);

            Graphics.Blit(accumulationTex, destination);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }

    /// <summary>
    /// 如果脚本失活 那么把累积纹理删除掉
    /// </summary>
    private void OnDisable()
    {
        DestroyImmediate(accumulationTex);
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏