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

利用深度纹理实现运动模糊效果

一、如何实现


这里就是基于速度缓存的方式来进行
只需要用当前帧的位置和上一帧的位置进行计算,得到位置差,从而得到该像素的速度矢量
想要得到位置差,我们可以利用深度纹理中的信息来进行计算

注意点:

  1. 这种实现的方式 只适合场景静止,即摄像头快速移动的情况
    不太适合用于物体快速移动产生的运动模糊效果,只有摄像头移动时才能看见运动模糊的效果
  2. 这种实现方式 并不是基于真实物理运动规律来计算的,只是近似计算!
    它符合图形学的基本规则:看起来对即可

二、基本原理


得到像素当前帧和上一帧中在裁剪空间下的位置
利用两个位置计算出物体的运动方向,从而模拟出运动模糊的效果

关键点:

  1. 如何得到像素当前帧和上一帧在裁剪空间下的位置
  2. 如何得到运动方向
  3. 如何模拟出运动模糊效果

三、如何处理这些关键点

1.如何得到像素当前帧和上一帧在裁剪空间下的位置

  1. 利用uv坐标和深度值组合成一个裁剪空间下的组合坐标 newClipPos
    float clipPos = float4(uv.x,uv.y,depth,1);
    我们知道uv坐标的取值范围为01,通过宏取出的深度值也是01,而裁剪空间下的坐标范围为-1~1
    因此我们需要将七修改到裁剪空间坐标系下的范围内
    float clipPos = float4(uv.x*2-1,uv*2-1,depth*2-1,1);
  2. 利用 这一帧 世界空间->裁剪空间 的变换矩阵 nowM的逆矩阵
    将刚才裁剪空间下的组合坐标nowClipPos转换到世界空间中
    Camera.projectionMatrix;//相机投影到裁剪空间的变换矩阵
    Camera.worldToCameraMatrix;//世界空间到观察空间的变换矩阵
    Matrix4x4 worldToClipMatrix = Camera.projectionMatrix * Camera.worldToCameraMatrix;//世界空间到裁剪空间下的变换矩阵
    Martix4x4 clipToWorldMatrix = worldToClipMartrix.inverse;//裁剪空间到世界空间的转换矩阵
    我们只需要利用上面的这个裁剪空间到世界空间的变换矩阵,就可以将刚才得到的裁剪空间下的组合坐标变换到世界空间下了
  3. 利用上一帧的 世界空间->裁剪空间 的变换矩阵oldM得到
    上一帧世界空间下的组合坐标oldClipPos在裁剪空间下的位置
    利用上一帧的世界坐标—>裁剪空间的变换矩阵得到上一帧该世界空间下的组合坐标在裁剪空间下位置 ### 2.如何得到运动方向


直接用 当前位置.xy - 上一帧位置.xy 便可以得到移动方向

3.如何模拟运动模糊效果


在获取到像素在裁剪空间的移动方向,相当于知道了像素在"uv纹理空间中的移动方向"
那么我们只需要利用这个方向在纹理中进行多次uv坐标的偏移采样后,得到的颜色累加,最后计算数平均值即可
此时我们通常会加入一个"模糊偏移量"用于控制模糊程度,只需要在每次采样时进行 方向 * 模糊偏移量 的偏移采样

四、实现示例

1.Shader部分

1.实现思路

  1. 属性声明
    • 主纹理 _MainTex
    • 模糊偏移位置 _BlurSize
  2. 属性映射
    • 深度纹理 _CameraDepthTexture
    • 当前帧的裁剪空间到世界空间的变换矩阵 float4x4 _CameraToWorldMatrix
    • 上一帧世界空间到裁剪空间的变换矩阵 float4x4 _FrontWorldToClipMatrix
  3. 屏幕后处理标配
    • ZTest Always
    • Cull Off
    • ZWrite Off
  4. 结构体 顶点和uv坐标
  5. 顶点着色器 顶点转换、uv坐标赋值
  6. 片元着色器
    1. 得到裁剪空间下的两个点
      • 点一:深度值获取,构建裁剪空间下组合坐标 uv和深度
      • 点二:裁剪空间坐标转世界空间(注意进行齐次除法),利用上一帧变换矩阵将世界空间的坐标转换到裁剪空间下
    2. 得到运动方向 用 当前帧点 - 上一帧点 得到运动方向
    3. 进行模糊处理 利用模糊偏移量变量进行三次偏移采样颜色后机械能平均值处理
  7. FallBack off #### 2.实现示例
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
Shader "Unlit/DepthNormalMotionBlur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
//用于控制模糊程度的 模糊偏移值
_BlurSize("BlurSize",Float) = 0.5
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}

Pass
{
//屏幕后处理标配
ZTest Always
Cull Off
ZWrite Off

CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct v2f
{
float2 uv : TEXCOORD0;
float2 uv_depth : TEXCOORD1;
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_TexelSize;
float _BlurSize;
sampler2D _CameraDepthTexture;//深度纹理
float4x4 _ClipToWorldMatrix;//当前帧裁剪空间到世界空间的变换矩阵
float4x4 _FrontWorldToClipMatrix;//上一帧世界空间到裁剪空间的变换矩阵

v2f vert(appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
o.uv_depth = v.texcoord;
//多平台注意翻转
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize.y < 0)
o.uv_depth.y = 1 - o.uv_depth.y;
#endif
return o;
}

fixed4 frag(v2f i) : SV_Target
{
//1.得到裁剪空间下的两个点
//获取深度值
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth);
//构建裁剪空间下的组合坐标 把0~1范围变化到-1~1
//第一个点
float4 nowClipPos = float4(i.uv.x * 2 - 1,i.uv.y * 2 - 1,depth* 2 - 1,1);
//用裁剪空间到世界空间的变换矩阵 得到 世界空间下的点
float4 worldPos = mul(_ClipToWorldMatrix,nowClipPos);
//进行齐次除法
worldPos /= worldPos.w;
//利用上一帧变换矩阵 得到上一帧 对应的裁剪空间下的点
//第二个点
float4 oldClipPos = mul(_FrontWorldToClipMatrix,worldPos);
oldClipPos /= oldClipPos.w;
//2.得到运动方向
//float2 moveDir = nowClipPos.xy - oldClipPos.xy;
//降低运动模糊的效果
float2 moveDir = (nowClipPos.xy - oldClipPos.xy)/2;
//3.进行模糊处理
float2 uv = i.uv;
float4 color = float4(0,0,0,0);
for (int it = 0;it<3;it++)
{
//第一次采样累加的当前像素所在位置的颜色
//第二次采样累加的当前像素进行了moveDir * _BlurSize 偏移后的颜色
//第三次采样累加的当前像素进行了 2 * moveDir * _BlurSize 偏移后的颜色
color += tex2D(_MainTex,uv);
uv += moveDir * _BlurSize;
}
//计算叠加3次后颜色的平均值 相当于就是在进行了模糊处理
color /=3;
//返回模糊处理后的颜色
return fixed4(color.rgb,1);
}
ENDCG
}
}
Fallback Off
}

2.C#部分

1.实现思路

  1. 声明模糊偏移量变量和用于记录上一次变换矩阵的变量
  2. 重写OnRenderImage函数
    在其中进行属性设置,变换矩阵计算,屏幕后处理
  3. 在生命周期函数中启用深度纹理,初始化上一帧变换矩阵

2.实现示例

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
public class DepthMotionBlur : PostEffectBase
{
[Range(0f, 1f)]
public float blurSize = 0.5f;
//用于记录上一次的变换矩阵的变量
private Matrix4x4 _frontWorldToClipMatrix;

private void Start()
{
//开启深度纹理
Camera.main.depthTextureMode = DepthTextureMode.Depth;
//初始化上一次的变换矩阵
//用观察到裁剪变换矩阵 * 世界到观察变换矩阵
//得到的就是世界空间到裁剪空间的变量矩阵
_frontWorldToClipMatrix = Camera.main.projectionMatrix * Camera.main.worldToCameraMatrix;
}
private void OnEnable()
{
//有时我们会在界面上让脚本失活,每次激活时 可以初始化一次
//初始化上一次的变换矩阵
//用观察到裁剪变换矩阵 * 世界到观察变换矩阵
//得到的就是世界空间到裁剪空间的变量矩阵
_frontWorldToClipMatrix = Camera.main.projectionMatrix * Camera.main.worldToCameraMatrix;
}
protected override void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if (material != null)
{
//属性设置
material.SetFloat("_BlurSize",blurSize);
//设置上一帧世界空间到裁剪空间的矩阵
material.SetMatrix("_FrontWorldToClipMatrix",_frontWorldToClipMatrix);
//计算这一帧的变换矩阵
_frontWorldToClipMatrix = Camera.main.projectionMatrix * Camera.main.worldToCameraMatrix;
//设置这一帧的 裁剪到世界空间的变换矩阵(上面的注意转换)
material.SetMatrix("_ClipToWorldMatrix",_frontWorldToClipMatrix.inverse);
//进行屏幕后处理
Graphics.Blit(source, destination, material);
}
else
Graphics.Blit(source, destination);

}
}

五、注意点

1. 需要考虑不同平台可能存在垂直翻转问题

1
2
3
4
#if UNITY_UV_STARTS_AT_TOP
if(_MainTex_TexelSize<0)
o.uv_depth.y = 1 - o.uv_depth.y;
#endif

2.让移动方向向量/2

主要是用于降低运动模糊的效果

评论