Unity Shader 屏幕后效果——摄像机运动模糊(速度映射图实现)


速度映射图主要是为了得到每个像素相对于前一帧的运动矢量,其中一种方法是使用摄像机的深度纹理来推导。

推导过程如下:

先由深度纹理逆推出NDC(归一化的设备坐标)下的顶点坐标,利用VP矩阵(视角*投影矩阵)的逆矩阵反向变换出每个像素在世界空间中的位置,

再利用世界空间下的坐标与前一帧的VP矩阵顺向变换出前一帧的NDC坐标,利用NDC下前一帧和相当帧的坐标差来确定速度的方向,

最后利用速度的方向对纹理采样的结果进行加权平均并多次绘制,以达到带有物体运动方向的模糊效果。

基于这一原理,需要准备的要素有:

1.摄像机的深度纹理(是由NDC下的坐标映射来的,需要先反向映射回NDC)

2.当前帧的VP矩阵的逆矩阵

3.前一帧的VP矩阵

摄像机深度值和深度纹理的获取方法在之前的博客中有写,具体可以参考:

https://www.cnblogs.com/koshio0219/p/11178215.html

视角矩阵(V矩阵):

MyCamera.worldToCameraMatrix;(也就是世界空间变换到摄像机空间(也叫视角空间,观察空间))

投影矩阵(P矩阵):

MyCamera.projectionMatrix;(也就是摄像机空间变换到裁剪空间)

具体的数学推导过程可以见这篇文章:

http://feepingcreature.github.io/math.html

下面是具体的程序实现:

1.此脚本挂载在摄像机上,用于模糊参数调控,深度纹理准备,矩阵传递:

 1 using UnityEngine;
 2 
 3 public class MotionBlurWithDepthTexCtrl : ScreenEffectBase
 4 {
 5     private const string _BlurSize = "_BlurSize";
 6     private const string _PreViewMatrix = "_PreViewMatrix";
 7     private const string _CurViewInserseMatrix = "_CurViewInserseMatrix";
 8 
 9     [Range(0.0f, 1.0f)]
10     public float blurSize = .5f;
11 
12     //前一帧的VP矩阵
13     private Matrix4x4 preViewMatrix;
14 
15     private Camera myCamera;
16     public Camera MyCamera
17     {
18         get
19         {
20             if(null == myCamera)
21             {
22                 myCamera = GetComponent<Camera>();
23             }
24             return myCamera;
25         }
26     }
27 
28     //开启这相机深度模式,并初始化前一帧的VP矩阵
29     private void OnEnable()
30     {
31         MyCamera.depthTextureMode |= DepthTextureMode.Depth;
32 
33         //右乘原则,前边是P矩阵,后边是V矩阵
34         preViewMatrix = MyCamera.projectionMatrix * MyCamera.worldToCameraMatrix;
35     }
36 
37     //不用时恢复
38     private void OnDisable()
39     {
40         MyCamera.depthTextureMode &= ~DepthTextureMode.Depth;
41     }
42 
43     private void OnRenderImage(RenderTexture source, RenderTexture destination)
44     {
45         if (Material)
46         {
47             Material.SetFloat(_BlurSize, blurSize);
48             //设置前一帧的VP矩阵
49             Material.SetMatrix(_PreViewMatrix, preViewMatrix);
50             //计算当前帧VP矩阵,右乘
51             Matrix4x4 curViewMatrix = MyCamera.projectionMatrix * MyCamera.worldToCameraMatrix;
52             //存起来,作为下次计算的前一帧
53             preViewMatrix = curViewMatrix;
54             //设置当前帧的VP矩阵的逆矩阵
55             Material.SetMatrix(_CurViewInserseMatrix, curViewMatrix.inverse);
56 
57             Graphics.Blit(source, destination, Material);
58         }
59         else
60             Graphics.Blit(source, destination);
61 
62     }
63 }

基类脚本见:

https://www.cnblogs.com/koshio0219/p/11131619.html

2.Shader脚本:

 1 Shader "MyUnlit/MotionBlurWithDepthTex"
 2 {
 3     Properties
 4     {
 5         _MainTex ("Texture", 2D) = "white" {}
 6     }
 7     SubShader
 8     {
 9         Pass
10         {
11             ZTest Always Cull Off ZWrite Off
12 
13             CGPROGRAM
14             #pragma vertex vert
15             #pragma fragment frag
16 
17             #include "UnityCG.cginc"
18 
19             sampler2D _MainTex;
20             half4 _MainTex_TexelSize;
21             fixed _BlurSize;
22             //声明深度纹理和对应矩阵
23             sampler2D _CameraDepthTexture;
24             float4x4 _PreViewMatrix;
25             float4x4 _CurViewInserseMatrix;
26 
27             struct appdata
28             {
29                 float4 vertex : POSITION;
30                 float2 uv : TEXCOORD0;
31             };
32 
33             struct v2f
34             {
35                 //这里的的uv同时存了主纹理的uv和深度纹理uv,xy为主纹理,zw为深度纹理
36                 half4 uv : TEXCOORD0;
37                 float4 vertex : SV_POSITION;
38             };
39 
40             v2f vert (appdata v)
41             {
42                 v2f o;
43                 o.vertex = UnityObjectToClipPos(v.vertex);
44                 o.uv.xy =v.uv;
45                 o.uv.zw=v.uv;
46 
47                 //主纹理外的纹理要进行平台差异化处理
48                 #if UNITY_UV_STARTS_AT_TOP
49                 if(_MainTex_TexelSize.y<0)
50                     o.uv.w=1-o.uv.w;        
51                 #endif
52 
53                 return o;
54             }
55 
56             fixed4 frag (v2f i) : SV_Target
57             {
58                 //用深度纹理和zw取得深度值
59                 float d=SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv.zw);
60                 //反映射回NDC坐标,由[0,1]到[-1,1]的映射,z分量就是深度值本身
61                 float4 H=float4(i.uv.x*2-1,i.uv.y*2-1,d*2-1,1);
62                 //用VP的逆矩阵反向变换并除以w分量得到世界坐标位置,为什么除以w详细见前面数学推导的文章链接
63                 float4 D=mul(_CurViewInserseMatrix,H);
64                 float4 worldPos=D/D.w;
65 
66                 //分别得到前一帧和当前帧的NDC坐标取差值计算速度方向
67                 float4 curViewPos=H;
68                 float4 preViewPos=mul(_PreViewMatrix,worldPos);
69                 preViewPos/=preViewPos.w;
70 
71                 //除以的系数可以根据自己的需求调整
72                 float2 velocity=(curViewPos.xy-preViewPos.xy)/10.0f;
73 
74                 float2 uv=i.uv.xy;
75                 //纹理采样的速度权重,这里进行了前2帧的计算,包括当前帧总共3个值,值依次递减且保证和为1,不为1则需要进行额外的除法
76                 //目的是为了让越之前的帧看上去效果越淡,轨迹逐渐消失
77                 float velColRate[3]={0.6,0.3,0.1};
78                 //当前采样结果用权重最大的值0.6
79                 fixed4 col = tex2D(_MainTex, uv)*velColRate[0];
80                 uv+=velocity*_BlurSize;
81 
82                 for(int it=1;it<3;it++)
83                 {
84                     //前两帧采样结果依次递减,0.3,0.1
85                     fixed4 curCol=tex2D(_MainTex,uv.xy)*velColRate[it];
86                     //将所有结果加起来,保证权重为1
87                     col+=curCol;
88                     //按速度方便对纹理进行偏移,并用模糊系数加以控制
89                     uv+=velocity*_BlurSize;
90                 }
91 
92                 return fixed4(col.rgb,1.0);
93             }
94             ENDCG
95         }
96     }
97     FallBack Off
98 }

效果如下:

优质内容筛选与推荐>>
1、Python发送带附件的Email
2、初识Kafka
3、WM6 Rapi 开发(二) Hello World
4、申请elasticsearch中x-pack插件许可证
5、Springboot2.x 自动创建表并且执行初始化数据


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn