1、运动模糊效果
运动模糊效果,是一种用于 模拟真实世界中快速移动物体产生的模糊现象 的图像处理技术,当一个物体以较高速度移动时,由于人眼或摄像机的曝光时间过长,该物体会在图像中留下模糊的运动轨迹。这种效果游戏、动画、电影中被广泛应用,以增加视觉真实性和动感。
2、基本原理
想要在屏幕后期处理中实现运动模糊,一般有两种常用方式:
- 累积缓存:物体快速移动时存储多帧图像信息,取它们之间的加权平均值作为最后的运动模糊图像。优点:质量高、效果好;缺点:计算量大,存储开销大
- 速度缓存:物体快速移动时存储多帧运动速度信息,利用速度来决定模糊的方向和大小。优点:性能较累积缓存好;缺点:效果较差,可能产生重影和伪影
基于累积缓存来实现动态模糊效果,但是不需要像累积缓存中那样存储多张场景信息,但是需要保存之前的渲染结果,不断把当前的渲染图像叠加到之前的渲染图像中,从而产生一种运动轨迹的视觉效果。相当于是基于累积缓存的优化,
性能会更好,但是模糊效果可能略有欠缺,但是效果也是可以接受的。
它的基本原理是:
用一个RenderTexture记录上一次渲染的信息,然后每一次用新的屏幕图像信息和上一次的图像信息进行混合渲染,从而产生模糊效果(相当于用一张图保留了之前n次的叠加渲染结果)
在使用Graphics.Blit(源纹理,目标纹理,材质)方法时,如果目标纹理中包含内容,会直接认为目标纹理中的颜色为颜色缓冲区中的颜色,因此完全可以利用该方法,配合Shader代码将两张图片信息进行混合处理,从而实现运动模糊效果。
它的主要混合思路是:
- RGB通道由两张图片根据模糊程度决定最终效果
- A通道根据当前屏幕图像决定
利用一个模糊程度变量来控制运动模糊程度,值越大模糊程度越强;越小模糊程度越弱
利用两个Pass进行混合处理的方式:
第一个Pass:让当前屏幕图像 和 上一次的屏幕图像 进行指定RGB通道的颜色混合,目的是利用模糊程度参数控制两张图片的混合效果,值越大上一次屏幕内容保留的越多
第二个Pass:利用第一个Pass处理后得到的颜色在 和源纹理进行A通道的颜色混合,目的是保留源纹理透明度信息
混合方式如何设置
第一个Pass:
- Blend SrcAlpha OneMinusSrcAlpha((源颜色 * SrcAlpha) + (目标颜色 * (1 - SrcAlpha)))
- ColorMask RGB (只改变颜色缓冲区中的RGB通道)
第二个Pass:
- Blend One Zero(最终颜色 = (源颜色 * 1) + (目标颜色 * 0))
- ColorMask A(只改变颜色缓冲区中的A通道)
Shader "ShaderProj/12/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 v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
ENDCG
ZTest Always
Cull Off
ZWrite Off
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB
CGPROGRAM
#pragma vertex vert
#pragma fragment fragRGB
fixed4 fragRGB (v2f i) : SV_Target
{
return fixed4(tex2D(_MainTex, i.uv).rgb, _BlurAmount);
}
ENDCG
}
Pass
{
Blend One Zero
ColorMask A
CGPROGRAM
#pragma vertex vert
#pragma fragment fragA
fixed4 fragA (v2f i) : SV_Target
{
return fixed4(tex2D(_MainTex, i.uv));
}
ENDCG
}
}
Fallback Off
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class 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);
}
material.SetFloat("_BlurAmount", 1.0f - blurAmount);
//没有直接写入目标中的目的 也是可以通过accumulationTex记录当前渲染结果
//那么在下一次时 它就相当于是上一次的结果了
Graphics.Blit(source, accumulationTex, material);
Graphics.Blit(accumulationTex, destination);
}
else
Graphics.Blit(source, destination);
}
// 如果脚本失活 那么把累积纹理删除掉
private void OnDisable() {
DestroyImmediate(accumulationTex);
}
}