方框模糊、高斯模糊和雙重模糊的實現(xiàn)原理和方法

方框模糊(Box Blur)

我們知道,位圖其實都是由無數(shù)個像素點組合而成的,每個像素點都帶有顏色值,方框模糊的基本原理就是:遍歷每一個像素點,對像素點進(jìn)行重新賦值,采樣方法通常是取臨近像素點顏色值之和的均值,因此方框模糊又被稱為均值模糊。

最簡單的一種采樣方法就是2*2采樣,取目標(biāo)像素點周圍四個角的像素值,然后求均值。

舉個最簡單的例子!把下面左圖理解為一張圖片,每個色塊代表一個像素點。


使用2*2采樣方法遍歷每一個像素點,那么最后方框模糊的效果就像右圖這樣。以左上角的像素塊為例,它的RGB色值為(75,75,194),因為是邊緣色塊,它只有右下角色值為RGB(153,75,194)一個色塊,那模糊后的色值計算方法(四舍五入取整數(shù)值)為:

R = 153 / 4 = 67

G = 75 / 4 = 18

B = 194 / 4 = 48

它模糊后的最終色值為RGB(67,18,48),也就是右側(cè)左上方的暗紫色色塊。

其他像素點的模糊算法也以此類推。

具體用Unity Shader 實現(xiàn)如下:

float _BlurOffset;

            half4 frag (v2f_img i) : SV_Target
            {
                half4 s = 0;
                half4 d =  _MainTex_TexelSize.xyxy * half4(-1,-1,1,1) * _BlurOffset;
                // 采樣右上角的值
                s += tex2D(_MainTex, i.uv + d.yz);
                // 采樣右下角的值
                s += tex2D(_MainTex, i.uv + d.xy);
                // 采樣左下角的值
                s += tex2D(_MainTex, i.uv + d.zy);
                // 采樣左上角的值
                s += tex2D(_MainTex, i.uv + d.zw);
                s *= 0.25;
                return s;
            }

2*2 采樣方法雖然計算簡單,但它的模糊效果并不理想,單次迭代模糊過渡并不順滑,很容易出現(xiàn)色塊,就像下面一樣。


要解決這個問題,需要經(jīng)過多次迭代才能達(dá)到比較好的效果。

既然22采樣方法不太理想,那么可以考慮采樣33的采樣方法,也就是采樣目標(biāo)像素周圍9個像素點的色值(包括自身)。采樣方法可以根據(jù)實際項目需要靈活調(diào)整,比如說44、55的采樣方法,這里就不再一一列舉了,只給出3*3采樣方法的簡單示例:

還是以左上方色塊為例,使用3*3采樣方法,他的鄰近像素色塊只有四塊,那么方框模糊后的色值計算方法(四舍五入取整數(shù)值)為:

R = (75+75+75+153)/ 9 = 42

G = (75+117+194+75)/9 = 51

B = (194+194+125+194)/9 = 79

它模糊后的最終色值為RGB(67,18,48),也就是右側(cè)左上方的灰紫色色塊。

其他像素點的模糊算法也以此類推。

具體用Unity Shader 實現(xiàn)如下:

half4 frag_BoxFilter_9Tap (v2f_img i) : SV_Target
            {
                half4 s = 0;
                half4 d =  _MainTex_TexelSize.xyxy * half4(-1,-1,1,1) * _BlurOffset;
                s = tex2D(_MainTex, i.uv);
                // 采樣右上角的值 -1 1
                s += tex2D(_MainTex, i.uv + d.yz);
                // 采樣右下角的值 -1 -1
                s += tex2D(_MainTex, i.uv + d.xy);
                // 采樣左下角的值 1 -1
                s += tex2D(_MainTex, i.uv + d.zy);
                // 采樣左上角的值 1 1
                s += tex2D(_MainTex, i.uv + d.zw);

                // 采樣上下左右四個值
                s+=  tex2D(_MainTex, i.uv + half2(0.0,d.w)); // 0,1
                s+=  tex2D(_MainTex, i.uv + half2(0.0,d.y)); // 0,-1
                s+=  tex2D(_MainTex, i.uv + half2(d.z,0.0)); // 1,0
                s+=  tex2D(_MainTex, i.uv + half2(d.x,0.0)); // -1,0
  
                s = s/9.0;
                return s;
            }

在其他參數(shù)相同的情況下,33采樣方法模糊后的效果,會比22采樣方法過渡更自然,更不容易產(chǎn)生色塊,顏色細(xì)節(jié)也會更加豐富。

高斯模糊(Gaussian Blur)

理解了方框模糊,再來解釋高斯模糊就更加容易了。

首先,高斯模糊是采用5*5的采樣方法,也就是需要采樣目標(biāo)像素點周圍25個像素點的色值(包括目標(biāo)像素)。

但區(qū)別于方框模糊,高斯模糊采樣的每個像素點需要再乘以一個權(quán)重值,越靠近目標(biāo)值,權(quán)重值越大。


如果計算每一個像素點高斯模糊后的值,工作量將會變得特別大。有一種方法能將計算變得非常簡單,那就是先對像素進(jìn)行水平采樣,然后再垂直拆樣。具體采樣方法如下:

使用unity shader 實現(xiàn)代碼如下:

// 高斯模糊 水平采樣
            half4 frag_HorizontalBlur (v2f_img i) : SV_Target
            {
                half2 uv1 = i.uv + _BlurOffset.xy * half2(1,0) * -2.0;
                half2 uv2 = i.uv + _BlurOffset.xy * half2(1,0) * -1.0;
                half2 uv3 = i.uv;
                half2 uv4 = i.uv + _BlurOffset.xy * half2(1,0) * 1.0;
                half2 uv5 = i.uv + _BlurOffset.xy * half2(1,0) * 2.0;

                half4 s = 0;
                s += tex2D(_MainTex, uv1) * 0.05;
                s += tex2D(_MainTex, uv2) * 0.25;
                s += tex2D(_MainTex, uv3) * 0.40;
                s += tex2D(_MainTex, uv4) * 0.25;
                s += tex2D(_MainTex, uv5) * 0.05;

                return s;
            }

            // 高斯模糊 垂直采樣
             half4 frag_VerticalBlur (v2f_img i) : SV_Target
            {
                half2 uv1 = i.uv + _BlurOffset.xy * half2(0,1) * -2.0;
                half2 uv2 = i.uv + _BlurOffset.xy * half2(0,1) * -1.0;
                half2 uv3 = i.uv;
                half2 uv4 = i.uv + _BlurOffset.xy * half2(0,1) * 1.0;
                half2 uv5 = i.uv + _BlurOffset.xy * half2(0,1) * 2.0;

                half4 s = 0;
                s += tex2D(_MainTex, uv1) * 0.05;
                s += tex2D(_MainTex, uv2) * 0.25;
                s += tex2D(_MainTex, uv3) * 0.40;
                s += tex2D(_MainTex, uv4) * 0.25;
                s += tex2D(_MainTex, uv5) * 0.05;

                return s;
            }

實現(xiàn)的效果如下:

相比之下,高斯模糊的過渡效果會比方框模糊更加平滑自然。

雙重模糊(Dual Blur)

嚴(yán)格來說,雙重模糊并不是一種模糊算法,而是一種模糊的優(yōu)化方法,它可以應(yīng)用在方框模糊、高斯模糊或者其他模糊算法之上。它的實現(xiàn)思路在于:在原有模糊方法的基礎(chǔ)上,采用降采樣、升采樣的方法進(jìn)行迭代,從而達(dá)到模糊的優(yōu)化。

那么什么叫降采樣?什么叫升采樣呢?

先看以下一段代碼:

private void OnRenderImage(RenderTexture source, RenderTexture destination){
        int width = source.width;
        int height = source.height;
        RenderTexture RT1 = RenderTexture.GetTemporary(width, height);
        RenderTexture RT2 = RenderTexture.GetTemporary(width, height);
                Graphics.Blit(RT1, RT2);
}

這是一段最簡單采樣渲染圖片的代碼,新建了兩塊畫布,畫布的尺寸為屏幕的寬、高。

在采樣的過程中,如果畫布的寬高越來越小,采樣的速度越快,畫質(zhì)會變模糊,渲染速度變快,這個過程就叫做降采樣。

與此相反,畫布的寬高越來越大,采樣的速度變慢,但畫質(zhì)渲染會更加精細(xì),渲染速度變慢,這樣過程就叫做升采樣。

雙重模糊就是通過不斷迭代,先降采樣然后再升采樣的方法,來達(dá)到優(yōu)化模糊效果的。

以高斯模糊為例,它使用雙重模糊的代碼如下:

Graphics.Blit(source, RT1);
        // 設(shè)置綁定的材質(zhì)參數(shù)
        material.SetVector("_BlurOffset", new Vector4(_BlurRadius / width, _BlurRadius / height,0,0));
        // 降采樣
        for (int i = 0; i < _Iteration; i++) {
            RenderTexture.ReleaseTemporary(RT2);
            width = width / 2;
            height = height / 2;
            RT2= RenderTexture.GetTemporary(width, height);
            Graphics.Blit(RT1, RT2, material, 0);

            RenderTexture.ReleaseTemporary(RT1);
            width = width / 2;
            height = height / 2;
            RT1 = RenderTexture.GetTemporary(width, height);
            Graphics.Blit(RT2, RT1, material, 0);
        }
        // 升采樣
        for (int i = 0; i < _Iteration; i++)
        {
            RenderTexture.ReleaseTemporary(RT2);
            width = width * 2;
            height = height * 2;
            RT2 = RenderTexture.GetTemporary(width, height);
            Graphics.Blit(RT1, RT2, material, 0);

            RenderTexture.ReleaseTemporary(RT1);
            width = width * 2;
            height = height * 2;
            RT1 = RenderTexture.GetTemporary(width, height);
            Graphics.Blit(RT2, RT1, material, 0);
        }

        Graphics.Blit(RT2, destination);

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容