0
votes
  • Description

    1. I using the OpenGL es 2.0 in the android with the glsl shader to do the gaussian blur effect.
    2. If without alpha channel, it works correct, but when i have alpha channel with png, the result will become too dark, it will be the same the link: http://www.jhlabs.com/ip/premultiplied_blur.jpg (the middle one effect with some dark).
    3. And all the article says using the premultiplied alpha, but i don't know how to using it, is same one can help me, thanks.
  • How i do the blur

    1. Load png texture
    2. Create FBO to render the horizontal blur
    3. Create FBO to render the vertical blur
    4. Render the result texture to the screen
  • My shader file

	static const char* ULBlurShader_vertexShader =
		"uniform mat4 fpsWorldViewProjectionMatrix;\n"
		"attribute vec4 gInPosition;\n"
		"attribute vec2 gInTex0;\n"
		"varying vec2 varTex0;\n"
		"void main() {\n"
		"	gl_Position = fpsWorldViewProjectionMatrix * gInPosition;\n"
		"	varTex0 = gInTex0.xy;\n"
		"}\n";

	static const char* ULBlurShaderHor_pixelShader =
		"uniform sampler2D sampler0; \n"
		"uniform vec4 fpsGaussBlur; \n"
		"uniform vec4 fpsGaussWeights[65]; \n"
		"varying vec2 varTex0;\n"
		"vec4 gaussBlurHorizontal(const int anRadius,vec2 avBaseCoo, vec2 avSamplerRes)"
		"{ \n"
		"	float fStartX = avBaseCoo.x - anRadius*avSamplerRes.x; \n"
		"	vec4 color = vec4(0, 0, 0, 0); \n"
		"	for (int x = 0; x < anRadius * 2; x++) \n"
		"	{ \n"
		"		color += texture2D(sampler0, vec2(fStartX + x*avSamplerRes.x, avBaseCoo.y))*fpsGaussWeights[x / 4][x % 4]; \n"
		"	} \n"
		"	return color; \n"
		"}\n"
		"void main() {\n"
		"	gl_FragColor.rgba = gaussBlurHorizontal(int(fpsGaussBlur.y), varTex0, vec2(fpsGaussBlur.z, fpsGaussBlur.w)); \n"
		"}\n";

	static const char* ULBlurShaderVert_pixelShader =
		"uniform sampler2D sampler0; \n"
		"uniform vec4 fpsGaussBlur; \n"
		"uniform vec4 fpsGaussWeights[65]; \n"
		"varying vec2 varTex0;\n"
		"vec4 gaussBlurVertical(const int anRadius,vec2 avBaseCoo, vec2 avSamplerRes)"
		"{ \n"
		"	float fStartY = avBaseCoo.y - (anRadius*avSamplerRes.y); \n"
		"	vec4 color = vec4(0, 0, 0, 0); \n"
		"	for(int y=0; y<anRadius*2; y++) \n"
		"	{ \n"
		"		color += texture2D(sampler0, vec2(avBaseCoo.x, fStartY+y*avSamplerRes.y))*fpsGaussWeights[y/4][y%4]; \n"
		"	} \n"
		"	return color; \n"
		"}\n"
		"void main() {\n"
		"	gl_FragColor.rgba = gaussBlurVertical(int(fpsGaussBlur.y), varTex0, vec2(fpsGaussBlur.z, fpsGaussBlur.w));\n"
		"}\n";
2

2 Answers

1
votes

I had the same problem, especially when blurring text that was surrounded by pixels with alpha=0.0. The text would fade away, to the point that it couldn't be seen (Middle image). However, I managed to get much better results once I added a 'Gamma Correction' step after the blur.

Left: original, Middle: Blur only (practically invisible), Right: Blur+Gamma Correction

So, for you case, after the blurring is complete, adjust the RGBA values according to something like this:

color = pow(color, vec4(1.0/fGamma))

Where fGamma is a float between 0.0 and 1.0 (I used fGamma=5.0 in my example).

Here is a link to a very good explanation: https://learnopengl.com/#!Advanced-Lighting/Gamma-Correction

0
votes

Yes well unfortunately there is an overall problem with transparency and what happens to other color channels when a pixel is transparent. If you try to draw the image without the alpha transparency (using RGB1) you will see that the transparent pixels are black. This may seem natural for at least some cases but not for all. Imagine you have an image which has nice pure white gradient starting from full opacity dropping down to including the full transparent. Then the first pixel would be (1,1,1,1), somewhere a pixel would be (1,1,1,0.5) and near the end it might (1,1,1,0.00X) but then the next pixel which has an alpha of 0 would become (0,0,0,0) instead of (1,1,1,0). So this is the black color you are mixing with and you see the dark border.

So it might seem the png exporter did a mistake since all transparent pixels should still be red but that is not the case. If the image had different colors (not just white or red in your case) then the exporter may not predict what color those pixels are. So in the end this is a correct state.

So what to do in this situation is you need to rebalance the color mixing for the transparent pixels. It also means changing how your blur shaders work. You need to ignore the transparent pixels when computing the color (but not the alpha) or even better you need to scale the effect on the color depending on the transparency, the alpha channel.

I will give you an example when summing 5 pixels in a certain weight similar to blur. Lets say the relative scales are 1, 2, 5, 2, 1. What you are doing now is color = (c1*1 + c2*2 + c3*5 + c4*2 + c5*1)/(1+2+5+2+1) but you have an optimization in this equation where you already have a normalized values you call fpsGaussWeights which in my example are 1/11, 2/11, 5/11... (Eleven is received by summing (1+2+5+2+1)) so you end with c1*1/11 + c2*2/11... Now let us change the equation to include the alpha values as well. Now the original is

color = (c1*1*c1.a + c2*2*c2.a + c3*5*c3.a + c4*2*c4.a + c5*1*c5.a)/(1*c1.a+2*c2.a+5*c3.a+2*c4.a+1*c5.a)

But you need to do this only on the color part so

color.rgb = (c1.rgb*1*c1.a + c2.rgb*2*c2.a + c3.rgb*5*c3.a + c4.rgb*2*c4.a + c5.rgb*1*c5.a)/(1*c1.a+2*c2.a+5*c3.a+2*c4.a+1*c5.a)

while the alpha channel preserves the previous blur equation. So as you can see you may no longer include the normalized values for weights as the sum of your weight values will not be 1.0 when multiplied by alpha channel.

Now I hope you can optimize this equation in a for loop by yourself. Also watch out for overflows if you are using low precisions.

Take a look into this for a reference (might even just work but I did not test it or even double check it):

    int sampleCount = 5;
    float weights[sampleCount];

    vec4 color = vec4(.0);
    float fullWeight = .0;
    for(int i=0; i<sampleCount; i++) {
        float actualWeight = weights[i] * textureColor.a;
        vec4 textureColor = ; // fetch the pixel
        color.rgb += textureColor.rgb * actualWeight; // can produce overflow
        /*
            We may do the computation on the fly so we do not need to divide in the end. This will remove the overflow issue
        color.rgb = (color.rgb*fullWeight + textureColor.rgb*actualWeight)/(fullWeight + actualWeight);
            Leads to:
        float newWeightResult = (fullWeight + actualWeight);
        float leftWeight = fullWeight/(fullWeight + actualWeight); //will be less or equal then 1
        float rightWeight = actualWeight/(fullWeight + actualWeight); //will be less or equal then 1
        color.rgb = color.rgb*leftWeight + textureColor.rgb*rightWeight; // no overflow
            The color.rgb /= fullWeight line needs to be removed when using this
         */

        color.a += textureColor.a * weights[i];
        fullWeight += actualWeight;
    }
    color.rgb /= fullWeight;

The weights you use can still be kept as they are. I only used (1,2,5...) so it is easier to explain.