再談運動模糊【Unity Shader入門精要13.2】
阿新 • • 發佈:2018-11-21
使用速度對映圖:速度對映圖中儲存了每個畫素的速度,然後使用這個速度決定模糊的大小和方向
速度緩衝的生成有多種方法:
一種方法是把場景中所有物體的速度渲染到一張紋理中,但這種方法的缺點在於需要修改場景中所有的物體的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); } } }