Gaussian Blur Shader

I’m attempting to do a 3-pass gaussian blur shader on a texture. The first pass is a horizontal blur, the second pass is a vertical blur, and the third pass is to draw the original image back on top of the two blurred versions. The result I am looking for is what I would describe as a yellow outer glow effect.

I think I am running into a problem because of my grab pass which passes the result of the first shader to the second. The second pass seems to be flipped (and smaller). If I negate the Y-value passed to tex2D() in the second pass I don’t see the flipped version anymore but I also don’t see the vertical blur that I’m expecting. I’m also curious as to why the background of the final texture appears to have a slight yellow tint to it (the original texture has an entirely transparent background).

What am I doing wrong?

8317-capture.png

Here is my shader code:

Shader "Custom/GaussianBlur"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" { }	 
	}

	SubShader
	{
		// Horizontal blur pass
		Pass
		{
            Blend SrcAlpha OneMinusSrcAlpha 
            Name "HorizontalBlur"

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag			
			#include "UnityCG.cginc"
			
			sampler2D _MainTex;
			
			struct v2f
			{
				float4  pos : SV_POSITION;
				float2  uv : TEXCOORD0;
			};
			
			float4 _MainTex_ST;
			
			v2f vert (appdata_base v)
			{
				v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
			}
			
			half4 frag (v2f i) : COLOR
			{
				float blurAmount = 0.0075;

				half4 sum = half4(0.0);

                sum += tex2D(_MainTex, float2(i.uv.x - 5.0 * blurAmount, i.uv.y)) * 0.025;
                sum += tex2D(_MainTex, float2(i.uv.x - 4.0 * blurAmount, i.uv.y)) * 0.05;
                sum += tex2D(_MainTex, float2(i.uv.x - 3.0 * blurAmount, i.uv.y)) * 0.09;
                sum += tex2D(_MainTex, float2(i.uv.x - 2.0 * blurAmount, i.uv.y)) * 0.12;
                sum += tex2D(_MainTex, float2(i.uv.x - blurAmount, i.uv.y)) * 0.15;
                sum += tex2D(_MainTex, float2(i.uv.x, i.uv.y)) * 0.16;
                sum += tex2D(_MainTex, float2(i.uv.x + blurAmount, i.uv.y)) * 0.15;
                sum += tex2D(_MainTex, float2(i.uv.x + 2.0 * blurAmount, i.uv.y)) * 0.12;
                sum += tex2D(_MainTex, float2(i.uv.x + 3.0 * blurAmount, i.uv.y)) * 0.09;
                sum += tex2D(_MainTex, float2(i.uv.x + 4.0 * blurAmount, i.uv.y)) * 0.05;
                sum += tex2D(_MainTex, float2(i.uv.x + 5.0 * blurAmount, i.uv.y)) * 0.025;

                sum.r = 1.0;
                sum.g = 1.0;
                sum.b = 0.0;

				return sum;
			}
			ENDCG
		}
        
    	GrabPass { }

    	// Vertical blur pass
        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha
            Name "VerticalBlur"
                        
            CGPROGRAM
            #pragma vertex vert
			#pragma fragment frag			
			#include "UnityCG.cginc"

			sampler2D _GrabTexture : register(s0);

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

            float4 _GrabTexture_ST;

            v2f vert (appdata_base v)
			{
				v2f o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _GrabTexture);
                return o;
			}

            half4 frag (v2f i) : COLOR
			{
				float blurAmount = 0.0075;

				half4 sum = half4(0.0);

                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y - 5.0 * blurAmount)) * 0.025;
                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y - 4.0 * blurAmount)) * 0.05;
                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y - 3.0 * blurAmount)) * 0.09;
                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y - 2.0 * blurAmount)) * 0.12;
                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y - blurAmount)) * 0.15;
                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y)) * 0.16;
                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y + blurAmount)) * 0.15;
                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y + 2.0 * blurAmount)) * 0.12;
                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y + 3.0 * blurAmount)) * 0.09;
                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y + 4.0 * blurAmount)) * 0.05;
                sum += tex2D(_GrabTexture, float2(i.uv.x, i.uv.y + 5.0 * blurAmount)) * 0.025;

                sum.r = 1.0;
                sum.g = 1.0;
                sum.b = 0.0;

				return sum;
			}
            ENDCG
        }

        // Original image pass
        Pass
		{
			ZWrite Off
 			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"
			sampler2D _MainTex;
			struct v2f
			{
				float4  pos : SV_POSITION;
				float2  uv : TEXCOORD0;
				float2 depth : TEXCOORD1;
			};

			float4 _MainTex_ST;
			v2f vert (appdata_base v)
			{
				v2f o;
				o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
				UNITY_TRANSFER_DEPTH(o.depth);
				o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
				return o;
			}

			half4 frag (v2f i) : COLOR
			{
				half4 originalPixel = half4(0.0);
				originalPixel = tex2D(_MainTex, float2(i.uv.x, i.uv.y));

				return originalPixel;
			}
			ENDCG
		}
	}

	Fallback "VertexLit"
}

For the yellowing, it appears you have told the shader to yellow the blur in each pass. here’s where you went wrong:

sum.r = 1.0
sum.g = 1.0
sum.b = 0.0

For the blurring, count the code lines that do the blurring, then divide the result color by the number of lines. Example:

sum += (whatever you do to blur the texture)
sum = sum / (number of times you did the above)

this will give the same colour, but with blurring!

just remove those lines and the yellowing should go. as for flipped textures, i am so sure i saw something in the manual describing this issue and it having something to do with multiple renderTextures and having AA turned on. in this case, you could flip the texture using this in your vertex shader (this was taken from the unity manual):

// On D3D when AA is used, the main texture & scene depth texture
// will come out in different vertical orientations.
// So flip sampling of the texture when that is the case (main texture
// texel size will have negative Y).
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
        uv.y = 1-uv.y;
#endif

Then try testing the shader. if the shader comes out right, it’s fixed! hope this is useful.

In case anyone’s still looking, here’s a Gaussian blur function I wrote for Unity shaders by cleaning up above code and combining it with mrharicot’s gaussian blur shader on Shadertoy:

//normpdf function gives us a Guassian distribution for each blur iteration; 
//this is equivalent of multiplying by hard #s 0.16,0.15,0.12,0.09, etc. in code above
float normpdf(float x, float sigma)
{
	return 0.39894*exp(-0.5*x*x / (sigma*sigma)) / sigma;
}

//this is the blur function... pass in standard col derived from tex2d(_MainTex,i.uv)
half4 blur(sampler2D tex, float2 uv,float blurAmount) {

    //get our base color...
    half4 col = tex2D(tex, uv);

    //total width/height of our blur "grid":
    const int mSize = 11;

    //this gives the number of times we'll iterate our blur on each side 
    //(up,down,left,right) of our uv coordinate;
    //NOTE that this needs to be a const or you'll get errors about unrolling for loops
	const int iter = (mSize - 1) / 2;

    //run loops to do the equivalent of what's written out line by line above
    //(number of blur iterations can be easily sized up and down this way)
	for (int i = -iter; i <= iter; ++i) {
		for (int j = -iter; j <= iter; ++j) {
			col += tex2D(tex, float2(uv.x + i * blurAmount, uv.y + j * blurAmount)) * normpdf(float(i), 7);
	       }
	}

    //return blurred color
    return col/mSize;
}