Single pass box blur is not giving same result as double pass box blur

I've been using 2-pass box blur written in GLSL in Blender without problem.

Blur horizontally:

uniform sampler2D bgl_RenderedTexture;
void main()  {
float blur = 0.3;

float resolution = blur * 0.1;
int n = 10;
int i = -n;
int totalWeight = 0;

vec2 st = gl_TexCoord[0].st;

vec4 outColor = vec4(0.0);
for(; i < n; ++i) {
vec2 uv = st + vec2(float(i) / 2.0 / float(n) * resolution, 0);
outColor += texture2D(bgl_RenderedTexture, uv);
totalWeight += 1;
}
gl_FragColor = outColor / float(totalWeight);
}


Blur vertically:

uniform sampler2D bgl_RenderedTexture;
void main()  {
float blur = 0.3;

float resolution = blur * 0.1;
int n = 10;
int j = -n;
int totalWeight = 0;

vec2 st = gl_TexCoord[0].st;

vec4 outColor = vec4(0.0);
for(; j < n; ++j) {
vec2 uv = st + vec2(0, float(j) / 2.0 / float(n) * resolution);
outColor += texture2D(bgl_RenderedTexture, uv);
totalWeight += 1;
}
gl_FragColor = outColor / float(totalWeight);
}


The output is as desired:

But when I try to do it with single pass, it's only blurring in the direction of the inner loop.

uniform sampler2D bgl_RenderedTexture;
void main()  {
float blur = 0.3;

float resolution = blur * 0.01;
int n = 10;
int i = -n;
int j = -n;
int totalWeight = 0;

vec2 st = gl_TexCoord[0].st;

vec4 outColor = vec4(0.0);
for(; i <= n; ++i) {
for(; j <= n; ++j) {
vec2 uv = st + vec2(float(i), float(j))
* resolution / float(n) / 2.0;
outColor += texture2D(bgl_RenderedTexture, uv);
totalWeight += 1;
}
}
gl_FragColor = outColor/ float(totalWeight);
}


I must be missing something really simple but it's not visible to me. Can anyone help me?

• You changed order of your mults and divs when calculating uv. – Andreas May 11 '16 at 17:15
• How does that affect a floating point calculation? I just checked with vec2 uv = st + vec2(float(i), float(j)) / 2.0 / float(n) * resolution; and the result is same. – Samik May 11 '16 at 17:29
• Have you tried turning it off and on again? Try rewriting the shader without looking at current implementation. I tried it on shadertoy and it works so there must be a really treacherous error connected to GLSL version you re using(precedence, rounding, etc.) that will probably wont show if you rewrite it. – wondra May 11 '16 at 17:47
• Treacherous indeed! Replaced for(;i <= n; ++i) and for(;j <=n; ++j) with for(i = -n; i <= n; ++i) and for(j = -n; j <=n; ++j) and it worked. Is it optimization? – Samik May 11 '16 at 18:00
• If you want some optimalizations, make n and resolution const, dont devide by two(make the base const smaller) and remove totalWeight , it is redundant (you know how many samples are in 2n+1 wide square, dont you) – wondra May 11 '16 at 18:43

I can see this question is old, but the answer is kind of trivial. Omitting the initialization in a nested loop is not equivalent to omitting it when it's the topmost level of the loop.

Let's walk through executing the code to see why:

int i = -n;
int j = -n;

for(; i <= n; ++i) {
for(; j <= n; ++j) {
vec2 uv = st + vec2(float(i), float(j))
* resolution / float(n) / 2.0;
outColor += texture2D(bgl_RenderedTexture, uv);
totalWeight += 1;
}
}


For simplicity, let's say n is 1.

1. We encounter the top for loop. We don't initialize i, so it's still i = -n = -1.

• we test i <= n (-1 <= 1) and pass, so we continue...
2. Next we encounter the inner for loop. We don't initialize j, so it's still j = -n = -1

• we test j <= n (-1 <= 1) and pass, so we continue...
3. We sample our texture with an offset of (-1, -1)

4. We loop and increment j, so it's now 0

• we test j <= n (0 <= 1) and pass, so we continue...
5. We sample our texture with an offset of (-1, 0)

6. We loop and increment j, so it's now 1

• we test j <= n (1 <= 1) and pass, so we continue...
7. We sample our texture with an offset of (-1, 1)

8. We loop and increment j, so it's now 2

• we test j <= n (2 <= 1) and fail, so we fall through to the outer loop
9. We loop and increment i, so it's now 0

• we test i <= n (0 <= 1) and pass, so we continue...
10. We encounter the inner for loop a second time. We don't initialize j, so it still has its previous value of 2

• we test j <= n (2 <= 1) and fail, so we immediately fall through to the outer loop.
11. This pattern repeats: only the first trip through the outer loop actually samples the texture. Every subsequent pass skips the inner loop entirely because we incremented j beyond the loop limit and never bothered to reset it. So we only get one vertical strip of samples, resulting in the image shown.

The solution is dirt simple, as noted in the comments: initialize your iteration variables. There's no weird undocumented feature or optimization or trick of the language happening here - the shader is doing exactly what you asked it to do, increasing j without ever reducing it back to -n.

• I must not be sleeping enough back then. Thanks for taking time to write 11 steps to explain it :D – Samik Sep 22 '16 at 16:45

It would appear omiting the loop init statement is not formally supported by GLSL, although probably widely implemented. Here is a link to someone else having similar problem:

https://bugs.chromium.org/p/angleproject/issues/detail?id=140

I did an honest attempt reading the GLSL grammar section but was unable to make much sense of it. Some token (conditionopt) explicitly states empty to be ok. The for init statement does not. (It would be great if someone could verify this.)

• GLSL 1.10.59 Specification, I believe this is the version I'm using since the code is written in legacy GLSL, please refer to page 77. "iteration statement" > "FOR LEFT_PAREN for_init_statement..." > "for_init_statement" > "expression_statement" > "SEMICOLON". So an expression statement can contain a single semicolon and for-loop initialization statement can contain an expression statement or a declaration statement. – Samik May 13 '16 at 10:40
• Right you are. You sure Blender is not using GLSL ES? (Will update answer tomorrow) – Andreas May 13 '16 at 12:03