First of all, due to requirements constraints, I am working in OpenGL v. 2.1, and GLSL 120. I have implemented a simple fragment shader that applies a two pass (horizontal & vertical) Gaussian blur with n-kernel weights obtained from Pascal's Triangle. For the image below, I have decided to use a kernel size of 32, just for funsies:
As observed, the edges of the filtered blob seems to have some strange artifacts, as well as a ringing effect on its edges. For reference, here's how I am applying the blur:
if(isHorizontal)
{
result += texture2D(tex, vec2( curFrag.x - 14.0 * xOff, curFrag.y )).rgba * 0.000000115484001;
result += texture2D(tex, vec2( curFrag.x - 13.0 * xOff, curFrag.y )).rgba * 0.00000115484001;
result += texture2D(tex, vec2( curFrag.x - 12.0 * xOff, curFrag.y )).rgba * 0.000008372590071;
result += texture2D(tex, vec2( curFrag.x - 11.0 * xOff, curFrag.y )).rgba * 0.0000468865044;
result += texture2D(tex, vec2( curFrag.x - 10.0 * xOff, curFrag.y )).rgba * 0.0002109892698;
result += texture2D(tex, vec2( curFrag.x - 9.0 * xOff, curFrag.y )).rgba * 0.0007836744306;
result += texture2D(tex, vec2( curFrag.x - 8.0 * xOff, curFrag.y )).rgba * 0.002448982596;
result += texture2D(tex, vec2( curFrag.x - 7.0 * xOff, curFrag.y )).rgba * 0.006530620255;
result += texture2D(tex, vec2( curFrag.x - 6.0 * xOff, curFrag.y )).rgba * 0.01502042659;
result += texture2D(tex, vec2( curFrag.x - 5.0 * xOff, curFrag.y )).rgba * 0.03004085317;
result += texture2D(tex, vec2( curFrag.x - 4.0 * xOff, curFrag.y )).rgba * 0.05257149305;
result += texture2D(tex, vec2( curFrag.x - 3.0 * xOff, curFrag.y )).rgba * 0.08087922008;
result += texture2D(tex, vec2( curFrag.x - 2.0 * xOff, curFrag.y )).rgba * 0.1097646558;
result += texture2D(tex, vec2( curFrag.x - 1.0 * xOff, curFrag.y )).rgba * 0.131717587;
result += texture2D(tex, curFrag).rgba * 0.1399499362;
result += texture2D(tex, vec2( curFrag.x + 1.0 * xOff, curFrag.y )).rgba * 0.131717587;
result += texture2D(tex, vec2( curFrag.x + 2.0 * xOff, curFrag.y )).rgba * 0.1097646558;
result += texture2D(tex, vec2( curFrag.x + 3.0 * xOff, curFrag.y )).rgba * 0.08087922008;
result += texture2D(tex, vec2( curFrag.x + 4.0 * xOff, curFrag.y )).rgba * 0.05257149305;
result += texture2D(tex, vec2( curFrag.x + 5.0 * xOff, curFrag.y )).rgba * 0.03004085317;
result += texture2D(tex, vec2( curFrag.x + 6.0 * xOff, curFrag.y )).rgba * 0.01502042659;
result += texture2D(tex, vec2( curFrag.x + 7.0 * xOff, curFrag.y )).rgba * 0.006530620255;
result += texture2D(tex, vec2( curFrag.x + 8.0 * xOff, curFrag.y )).rgba * 0.002448982596;
result += texture2D(tex, vec2( curFrag.x + 9.0 * xOff, curFrag.y )).rgba * 0.0007836744306;
result += texture2D(tex, vec2( curFrag.x + 10.0 * xOff, curFrag.y )).rgba * 0.0002109892698;
result += texture2D(tex, vec2( curFrag.x + 11.0 * xOff, curFrag.y )).rgba * 0.0000468865044;
result += texture2D(tex, vec2( curFrag.x + 12.0 * xOff, curFrag.y )).rgba * 0.000008372590071;
result += texture2D(tex, vec2( curFrag.x + 13.0 * xOff, curFrag.y )).rgba * 0.00000115484001;
result += texture2D(tex, vec2( curFrag.x + 14.0 * xOff, curFrag.y )).rgba * 0.000000115484001;
}
else
{
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 14.0 * yOff )).rgba * 0.000000115484001;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 13.0 * yOff )).rgba * 0.00000115484001;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 12.0 * yOff )).rgba * 0.000008372590071;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 11.0 * yOff )).rgba * 0.0000468865044;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 10.0 * yOff )).rgba * 0.0002109892698;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 9.0 * yOff )).rgba * 0.0007836744306;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 8.0 * yOff )).rgba * 0.002448982596;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 7.0 * yOff )).rgba * 0.006530620255;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 6.0 * yOff )).rgba * 0.01502042659;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 5.0 * yOff )).rgba * 0.03004085317;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 4.0 * yOff )).rgba * 0.05257149305;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 3.0 * yOff )).rgba * 0.08087922008;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 2.0 * yOff )).rgba * 0.1097646558;
result += texture2D(tex, vec2( curFrag.x, curFrag.y - 1.0 * yOff )).rgba * 0.131717587;
result += texture2D(tex, curFrag).rgba * 0.1399499362;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 1.0 * yOff )).rgba * 0.131717587;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 2.0 * yOff )).rgba * 0.1097646558;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 3.0 * yOff )).rgba * 0.08087922008;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 4.0 * yOff )).rgba * 0.05257149305;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 5.0 * yOff )).rgba * 0.03004085317;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 6.0 * yOff )).rgba * 0.01502042659;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 7.0 * yOff )).rgba * 0.006530620255;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 8.0 * yOff )).rgba * 0.002448982596;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 9.0 * yOff )).rgba * 0.0007836744306;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 10.0 * yOff )).rgba * 0.0002109892698;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 11.0 * yOff )).rgba * 0.0000468865044;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 12.0 * yOff )).rgba * 0.000008372590071;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 13.0 * yOff )).rgba * 0.00000115484001;
result += texture2D(tex, vec2( curFrag.x, curFrag.y + 14.0 * yOff )).rgba * 0.000000115484001;
}
Furthermore, I am using two framebuffers. First, I draw the white blob onto a texture bound to the first framebuffer, then I apply my blur shader onto the second framebuffer for a horizontal pass, then back to the first one for a vertical pass. I have implemented a slider that repeats this process as well, see snippet below:
glUseProgram(gauss_blur_frag);
glUniform1f(glGetUniformLocation(gauss_blur_frag, "offset"), (float)radius);
glUniform2f(glGetUniformLocation(gauss_blur_frag, "resolution"), (float)fboWidth, (float)fboHeight);
for(int i = 1; i < smoothAmount; i++)
{
glUniform1i(glGetUniformLocation(gauss_blur_frag, "isHorizontal"), true);
drawTexOnFBO(secondFBO, firstFBO->texId, bounds);
glUniform1i(glGetUniformLocation(gauss_blur_frag, "isHorizontal"), false);
drawTexOnFBO(firstFBO, secondFBO->texId, bounds);
}
The banding/ringing/artifacts get more pronounced as I increase the offset/radius of my blur, as well as increase the number of times the for-loop runs. The aim for this exercise is to simply apply a 'softening' effect on the edges of the blob, without the kernels being visible while being able to manipulate the offset. Can anyone shed some light on this issue? Thank you.
Of course. If you increase the radius, the you increase the distance between the sample points, but you don't increase the number of sample points itself. This causes that there is a gap between to texels when you lookup the 32 samples and you don't consider the whole information of the source texture.
Note, for a large radius, 2 adjacent points in the target texture uses completely different texels from the source texture when be processed. This causes the banding and artefacts.
In common this effect can be decreased by using bilinear texture filtering (
GL_LINEAR
). For a completely smooth and free of artefact blur effect, you have to increase the number of samples. But this will decrease the performance rapidly. See also Fast Gaussian blur at pause.See the example, where the effect can be reproduced with decreasing blur factor and increasing radius: