Monokai
Monokai

Reputation: 1253

How to blur a transparent texture in a glsl shader?

My setup:

Ping-ponging RGBA FBO's, and two shaders for blurring: a horizontal and a vertical one. Imagine I want to blur a red rectangle.

The blend function is as follows:

_gl.blendEquationSeparate(_gl.FUNC_ADD, _gl.FUNC_ADD);
_gl.blendFuncSeparate(_gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA);

In my blur shaders I add several fragments like this:

vec4 color = texture2D(u_image, v_tex_coord + vec2(x, y) * u_amount) * weight;

Problem:

Blurring works fine on opaque textures, but as it mixes in more and more transparency, the colors become black, as if everything is mixed with a black background. My clear color is 0,0,0,0, so that makes sense.

Question:

How do I get a blur effect that truly goes to transparent red, instead of a red mixed with more and more black as the alpha goes to zero?

I basically want the same as when you blur something on a complete transparent background in Photoshop. Do I need premultiplied FBO's or do I need to handle the mixing of fragments in the shader differently?

Upvotes: 2

Views: 3101

Answers (2)

Unick
Unick

Reputation: 674

To apply blur to a transparent texture, you need to use alpha to correct. Here is a simplified formula for one pixel:

resultColor = (sum of pixels of input image) * 1 / K;

Commonly K is kernel size.

If you blur a transparent texture, you will need to accumulate alpha and use it as K.

resultAlpha = (sum of alpha pixels of input image);
resultColor = (sum of pixels of input image) * 1 / resultAlpha;
resultAlpha = resultAlpha * 1 / K;

For this formula, if you blur 4 pixels and one of them is opaque while another is transparent, the resulting pixel's color will be the same, but its alpha will be 4 times smaller.

In my example all channels have a value between 0 and 1.

Upvotes: 4

Monokai
Monokai

Reputation: 1253

I solved it by using premultiplied alpha everywhere.

Being used to how Photoshop works, it took me a while to grasp the concept. It felt a bit counterintuitive, but this explanation from Tom Forsyth helped me a lot.

All my shaders now multiply the RGB values by it's A:

gl_FragColor = clr.rgb * alpha;

and I'm using a different blendmode that makes this work:

_gl.blendEquation(_gl.FUNC_ADD); _gl.blendFunc(_gl.ONE, _gl.ONE_MINUS_SRC_ALPHA);

I had to make sure that my .PNG image textures are also premultiplied. I did that using:

_gl.pixelStorei(_gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);

Upvotes: 1

Related Questions