花了一晚上的時間終於看懂Image Effect中的Blur,其實很簡單,就是一下子沒有理解到。
原理:使用兩個一維[1*7]的高斯濾波模板,一個用在x方向,另一個用在y方向。高斯濾波有模糊的效果。
js腳本參數:
Down Sample:OnRenderImage中獲取的圖像進行降采樣,其實就是把要處理的紋理變小。有利於加快shader運行速度。
Blur Size:在使用高斯模板時,相鄰像素點的間隔。越大間隔越遠,圖像越模糊。但過大的值會導致失真。
Blur Iterations:迭代次數,越大模糊效果越好,但消耗越大。
Blur Type:兩個不同的shader,后一個是前一個的優化版本,但差別不大。
具體代碼分析:
function OnRenderImage (source : RenderTexture, destination : RenderTexture) { if(CheckResources() == false) { Graphics.Blit (source, destination); return; } var widthMod : float = 1.0f / (1.0f * (1<<downsample)); // 降采樣系數的倒數,用於調整降采樣后,相鄰像素的間隔 // blurMaterial.SetVector ("_Parameter", Vector4 (blurSize * widthMod, -blurSize * widthMod, 0.0f, 0.0f)); source.filterMode = FilterMode.Bilinear; var rtW : int = source.width >> downsample; // >> 是除法的優化 var rtH : int = source.height >> downsample; // downsample var rt : RenderTexture = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt.filterMode = FilterMode.Bilinear;
// 對應的shader的Pass 0 Graphics.Blit (source, rt, blurMaterial, 0); //首先對圖像進行降采樣,同時進行簡單的模糊 var passOffs = blurType == BlurType.StandardGauss ? 0 : 2; // 選擇不同的blurtype,就調用不同的shader pass for(var i : int = 0; i < blurIterations; i++) { var iterationOffs : float = (i*1.0f);
// _Parameter.x 記錄的是 相鄰像素的間隔,隨着迭代次數增大 blurMaterial.SetVector ("_Parameter", Vector4 (blurSize * widthMod + iterationOffs, -blurSize * widthMod - iterationOffs, 0.0f, 0.0f)); // vertical blur 垂直濾波 var rt2 : RenderTexture = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt2.filterMode = FilterMode.Bilinear; Graphics.Blit (rt, rt2, blurMaterial, 1 + passOffs); // 對應着shader的pass 1,2 RenderTexture.ReleaseTemporary (rt); rt = rt2; // horizontal blur 水平濾波 rt2 = RenderTexture.GetTemporary (rtW, rtH, 0, source.format); rt2.filterMode = FilterMode.Bilinear; Graphics.Blit (rt, rt2, blurMaterial, 2 + passOffs); // 對應着shader的pass 3,4 RenderTexture.ReleaseTemporary (rt); rt = rt2; } Graphics.Blit (rt, destination); RenderTexture.ReleaseTemporary (rt); }
接着分析shader文件:
先看5個pass,分別是用在上文cs腳本中的Bilt函數中。
SubShader { ZTest Off Cull Off ZWrite Off Blend Off Fog { Mode off } // 0 Pass { CGPROGRAM #pragma vertex vert4Tap #pragma fragment fragDownsample #pragma fragmentoption ARB_precision_hint_fastest ENDCG } // 1 Pass { ZTest Always Cull Off CGPROGRAM #pragma vertex vertBlurVertical #pragma fragment fragBlur8 #pragma fragmentoption ARB_precision_hint_fastest ENDCG } // 2 Pass { ZTest Always Cull Off CGPROGRAM #pragma vertex vertBlurHorizontal #pragma fragment fragBlur8 #pragma fragmentoption ARB_precision_hint_fastest ENDCG } // alternate blur // 3 Pass { ZTest Always Cull Off CGPROGRAM #pragma vertex vertBlurVerticalSGX #pragma fragment fragBlurSGX #pragma fragmentoption ARB_precision_hint_fastest ENDCG } // 4 Pass { ZTest Always Cull Off CGPROGRAM #pragma vertex vertBlurHorizontalSGX #pragma fragment fragBlurSGX #pragma fragmentoption ARB_precision_hint_fastest ENDCG } }
pass 0:在降采樣的同時,進行簡單地模糊處理。
v2f_tap vert4Tap ( appdata_img v ) { v2f_tap o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
// 取像素周圍的點 o.uv20 = v.texcoord + _MainTex_TexelSize.xy; o.uv21 = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5h,-0.5h); o.uv22 = v.texcoord + _MainTex_TexelSize.xy * half2(0.5h,-0.5h); o.uv23 = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5h,0.5h); return o; } fixed4 fragDownsample ( v2f_tap i ) : SV_Target { fixed4 color = tex2D (_MainTex, i.uv20); color += tex2D (_MainTex, i.uv21); color += tex2D (_MainTex, i.uv22); color += tex2D (_MainTex, i.uv23); return color / 4; }
接下來的pass 1,2 和pass 3, 4,都是分別在x y兩個方向進行高斯濾波。
先看看高斯濾波模板:
static const half4 curve4[7] = { half4(0.0205,0.0205,0.0205,0), half4(0.0855,0.0855,0.0855,0), half4(0.232,0.232,0.232,0), half4(0.324,0.324,0.324,1), half4(0.232,0.232,0.232,0), half4(0.0855,0.0855,0.0855,0), half4(0.0205,0.0205,0.0205,0) };
這是 [1*7]的模板,對中間點像素的左右兩邊各3個像素,總共7個像素進行加權求和,得到新的像素值。
pass 1,2的只有vert函數不一樣,分別是取水平和垂直方向的偏差值。
v2f_withBlurCoords8 vertBlurHorizontal (appdata_img v) { v2f_withBlurCoords8 o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = half4(v.texcoord.xy,1,1); o.offs = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _Parameter.x; // 水平方向的偏差值 return o; } v2f_withBlurCoords8 vertBlurVertical (appdata_img v) { v2f_withBlurCoords8 o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = half4(v.texcoord.xy,1,1); o.offs = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _Parameter.x; // 垂直方向的偏差值 return o; } half4 fragBlur8 ( v2f_withBlurCoords8 i ) : SV_Target { half2 uv = i.uv.xy; half2 netFilterWidth = i.offs; half2 coords = uv - netFilterWidth * 3.0; // 這里從中心點偏移3個間隔,從最左邊或者是最上邊開始進行加權累加 half4 color = 0; for( int l = 0; l < 7; l++ ) { half4 tap = tex2D(_MainTex, coords); color += tap * curve4[l]; // 像素值乘上對應的權值 coords += netFilterWidth; // 移到下一個像素 } return color; }
在pass 1,2中的uv值都是float2向量,然而寄存器可以一次性儲存float4,即可以一個float4值存儲兩個uv值。並且像素着色器函數中,計算相鄰像素的步驟,可以放在頂點着色器中。於是就有下面這個版本:
v2f_withBlurCoordsSGX vertBlurHorizontalSGX (appdata_img v) { v2f_withBlurCoordsSGX o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = v.texcoord.xy; half2 netFilterWidth = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _Parameter.x; half4 coords = -netFilterWidth.xyxy * 3.0; // 計算左右相鄰各3個像素的坐標 o.offs[0] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h); coords += netFilterWidth.xyxy; o.offs[1] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h); coords += netFilterWidth.xyxy; o.offs[2] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h); return o; } v2f_withBlurCoordsSGX vertBlurVerticalSGX (appdata_img v) { v2f_withBlurCoordsSGX o; o.pos = mul (UNITY_MATRIX_MVP, v.vertex); o.uv = half4(v.texcoord.xy,1,1); half2 netFilterWidth = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _Parameter.x; half4 coords = -netFilterWidth.xyxy * 3.0; // 計算上下相鄰各3個像素的坐標 o.offs[0] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h); coords += netFilterWidth.xyxy; o.offs[1] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h); coords += netFilterWidth.xyxy; o.offs[2] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h); return o; } half4 fragBlurSGX ( v2f_withBlurCoordsSGX i ) : SV_Target { half2 uv = i.uv.xy; half4 color = tex2D(_MainTex, i.uv) * curve4[3]; // 中間像素,乘上對應的權值 for( int l = 0; l < 3; l++ ) { half4 tapA = tex2D(_MainTex, i.offs[l].xy); half4 tapB = tex2D(_MainTex, i.offs[l].zw); color += (tapA + tapB) * curve4[l]; // 由於模板是對稱的,可以使用相同的權值 } return color; }
結論:
通過調試,發現使用downsampler為1,iteration為2時,調整blursize可以得到較好的效果,並且性能較好。但blursize為0時,還是模糊圖像,想做成那種從清晰到模糊的動畫,估計還要調整一下代碼。