利用深度纹理实现运动模糊
利用 深度纹理实现运动模糊效果 的这种方式有以下两点需要注意:
- 这种实现方式 只适合 场景静止,即摄像机快速移动的情况,
它不太适用于物体快速移动产生的运动模糊效果,只有摄像机移动时才能看到运动模糊效果
- 这种实现方式 并不是基于真实的物理运动规律来计算的,只是一种近似计算!
它符合图形学基本规则:看起来对那么就是对的
虽然它使用上有局限性,并且实现上也不符合物理计算规则,
但是由于它实现出来的效果和性能消耗都还不错,因此它也是一种常用的运动模糊处理方案。
1、基本原理
得到像素当前帧和上一帧中在裁剪空间下的位置,利用两个位置计算出物体的运动方向,从而模拟出运动模糊的效果。
其中的关键点:
- 如何得到像素当前帧和上一帧在裁剪空间下的位置
- 如何得到运动方向
- 如何模拟运动模糊效果
如何得到像素当前帧和上一帧在裁剪空间下的位置
关键步骤:
- 利用UV坐标和深度值组合成一个裁剪空间下的组合坐标 nowClipPos
- 利用 这一帧 世界空间—>裁剪空间 的变换矩阵 nowM 的逆矩阵,将刚才裁剪空间下的组合坐标nowClipPos转换到世界空间中
- 利用 上一帧的 世界空间—>裁剪空间 的变换矩阵oldM得到,上一帧该世界空间下的组合坐标oldClipPos在裁剪空间下的位置
步骤一:
利用UV坐标和深度值组合成一个裁剪空间下的组合坐标 nowClipPos
- float4 clipPos = float4(uv.x, uv.y, depth, 1);
uv坐标空间下的值是0~1,通过宏取出来的深度值也是0~1,而裁剪空间下的坐标范围是-1~1
因此,我们需要将它利用简单的公式转换到裁剪空间坐标系下
- float4 clipPos = float4(uv.x * 2 - 1, uv.y * 2 - 1, depth * 2 - 1, 1);
可以认为这一步是把带有深度值的uv坐标转换到了裁剪空间下,之所以加入深度值是为了让之后的裁剪空间转到世界空间或其他坐标空间更合理,相当于是把UV的2D坐标系转换到了3D坐标系中
步骤二:
利用 世界空间—>裁剪空间 的变换矩阵的逆矩阵,将刚才裁剪空间下的nowClipPos转换到世界坐标中
只需要利用上面的这个裁剪空间到世界空间的变换矩阵,就可以将刚才得到的裁剪空间下的组合坐标变换到世界空间下了
步骤三:
利用上一帧的世界坐标—>裁剪空间的变换矩阵得到上一帧该世界空间下的组合坐标在裁剪空间下位置
如何得到运动方向
得到像素当前帧和上一帧在裁剪空间下的位置后,直接用 当前位置.xy – 上一帧位置.xy 便可以得到移动方向
如何模拟运动模糊效果
有了像素在裁剪空间的移动方向,相当于知道了像素在“uv纹理空间的移动方向”吗,那么只需要利用这个方向在纹理中进行多次uv坐标偏移采样后,将得到的颜色累加起来,最后进行算数平均值计算即可,会加入一个 模糊偏移量 来控制模糊程度,只需要在每次采样时进行 方向 * 模糊偏移量 的偏移采样即可
2、实现
Shader "ShaderProj/13/MotionBlurWithDepthTexture"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
// 用于控制模糊程度的模糊偏移量
_BlurSize ("BlurSize", Float) = 0.5
}
SubShader
{
Pass
{
ZTest Always
ZWrite Off
Cull 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_ST;
float4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;
float4x4 _ClipToWorldMatrix; // 裁剪空间到世界空间的变换矩阵
float4x4 _FrontWorldToClipMatrix; // 上一帧世界空间到裁剪空间的变换矩阵
float _BlurSize;
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.vertex;
o.uv_depth = v.vertex;
#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
{
// 得到裁剪空间下的两个点
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);
float4 nowClipPos = float4(i.uv * 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) / 2;
// 进行模糊处理
float2 uv = i.uv;
float4 color = float4(0, 0, 0, 0);
for (int it = 0; it < 3; it++)
{
color += tex2D(_MainTex, uv);
uv += moveDir * _BlurSize;
}
color /= 3;
return fixed4(color.rgb, 1);
}
ENDCG
}
}
Fallback Off
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MotionblurWithDepthTexture : PostEffectBase
{
[Range(0, 1)]
public float blurSize = .5f;
private Matrix4x4 frontWorldToClipMatrix;
// Start is called before the first frame update
void Start()
{
Camera.main.depthTextureMode = DepthTextureMode.Depth;
}
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);
}
}