1.原理
這裏運動模糊特效值得是攝像機在運動的時候,場景會產生模糊的效應,而攝像機不動物體運動這種情況則不會產生。
原理很容易,我們有很多方法實現,比如說直接獲取相鄰幀的圖像,然後和這一幀圖像進行混合,在這裏我們採取另外一種方式,我們獲取每個像素的運動速度,然後根據速度來偏移uv來獲得多個採樣,再把多個採樣的結果進行混合即可。
那麼問題來了,要怎麼才能獲得速度呢,想要獲取速度,我們可以通過相鄰兩幀的位置差來獲取,我們可以在Camera的屬性中獲得投影矩陣和視角矩陣,把它們相乘就能獲得投影視角矩陣,這個矩陣可以直接把座標從世界空間變換到裁剪空間,結果除以其w分量就是NDC了,我們通過其逆矩陣可以將NDC轉換到世界空間座標。因此,我們獲得當前幀的世界投影矩陣的逆矩陣將物體座標變換到世界空間,因爲我們的運動模糊是基於相機的,我們可以假定物體位置不變,然後利用前一幀的世界投影矩陣可以獲得前一幀的世界座標,這樣我們便能得到其速度。
2.實現
如何傳遞當前和前一幀的世界投影矩陣?
void OnEnable() {
camera.depthTextureMode |= DepthTextureMode.Depth;
previousViewProjectionMatrix = camera.projectionMatrix * camera.worldToCameraMatrix;
}
void OnRenderImage (RenderTexture src, RenderTexture dest) {
if (material != null) {
material.SetFloat("_BlurSize", blurSize);
material.SetMatrix("_PreviousViewProjectionMatrix", previousViewProjectionMatrix);
Matrix4x4 currentViewProjectionMatrix = camera.projectionMatrix * camera.worldToCameraMatrix;
Matrix4x4 currentViewProjectionInverseMatrix = currentViewProjectionMatrix.inverse;
material.SetMatrix("_CurrentViewProjectionInverseMatrix", currentViewProjectionInverseMatrix);
previousViewProjectionMatrix = currentViewProjectionMatrix;
Graphics.Blit (src, dest, material);
} else {
Graphics.Blit(src, dest);
}
}
觀察上面的代碼,我們的前一幀的世界投影矩陣在OnEnable裏面被初始化,而OnRenderImage函數會每一幀進行調用,調用的時候賦值的previousViewProjectionMatrix是前一幀的,而後面currentViewProjectionMatrix則是當前幀的,也就是說,currentViewProjectionMatrix一定比previousViewProjectionMatrix早一幀。
接下來看我們的shader源碼:
fixed4 frag(v2f i) : SV_Target {
// Get the depth buffer value at this pixel.
float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);
// H is the viewport position at this pixel in the range -1 to 1.
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.
float4 worldPos = D / D.w;
// 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.
previousPos /= previousPos.w;
// Use this frame's position and last frame's 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 < 3; it++, uv += velocity * _BlurSize) {
float4 currentColor = tex2D(_MainTex, uv);
c += currentColor;
}
c /= 3;
return fixed4(c.rgb, 1.0);
}
首先我們獲得深度紋理的解碼結果d,但是d是在(0,1)之間的,unity NDC中的z是在(-1,1)之間,因此需要映射到H,然後通過世界投影矩陣的逆矩陣獲得當前幀的世界座標,因爲投影矩陣推導的時候座標全部乘上了-z,導致w項不再爲1,因此所有經過投影矩陣變換的結果最後都必須要除一個w,得到世界座標後,可以利用前一幀的世界投影矩陣來獲取前一幀的座標,這樣就可以獲取速度了,根據速度的方向獲取3個採樣結果,進行混合就能實現運動模糊了。