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 实现)
主要步骤
实现运动模糊效果的主要步骤
新建 Shader
创建一个名为运动模糊(MotionBlur
)的 Shader,删除其中无用代码。属性声明
- 主纹理
_MainTex
- 模糊程度
_BlurAmount
- 主纹理
共享 CG 代码
使用CGINCLUDE
和ENDCG
包围共享代码块:- 引用内置文件
UnityCG.cginc
- 进行属性映射
- 定义结构体(顶点和 UV)
- 实现顶点着色器(裁剪空间转换,UV 坐标赋值)
- 引用内置文件
屏幕后处理效果的标配设置
ZTest Always
(始终进行深度测试)Cull Off
(关闭背面剔除)ZWrite Off
(关闭深度写入)
第一个 Pass(混合 RGB 通道)
- 设置混合因子和颜色蒙版:
Blend SrcAlpha OneMinusSrcAlpha
(公式:(源颜色 * SrcAlpha) + (目标颜色 * (1 - SrcAlpha))
)ColorMask RGB
(只改变颜色缓冲区中的 RGB 通道)
- 编写片元着色器:
- 对主纹理采样,并利用模糊程度作为 A 通道与颜色缓冲区的颜色进行混合。
- 设置混合因子和颜色蒙版:
第二个 Pass(混合 A 通道)
- 设置混合因子和颜色蒙版:
Blend One Zero
(公式:最终颜色 = (源颜色 * 1) + (目标颜色 * 0)
)ColorMask A
(只改变颜色缓冲区中的 A 通道)
- 编写片元着色器:
- 对主纹理采样。
- 设置混合因子和颜色蒙版:
关闭 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