unity shader:屏幕后處理技術


屏幕后處理定義:就是渲染完整個場景得到屏幕圖像后,再對這個圖像進行一系列操作,實現各種屏幕特效。

屏幕后處理實現過程:首先需要在攝像機上添加一個用於屏幕后處理的腳本。在這個腳本中,我們會實現MonoBehaviour.OnRenderImage函數來獲取當前屏幕的渲染紋理。然后再調用Graphics.Blit函數使用特定的Unity Shader對當前的圖像進行處理,再把返回的渲染紋理顯示到屏幕上。

明亮度&飽和度&對比度屏幕特效:就是攝像機掛載的屏幕后處理腳本中通過控制明亮度,飽和度,對比度系數,傳遞給材質中的Shader進行處理,Shader代碼如下:

Shader "Custom/BrightnessSaturationAndContast" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }

    SubShader {
        Pass {
            // 開啟深度測試,關閉深度寫入和圖元裁剪
            ZTest Always ZWrite Off Cull Off

            CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #include "UnityCG.cginc"

                // 定義材質傳遞的參數屬性
                sampler2D _MainTex; // 渲染完場景后得到的屏幕圖像,必須使用該屬性名,由Graphics.Blit函數內部傳遞
                half _Brightness;   // 明亮度
                half _Saturation;   // 飽和度
                half _Contrast; // 對比度

                struct v2f {
                    float4 pos : SV_POSITION;
                    half2 uv : TEXCOORD0; 
                };

                v2f vert(appdata_img i) {
                    v2f o;
                    o.pos = mul(UNITY_MATRIX_MVP, i.vertex);
                    o.uv = i.texcoord;

                    return o;
                }

                fixed4 frag(v2f i) : SV_Target {
                    // 紋理采樣
                    fixed4 renderTex = tex2D(_MainTex, i.uv);

                    // 獲取明亮度屏幕特效:直接和明亮度參數相乘
                    fixed3 finalcolor = renderTex.rgb * _Brightness;

                    // 獲取飽和度屏幕特效:先獲取飽和度為0的顏色,然后和飽和度參數進行混合求插值
                    fixed luminance = 0.2125 * renderTex.r + 0.7154 * renderTex.g + 0.0721 * renderTex.b;
                    fixed3 luminanceColor = fixed3(luminance, luminance, luminance);
                    finalcolor = lerp(luminanceColor, finalcolor, _Saturation);

                    // 獲取對比度屏幕特效:先獲取對比度為0的顏色,然后和對比度參數進行混合求插值
                    fixed3 avgColor = fixed3(0.5, 0.5, 0.5);
                    finalcolor = lerp(avgColor, finalcolor, _Contrast);

                    return fixed4(finalcolor, renderTex.a);
                }
            ENDCG  
        }
    }

    FallBack Off
}

卷積核:通常是一個四方形的網格結構,並且每一個方格具有一定的權重值。

卷積操作:就是將圖像中每一個像素對應卷積核的中心點,然后計算卷積核與圖像覆蓋像素的乘積並累加起來得到當前處理像素的新像素值。

邊緣檢測屏幕特效:選擇合適的卷積核來對圖像中的所有像素進行卷積操作,參考代碼如下:

Shader "Custom/EdgeDetection" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}    // Graphics.Blit傳遞過來的源屏幕圖像
    }

    SubShader { 
        Pass {
            ZTest Always Cull Off ZWrite Off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            // 定義材質傳遞過來的屬性變量
            sampler2D _MainTex;
            half4 _MainTex_TexelSize;   // 紋素大小
            fixed _EdgeOnly;    // 邊緣線強度,當為1時表示只顯示邊緣線,不顯示原圖像
            fixed4 _EdgeColor;  // 邊緣顏色
            fixed4 _BackgroundColor;    // 背景顏色

            struct v2f {
                float4 pos : SV_POSITION;
                half2 uv[9] : TEXCOORD0;    // 包含當前像素以及鄰近素在內的9個像素在紋理中的采樣坐標
            };

            v2f vert(appdata_img i) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, i.vertex);

                half2 uv = i.texcoord;
                o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1);   // 對應左上像素
                o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1);    // 對應正上像素
                o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1);    // 對應右上像素
                o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0);    // 對應左邊像素
                o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0); // 對應當前像素
                o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0); // 對應右邊像素
                o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1);    // 對應左下像素
                o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1); // 對應正下像素
                o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1); // 對應右下像素

                return o;
            }

            // 設置指定的像素為0飽和度
            fixed luminance(fixed4 color) {
                return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
            }

            // 卷積操作函數
            half Sobel(v2f i) {
                // 定義Sobel卷積核
                const half Gx[9] = {-1,  0,  1,
                                    -2,  0,  2,
                                    -1,  0,  1};

                const half Gy[9] = {-1, -2, -1,
                                    0,  0,  0,
                                    1,  2,  1};

                half texColor;
                half edgeX = 0;
                half edgeY = 0;
                for (int it = 0; it < 9; it++) {
                    // 獲取像素采樣顏色值
                    texColor = luminance(tex2D(_MainTex, i.uv[it]));

                    // 獲取像素水平方向卷積操作,並和之前像素水平方向卷積操作結果相加
                    edgeX += texColor * Gx[it];

                    // 獲取像素垂直方向卷積操作,並和之前像素垂直方向卷積操作結果相加
                    edgeY += texColor * Gy[it];
                }

                // 獲取邊緣大小,值越小,說明卷積操作部分值越大,對應的就是邊緣
                half edge = 1 - abs(edgeX) - abs(edgeY);
                return edge;
            }

            fixed4 frag(v2f i) : SV_Target {
                // 使用Sobel卷積核進行卷積操作
                half edge = Sobel(i);

                // 獲取當前像素采樣后的顏色與卷積操作后的結果進行插值疊加
                fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);

                // 獲取背景顏色與卷積操作后的結果進行插值疊加
                fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);

                // 將背景顏色和像素顏色,以及邊緣強度進行插值疊加
                return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);   
            }
            ENDCG
        }
    }

    FallBack Off
}

高斯核定義:高斯核是一個正方形的濾波核,核中元素可以使用高斯方程來獲取。其中高斯方程的定義如下:
這里寫圖片描述
所以臨近像素與當前處理像素越近,影響程度越大。

高斯模糊屏幕特效:就是使用高斯核來對圖像的每一個像素進行卷積操作。5x5維度的高斯模糊參考代碼如下:

Shader "Custom/GaussianBlur" {
    Properties {
        _MainTex("Base (RGB)", 2D) = "white" {} // 場景渲染完畢后得到的屏幕對象
    }

    SubShader {
        // 定義頭文件,該文件的內容可以在包含該文件的地方進行使用
        CGINCLUDE
            #include "UnityCG.cginc"

            // 定義材質傳遞的屬性變量
            sampler2D _MainTex;
            half4 _MainTex_TexelSize;   // 當前紋素大小
            float _BlurSize;    // 模糊范圍大小

            struct v2f {
                float4 pos : SV_POSITION;
                half2 uv[5] : TEXCOORD0;    // 當前像素和臨近四個像素的紋理采樣坐標 
            };

            // 頂點着色器中使用高斯核垂直分量進行計算采樣操作
            v2f vertBlurVertical(appdata_img i) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, i.vertex);

                half2 uv = i.texcoord;
                o.uv[0] = uv;   // 當前像素采樣坐標
                o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize; // 當前像素向上一個像素采樣坐標
                o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize; // 當前像素向下一個像素采樣坐標
                o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize; // 當前像素向上兩個像素采樣坐標
                o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize; // 當前像素向下兩個像素采樣坐標

                return o;
            }

            // 頂點着色器使用高斯核水平分量進行計算采樣操作
            v2f vertBlurHorizontal(appdata_img i) {
                v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, i.vertex);

                half2 uv = i.texcoord;
                o.uv[0] = uv;
                o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize; // 當前像素向右一個像素采樣坐標
                o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize; // 當前像素向左一個像素采樣坐標
                o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize; // 當前像素向右兩個像素采樣坐標
                o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize; // 當前像素向左兩個像素采樣坐標

                return o;
            }

            // 片元着色器使用高斯核的水平和垂直分量進行卷積操作
            fixed4 fragBlur(v2f i) :SV_Target {
                // 獲取高斯核水平和垂直方向的權重值
                float weight[3] = {0.4026, 0.2442, 0.0545};

                // 當前像素點和臨近4個點(水平或者垂直)進行卷積操作
                fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
                for (int it = 1; it < 3; it++) {
                    sum += tex2D(_MainTex, i.uv[it * 2 - 1]).rgb * weight[it];
                    sum += tex2D(_MainTex, i.uv[it * 2]).rgb * weight[it];
                }

                return fixed4(sum, 1.0);
            }
        ENDCG

        // 屏幕后處理渲染標簽
        ZTest Always Cull Off ZWrite Off

        // 第一個Pass主要進行高斯核垂直方向的處理
        Pass {
            NAME "GAUSSIAN_BLUR_VERTICAL"

            CGPROGRAM
                #pragma vertex vertBlurVertical
                #pragma fragment fragBlur
            ENDCG
        }

        // 第二個Pass主要進行高斯核水平方向的處理
        Pass {
            NAME "GAUSSIAN_BLUR_HORIZONTAL"

            CGPROGRAM
                #pragma vertex vertBlurHorizontal
                #pragma fragment fragBlur
            ENDCG
        }
    }

    FallBack "Diffuse"
}

運動模糊屏幕特效:通過將當前渲染紋理與之前渲染紋理之間進行混合,並按照模糊因子進行相乘來得到運動中模糊的效果。參考實現代碼如下:

Shader "Custom/Motion Blur" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }

    SubShader {
        CGINCLUDE

        #include "UnityCG.cginc"

        sampler2D _MainTex;
            fixed _BlurAmount;// 模糊系數

        struct v2f {
            float4 pos : SV_POSITION;
            half2 uv : TEXCOORD0;
        };

        v2f vert(appdata_img v) {
            v2f o;
            o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
            o.uv = v.texcoord;

            return o;
        }

        fixed4 fragRGB (v2f i) : SV_Target {
            return fixed4(tex2D(_MainTex, i.uv).rgb, _BlurAmount);
        }

        half4 fragA (v2f i) : SV_Target {
            return tex2D(_MainTex, i.uv);
        }

        ENDCG

        ZTest Always Cull Off ZWrite Off

        Pass {
            Blend SrcAlpha OneMinusSrcAlpha
            ColorMask RGB

            CGPROGRAM

            #pragma vertex vert 
            #pragma fragment fragRGB 

            ENDCG
        }

        Pass {   
            Blend One Zero
            ColorMask A

            CGPROGRAM  

            #pragma vertex vert 
            #pragma fragment fragA

            ENDCG
        }
    }
    FallBack Off
}

Bloom屏幕特效:首先根據閾值提取屏幕圖像中較亮的區域,把它們存儲在一張渲染紋理中,再利用高斯模糊對這張渲染紋理進行模糊處理,模擬光線擴散的效果,最后再將其與源屏幕圖像進行混合,得到最終效果。參考代碼如下:

Shader "Custom/Bloom" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}    // 場景渲染完畢后得到的屏幕對象
    }

    SubShader {
        CGINCLUDE

        #include "UnityCG.cginc"

        // 定義材質傳遞的屬性變量
        sampler2D _MainTex;
        half4 _MainTex_TexelSize;
        sampler2D _Bloom;   // 進行提取明亮和高斯模糊處理后的渲染紋理對象
        float _LuminanceThreshold;  // 明亮閾值
        float _BlurSize;    // 模糊范圍大小

        struct v2f {
            float4 pos : SV_POSITION; 
            half2 uv : TEXCOORD0;
        };  

        v2f vertExtractBright(appdata_img v) {
            v2f o;

            o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

            o.uv = v.texcoord;

            return o;
        }

        fixed luminance(fixed4 color) {
            return  0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b; 
        }

        fixed4 fragExtractBright(v2f i) : SV_Target {
            fixed4 c = tex2D(_MainTex, i.uv);
            fixed val = clamp(luminance(c) - _LuminanceThreshold, 0.0, 1.0);

            return c * val;
        }

        struct v2fBloom {
            float4 pos : SV_POSITION; 
            half4 uv : TEXCOORD0;
        };

        v2fBloom vertBloom(appdata_img v) {
            v2fBloom o;

            o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
            o.uv.xy = v.texcoord;   // 源渲染紋理采樣坐標 
            o.uv.zw = v.texcoord;   // 高斯模糊和抽離明亮區域后的渲染紋理的采樣坐標

            #if UNITY_UV_STARTS_AT_TOP 
            if (_MainTex_TexelSize.y < 0.0)
                o.uv.w = 1.0 - o.uv.w;
            #endif

            return o; 
        }

        fixed4 fragBloom(v2fBloom i) : SV_Target {
            return tex2D(_MainTex, i.uv.xy) + tex2D(_Bloom, i.uv.zw);
        } 

        ENDCG

        ZTest Always Cull Off ZWrite Off

        // 第一個Pass根據閾值提取屏幕圖像中較亮的區域,把它們存儲在一張渲染紋理中
        Pass {  
            CGPROGRAM  
            #pragma vertex vertExtractBright 
            #pragma fragment fragExtractBright 

            ENDCG  
        }

        // 第二個Pass對渲染紋理進行高斯核垂直分量的模糊處理
        UsePass "Custom/GAUSSIAN_BLUR_VERTICAL"

        // 第三個Pass對渲染紋理進行高斯核水平分量的模糊處理
        UsePass "Custom/GAUSSIAN_BLUR_HORIZONTAL"

        // 第四個Pass對源渲染紋理和高斯模糊以及抽離明亮區域后得到的渲染紋理進行混合處理
        Pass {  
            CGPROGRAM  
            #pragma vertex vertBloom 
            #pragma fragment fragBloom 

            ENDCG  
        }
    }

    FallBack Off
}

注意:
1.屏幕后處理能不能正常使用通常是有一些限制條件的,如:當前環境是否支持渲染紋理,紋理圖像是否支持特效處理,指定的Shader是否被支持等。
2.Graphics.Blit函數來指定Shader處理MonoBehaviour.OnRenderImage中接收到的場景渲染結束后對應的屏幕圖像時,需要在Shader的Properties中加入_MainTex (“Base (RGB)”, 2D) = “white” {}來接收該屏幕圖像。
3.NxN大小的高斯核進行采樣時,對應的采樣次數是NxNxWxH,其中W是圖像的寬度,H是圖像的高度,為了降低采樣次數,我們可以將高斯核進行橫向和縱向的拆分,此時的采樣次數為:2xNxWxH。
4.屏幕后處理中,標准的渲染標簽為:ZTest Always Cull Off ZWrite Off。
5.在Pass渲染流程中使用Name “Pass的名稱”來對Pass賦予一個名字,在其他的Shader中可以通過UsePass “Pass的名稱”來進行訪問。
6.CGINCLUDE …….. ENDCG語義塊進行定義頭文件,文件內部內容可以在包含該語義塊的文件的任何地方使用。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com