The algorithm of Gaussian blur can be seen in this article written by Ruan Yifeng. Gaussian blur is called Gaussian blur because it uses gaussian’s density function of normal distribution:

One-dimensional form:

Two-dimensional form:

Related calculation steps:

In shader, we restore a simple Gaussian blur algorithm of PDD:

void main() {
    vec2 st = gl_FragCoord / iResulution;

    vec4 color = vec4(0.0);
    const int coreSize = 3;         // take a 3x3 pixel grid
    vec2 texelOffset = vec2(1.) /vec2(375..667.);  // The spacing of each plain grain

    float kernel[9];                // Convolution kernel, the weight of each pixel
    kernel[0] = 1.; kernel[1] = 2.; kernel[2] = 1.;
    kernel[3] = 2.; kernel[4] = 4.; kernel[5] = 2.;
    kernel[6] = 1.; kernel[7] = 2.; kernel[8] = 1.;

    int index = 0;
    for(int y=0; y<coreSize; y++)
    {
        for(int x = 0; x<coreSize; x++)
        {
            // This is the core, take the color value of 9 pixels in turn
            vec4 currentColor = texture2D(inputImageTexture, st + vec2(float(- 1+x)*texelOffset.x, float(- 1+y)*texelOffset.y));

            // Multiply the color value by the weight, which is convolution
            if (index= =0) { color += currentColor * kernel[0]; }
            else if (index= =1) { color += currentColor * kernel[1]; }
            else if (index= =2) { color += currentColor * kernel[2]; }
            else if (index= =3) { color += currentColor * kernel[3]; }
            else if (index= =4) { color += currentColor * kernel[4]; }
            else if (index= =5) { color += currentColor * kernel[5]; }
            else if (index= =6) { color += currentColor * kernel[6]; }
            else if (index= =7) { color += currentColor * kernel[7]; }
            else if (index= =8) { color += currentColor * kernel[8]; }

            index++;
        }
    }
    
    // Divide by the sum of the weights
    color /= 16.0;
    gl_FragColor=color;
}
Copy the code

The effect is not obvious, because our weight is not very accurate, and the size of our picture is relatively large (750×1334). If we only blur the mesh of 3×3, the effect is not obvious, so let’s optimize it below:

void main() {
    vec2 st = gl_FragCoord / iResulution;

    vec4 color = vec4(0.0);
    const int coreSize = 3;
    
    // Make the grain spacing bigger. It makes sense to make 3x3 9x9 or bigger
    vec2 texelOffset = vec2(2.) /vec2(375..667.);

    // Substitute the weight calculated by the Gaussian normal distribution function
    float kernel[9];
    kernel[0] = 0947416.; kernel[1] = 118318.; kernel[2] = 0947416.;
    kernel[3] = 118318.; kernel[4] = 147761.; kernel[5] = 118318.;
    kernel[6] = 0947416.; kernel[7] = 118318.; kernel[8] = 0947416.;

    int index = 0;
    for(int y=0; y<coreSize; y++)
    {
        for(int x = 0; x<coreSize; x++)
        {
            vec4 currentColor = texture2D(inputImageTexture, st + vec2(float(- 1+x)*texelOffset.x, float(- 1+y)*texelOffset.y));

            if (index= =0) { color += currentColor * kernel[0]; }
            else if (index= =1) { color += currentColor * kernel[1]; }
            else if (index= =2) { color += currentColor * kernel[2]; }
            else if (index= =3) { color += currentColor * kernel[3]; }
            else if (index= =4) { color += currentColor * kernel[4]; }
            else if (index= =5) { color += currentColor * kernel[5]; }
            else if (index= =6) { color += currentColor * kernel[6]; }
            else if (index= =7) { color += currentColor * kernel[7]; }
            else if (index= =8) { color += currentColor * kernel[8]; }

            index++;
        }
    }
    // The weights above have already been weighted averaged, so this step is not needed
    / / color / = 16.0;
    gl_FragColor = color;
}
Copy the code

As long as we enlarge the 3×3 grid, such as 9×9/16×16, or directly enlarge the pixel spacing, we can increase the blur effect.

However, the performance of the above implementation is relatively poor. Because the cost of traversal is too high. Usually split into two one-dimensional vectors, so that the time complexity is reduced from NxNxWxH to 2xNxWxH (W is the width of the image, H is the height of the image).

Let’s take the 5×5 convolution kernel as an example and sample in the vertical direction:

void main() {
    vec2 iResolution = vec2(375..667.);

    float offset[6];
    offset[1] = 0.; offset[2] = 1.; offset[3] = 2.; offset[4] = 3.; offset[5] = 4.;

    float weight[6];
    weight[1] = 0.2270270270; weight[2] = 0.1945945946; weight[3] = 0.1216216216;
    weight[4] = 0.0540540541; weight[5] = 0.0162162162; 

    vec4 color = texture2D(inputImageTexture, vec2(gl_FragCoord)/iResolution) * weight[0];

    for (int i=1; i<=5; i++) {
        color +=
            texture2D(inputImageTexture, (vec2(gl_FragCoord) +vec2(0.0.offset[i]))/iResolution)
                * weight[i];
        color +=
            texture2D(inputImageTexture, (vec2(gl_FragCoord) -vec2(0.0.offset[i]))/iResolution)
                * weight[i];
    }

    gl_FragColor = color;
}
Copy the code

The same is true for the horizontal direction, but this method still requires nine texture2D() texture sampling operations. In this article, we introduce a linear sampling method that reduces the number of texture2D() texture sampling operations from nine to five by using weights and spacing:

void main() {
    vec2 iResolution = vec2(375..667.);

    float offset[4];
    offset[1] = 0.; offset[2] = 1.3846153846; offset[3] = 3.2307692308;

    float weight[4];
    weight[1] = 0.2270270270; weight[2] = 0.3162162162; weight[3] = 0.0702702703;

    vec4 color = texture2D(inputImageTexture, vec2(gl_FragCoord)/iResolution) * weight[0];

    / / vertical
    for (int i=1; i<=3; i++) {
        color +=
            texture2D(inputImageTexture, (vec2(gl_FragCoord) +vec2(0.0.offset[i]))/iResolution)
                * weight[i];
        color +=
            texture2D(inputImageTexture, (vec2(gl_FragCoord) -vec2(0.0.offset[i]))/iResolution)
                * weight[i];
    }

    vec4 color2 = texture2D(inputImageTexture, vec2(gl_FragCoord)/iResolution) * weight[0];

    / / level
    for (int i=1; i<=3; i++) {
        color2 +=
            texture2D(inputImageTexture, (vec2(gl_FragCoord) +vec2(offset[i], 0.0))/iResolution)
                * weight[i];
        color2 +=
            texture2D(inputImageTexture, (vec2(gl_FragCoord) -vec2(offset[i], 0.0))/iResolution)
                * weight[i];
    }

    gl_FragColor = mix(color, color2, . 5);
}
Copy the code

The naked eye won’t be able to tell the difference, but performance will improve:

Another way to make the image more fuzzy is to apply the blur function to the framebuffer multiple times to enhance the blur effect.


/ / https://github.com/Jam3/glsl-fast-gaussian-blur
// It contains only one direction and needs to be superimposed
// 3x3
vec4 blur5(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
  vec4 color = vec4(0.0);
  vec2 off1 = vec2(1.3333333333333333) * direction;
  color += texture2D(image, uv) * 0.29411764705882354;
  color += texture2D(image, uv + (off1 / resolution)) * 0.35294117647058826;
  color += texture2D(image, uv - (off1 / resolution)) * 0.35294117647058826;
  return color; 
}
// 5x5
vec4 blur9(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
  vec4 color = vec4(0.0);
  vec2 off1 = vec2(1.3846153846) * direction;
  vec2 off2 = vec2(3.2307692308) * direction;
  color += texture2D(image, uv) * 0.2270270270;
  color += texture2D(image, uv + (off1 / resolution)) * 0.3162162162;
  color += texture2D(image, uv - (off1 / resolution)) * 0.3162162162;
  color += texture2D(image, uv + (off2 / resolution)) * 0.0702702703;
  color += texture2D(image, uv - (off2 / resolution)) * 0.0702702703;
  return color;
}
// 7x7
vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
  vec4 color = vec4(0.0);
  vec2 off1 = vec2(1.411764705882353) * direction;
  vec2 off2 = vec2(3.2941176470588234) * direction;
  vec2 off3 = vec2(5.176470588235294) * direction;
  color += texture2D(image, uv) * 0.1964825501511404;
  color += texture2D(image, uv + (off1 / resolution)) * 0.2969069646728344;
  color += texture2D(image, uv - (off1 / resolution)) * 0.2969069646728344;
  color += texture2D(image, uv + (off2 / resolution)) * 0.09447039785044732;
  color += texture2D(image, uv - (off2 / resolution)) * 0.09447039785044732;
  color += texture2D(image, uv + (off3 / resolution)) * 0.010381362401148057;
  color += texture2D(image, uv - (off3 / resolution)) * 0.010381362401148057;
  return color;
}
Copy the code
/ / https://www.shadertoy.com/view/XdfGDH
// Normally distributed probability density function
float normpdf(in float x, in float sigma) {
	return 0.39894*exp(0.5*x*x/(sigma*sigma))/sigma;
}
vec3 gaussianblur(int size, sampler2D texture.vec2 resolution) {
    //declare stuff
    const int mSize = size;
    const int kSize = (mSize- 1) /2;
    float kernel[mSize];
    vec3 final_colour = vec3(0.0);
    
    //create the 1-D kernel
    float sigma = 7.0;
    float Z = 0.0;
    for (int j = 0; j <= kSize; ++j)
    {
        kernel[kSize+j] = kernel[kSize-j] = normpdf(float(j), sigma);
    }
    
    //get the normalization factor (as the gaussian has been clamped)
    for (int j = 0; j < mSize; ++j)
    {
        Z += kernel[j];
    }
    
    //read out the texels
    for (int i=-kSize; i <= kSize; ++i)
    {
        for (int j=-kSize; j <= kSize; ++j)
        {
            final_colour += kernel[kSize+j]*kernel[kSize+i]*texture2D(texture, (gl_FragCoord.xy+vec2(float(i),float(j))) / resolution.xy).rgb; }}return final_colour/(Z*Z);
}
Copy the code
/ / from: https://gl-transitions.com/editor/LinearBlur
vec4 blur(vec2 _uv, sampler2D texture) {
    float disp = 0.;
    float intensity = 2.;
    const int passes = 6;
    vec4 c1 = vec4(0.0);
    disp = intensity*(0.5-distance(0.5.1.));
  
    for (int xi=0; xi<passes; xi++) {
        float x = float(xi) / float(passes) - 0.5;
        for (int yi=0; yi<passes; yi++) {
            float y = float(yi) / float(passes) - 0.5;
            vec2 v = vec2(x, y);
            float d = disp;
            c1 += texture2D(texture, _uv + d*v);
        }
    }
    c1 /= float(passes*passes);
    return c1;
}
Copy the code


Next article Shader motion blur.

Related links:

  • www.ruanyifeng.com/blog/2012/1…
  • Blog.csdn.net/fansongy/ar…
  • www.geeks3d.com/20100909/sh…
  • Rastergrid.com/blog/2010/0…