Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

运动模糊

一、什么是动态模糊


运动模糊效果
是一种 用于模拟真实世界中快速移动物体产生的模糊现象的图像处理技术
当一个物体以较高速度移动时,由于人眼或摄像机的曝光时间过长,该物体会在图像中留下模糊
的运动轨迹。这种效果游戏、动画、电影中被广泛应用,以增加视觉真实性和动感。

二、基本原理

  1. 累积缓存
    物体快速运动时存储多帧图像信息,取他们之间的加权平均值作为最后的运动模糊的图像
    • 优点:效果好、质量高
    • 缺点:存储消耗大、计算量大
  2. 速度缓存
    物体快速运动时存储多帧运动速度信息,利用速度来决定模糊的方向和大小
    • 优点:性能积累缓存好
    • 缺点:效果较差,可能产生重影和伪影 ## 三、如何实现


这里我们通过基于累积缓存来实现动态模糊效果。
但是我们不需要像累积缓存中那样存储多张场景信息,但是需要保存之前的渲染结果,不断把当前的渲
染图像叠加到之前的渲染图像中,从而产生一种运动轨迹的视觉效果

相当于是基于累积缓存的优化,
性能会更好,但是模糊效果可能略有欠缺,但是效果也是可以接受的。

简单来说:
因为使用Graphics.Blit(源纹理,目标纹理,材质)方法时
如果目标纹理中存在内容,会直接将目标纹理中的颜色作为颜色缓冲区的颜色
所以通过用一个RenderTexture记录上一次渲染的信息,然后每一次用新的屏幕图像信息和上一次的图像信息进
混合渲染,从而产生模糊效果(相当于用一张图保留了之前n次的叠加渲染结果)

1.主要混合思路

  1. RGB通道由两张图片根据模糊程度决定最终效果
  2. A通道根据当前屏幕图像决定
大致流程图

Shader中的具体表现

  1. 利用一个模糊程度的变量控制运动的模糊程度
  2. 利用两个Pass进行混合处理的方式
    1. 第一个Pass
      当前屏幕图像 和 上一次屏幕图像的 进行指定的RGB通道的颜色混合
      目的是:利用模糊程度的参数控制两张图片的混合效果,值越大,上次屏幕保留的内容就越多
    2. 第二个Pass
      利用第一个Pass处理好后的颜色在 和 源纹理进行 A的通道混合
      目的是:保留源纹理的透明信息

2.如何设置混合方式

第一个Pass

1
2
Blend SrcAlpha OneMinusSrcAlpha //((源颜色 * SrcAlpha) + (目标颜色 * (1 - SrcAlpha)))
ColorMask RGB //(只改变颜色缓冲区的RGB通道)

第二个Pass

1
2
Blend One Zero //(最终颜色 = (源颜色 * 1) + (目标颜色 * 0))
ColorMask A //(只改变颜色缓冲区的A通道)

四、具体实现

1.实现思路

1.)Shader部分

  1. 属性声明
    • 主纹理 _MainTex
    • 模糊程度 _BlurAmount
  2. 共享CG代码部分
    • 内置文件引用
    • 属性映射
    • 结构体(顶点坐标和uv)
    • 顶点着色器(顶点坐标转换、uv坐标赋值)
  3. 屏幕后处理标配
    • ZTest Always
    • Cull Off
    • ZWrite Off
  4. 第一个Pass(用于混合RGB通道)
    • 混合因子和颜色蒙版设置
    • 片元着色器
      主要是对纹理采样后利用模糊程度作为A通道与颜色缓冲区颜色进行混合
  5. 第二个Pass(用于混合A通道)
    • 混合因子和颜色蒙版设置
    • 片元着色器
      对主纹理采样
  6. FallBack off

2.)C#部分

  1. 声明成员属性
    • 公共的模糊程度
    • 私有的堆积纹理 accumulation Texture 主要作用就是用于存储上次的渲染结果
  2. 重写OnRenderTexture
    1. 若堆积纹理为空或宽高变化 则初始化渲染纹理 设置其hideFlags为HideFlags.HideAndDontSave(让其不保存)
    2. 设置模糊层度属性
    3. 将源纹理利用材质写入到堆积纹理中(相当于记录本次渲染结果)
    4. 将堆积纹理写入目标纹理中
  3. 组件失活时,销毁堆积纹理

2.实现示例

1.)Shader部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
Shader "Unlit/MotionBlur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
//模糊程度变量
_BlurAmount("BlurAmount" ,Float) = 0.5
}
SubShader
{

CGINCLUDE
#include "UnityCG.cginc"

sampler2D _MainTex;
float _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
Tags {"RenderType"="Opaque"}
//第一个Pass 用于混合RGB通道
Pass
{
//屏幕后处理标配
ZTest Always
Cull Off
ZWrite Off

Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB

CGPROGRAM
#pragma vertex vert
#pragma fragment fragRGB

fixed4 fragRGB(v2f i) : SV_Target
{
//这里相当于((源颜色 * _BlurAmount) + (目标颜色 * (1 - _BlurAmount)))
return fixed4(tex2D(_MainTex,i.uv).rgb, _BlurAmount);
}
ENDCG
}
//第二个Pass 用于混合A通道
Pass
{
//(最终颜色 = (源颜色 * 1) + (目标颜色 * 0))
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
}

2.)C#部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MotionBlur : PostEffectBase
{
[Range(0,0.9f)]
public float blurAmount = 0.5f;
//堆积纹理 用于存储之前渲染结果的 渲染纹理
private RenderTexture _accumulationTexture;
protected override void OnRenderImage(RenderTexture source, RenderTexture destination)
{
//初始化堆积纹理 如果为空 或者宽高变化了 都需要进行初始化
if (material)
{
if (_accumulationTexture == null ||
_accumulationTexture.width != source.width || _accumulationTexture.height != source.height
)
{
//初始化之前先删除
DestroyImmediate(_accumulationTexture);
//初始化
_accumulationTexture = new RenderTexture(source.width, source.height, 0);
_accumulationTexture.hideFlags = HideFlags.HideAndDontSave;
//保证第一次 累积纹理中是有内容的 因为之后 它的颜色 会作为颜色缓冲区中的颜色
Graphics.Blit(source,_accumulationTexture);
}
//1 - 模糊程度
//目的是 达到效果是模糊层度越大 越模糊
//这是由Shader中的混合因子决定的 因此我们需要 1 - 模糊程度
material.SetFloat("_BlurAmount",1.0f - blurAmount);
//利用材质进行混合处理
//第二个参数有内容时,他会作为颜色缓冲区的颜色进行处理
//没有直接写入目标中的目的 也是可以通过_accumulationTexture记录当前渲染结果
//那么在下一次时 就相当于上一次的结果了
Graphics.Blit(source, _accumulationTexture, material);
Graphics.Blit(_accumulationTexture,destination);
}
else
Graphics.Blit(source, destination);

}
/// <summary>
/// 如果脚本失活就删除累积纹理
/// </summary>
private void OnDisable()
{
DestroyImmediate(_accumulationTexture);
}
}

评论