速度映射圖主要是為了得到每個像素相對於前一幀的運動矢量,其中一種方法是使用攝像機的深度紋理來推導。
推導過程如下:
先由深度紋理逆推出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 }
效果如下: