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…