使用速度映射图:速度映射图中存储了每个像素的速度,然后使用这个速度决定模糊的大小和方向
速度缓冲的生成有多种方法:
一种方法是把场景中所有物体的速度渲染到一张纹理中,但这种方法的缺点在于需要修改场景中所有的物体的Shader代码,使其添加计算速度的代码并输出到一个渲染纹理中
《GPU Gems3》第27章中介绍了一种生成速度映射图的方法,这种方法利用深度纹理在片元着色器中为每个像素计算其在世界空间下的位置,这是通过使用当前的视角*投影矩阵的逆矩阵对NDC下的顶点坐标进行变换得到的,当得到世界空间下的顶点坐标后,使用前一帧的视角*投影矩阵对其进行变换,得到该位置在前一帧的NDC坐标,然后计算前一帧和当前的位置差,生成该像素的速度。这种方法的优点是可以在一个屏幕后处理步骤完成整个效果的模拟,但缺点是需要在片元着色器中进行矩阵纹理操作,对其性能有影响
本节实现的运动迷糊使用于场景静止,摄像机快速运动的情况
这是因为我们在计算时只考虑了摄像机的运动,因此,如果把代码应用到一个物体快速运动而摄像机静止的场景,就会发现不会产生任何运动模糊效果,
如果想要对快速移动的物体产生运动模糊的效果,就需要生成更加精确的速度映射贴图(可以在Unity自带的ImageEffect包中找到更多的运动模糊的实现方法)
//13.2 再谈运动模糊
Shader "Unlit/Chapter13-MotionBlurWithDepthTexture"
{
Properties{
_MainTex("Base (RGB)", 2D) = "white" {}
_BlurSize("Blur Size", Float) = 1.0
}
SubShader{
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;//输入的渲染纹理,UNITY传递的
half4 _MainTex_TexelSize;
sampler2D _CameraDepthTexture;//相机的深度纹理,UNITY传递的
float4x4 _CurrentViewProjectionInverseMatrix;//当前视图投影逆矩阵,脚本传递的
float4x4 _PreviousViewProjectionMatrix;//前视图投影矩阵,脚本传递的
half _BlurSize;//模糊图像时使用的参数
struct v2f {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
half2 uv_depth : TEXCOORD1;//对深度采样的空间
};
v2f vert(appdata_img v ){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
o.uv_depth = v.texcoord;//对深度采样的纹理坐标变量
//由于需要同时处理多张渲染纹理,因此在DirectX这样的平台上需要处理平台差异导致的图像翻转问题
#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{
//Get the depth buffer value at this pixel 获取该像素处的深度缓冲值
//过程开始于对深度纹理的采样,使用内置的SAMPLE_DEPTH_TEXTURE宏和纹理坐标对深度纹理进行采样,得到深度值d,d是有NDC下的坐标映射而来的
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth);
//H is the viewport Position at this pixel in the range -1 to 1 H是这个像素在-1到1之间的视口位置
//构造像素的NDC坐标H,就需要把这个深度值重新映射回NDC,(只需要使用原映射的反函数)
float4 H = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, d * 2 - 1, 1);
//Transform by the view-projection inverse 通过视图投影反变换
//使用当前帧的视角*投影矩阵的逆矩阵对其进行变换
float4 D = mul(_CurrentViewProjectionInverseMatrix, H);
//Divide by w to get the world position 除以w得到世界位置
float4 worldPos = D / D.w;
/*一旦得到了世界空间下的坐标,就可以使用前一帧的视角*投影矩阵对它进行变换,得到前一帧在NDC下的坐标PreviousPOS
然后计算前一帧和当前帧在屏幕空间下的坐标差,得到改像素的速度velocity*/
//Current viewport position 当前窗口的位置
float4 currentPos = H;
//Use the world position,and transform by the previous view-projection matrix 利用世界位置,利用之前的视图投影矩阵进行变换
float4 previousPos = mul(_PreviousViewProjectionMatrix, worldPos);
//Convert to nonhomogeneous points [-1,1]by dividing by w 通过除以w转换成非齐次点[-1,1]
previousPos /= previousPos.w;
/*当得到该像素的速度后就可以使用该速度值对它的邻域像素进行采样,相加后取平均值得到一个模糊的效果
采样时用_BlurSize来控制采样距离*/
//Use this frame is position and last frame is to compute the pixel velocity 使用这个帧是位置,最后一帧是计算像素速度
float2 velocity = (currentPos.xy - previousPos.xy) / 2.0f;
float2 uv = i.uv;
float4 c = tex2D(_MainTex, uv);
uv += velocity * _BlurSize;
for (int it = 1; it < 8; it++, uv += velocity * _BlurSize) {
float4 currentColor = tex2D(_MainTex, uv);
c += currentColor;
}
c /= 8;
return fixed4(c.rgb, 1.0);
}
ENDCG
Tags { "RenderType"="Opaque" }
//定义迷糊使用的Pass
Pass
{
ZTest Always Cull Off ZWrite off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
Fallback Off
}
//13.2 再谈运动模糊
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MotionBlurWithDepthTexture : PostEffectsBase {
public Shader motionBlurShader;
private Material motionBlurMaterial = null;
public Material material
{
get
{
motionBlurMaterial = CheckShaderAndCreateMaterial(motionBlurShader, motionBlurMaterial);
return motionBlurMaterial;
}
}
//定义运动模糊时迷糊图像使用的大小
[Range(0.0f, 1.0f)]
public float blurSize = 0.5f;
//由于需要使用到摄像机的视角和投影矩阵,需要定义一个Camera类型的变量,获取该脚本所在的摄像机组件
private Camera myCamera;
public Camera camera
{
get
{
if (myCamera == null)
{
myCamera = GetComponent<Camera>();
}
return myCamera;
}
}
//定义一个变量来保存上一帧摄像的视角*投影矩阵
private Matrix4x4 previousViewProjectionMatrix;
//由于要获取摄像机的深度纹理,在脚本OnEnable函数中设置摄像机的状态
private void OnEnable()
{
//设置摄像机的depthTexture,产生深度和法线纹理
camera.depthTextureMode |= DepthTextureMode.Depth;
//把 给变量PVPM来保存
// previousViewProjectionMatrix = camera.projectionMatrix * camera.worldToCameraMatrix;
}
//实现OnRenderImage函数
void OnRenderImage(RenderTexture src, RenderTexture dest)
{
if (material != null)
{
//传递运动模糊使用的各个属性
material.SetFloat("_BlurSize", blurSize);
/*需要使用两个变换矩阵
-1-前一帧的视角*投影矩阵
-2-当前帧的视角*投影矩阵的逆矩阵
*/
material.SetMatrix("_PreviousViewProjectionMatrix", previousViewProjectionMatrix);
// 通过调用camera.worldToCameraMatrix和camera.projectionMatrix来分别得到当前摄像机的视角矩阵和投影矩阵
Matrix4x4 currentViewProjectionMatrix = camera.projectionMatrix * camera.worldToCameraMatrix; //当前视图投影矩阵=投影矩阵*视角矩阵
Matrix4x4 currentViewProjecionInverseMatrix = currentViewProjectionMatrix.inverse; //当前视图投影逆矩阵=当前视图投影矩阵.inverse
material.SetMatrix("_CurrentViewProjectionInverseMatrix", currentViewProjecionInverseMatrix);//将逆矩阵传递给材质
previousViewProjectionMatrix = currentViewProjectionMatrix;//储存取逆前的视图投影矩阵
Graphics.Blit(src, dest, material);
}
else
{
Graphics.Blit(src, dest);
}
}
}