13

Q : Is there a way to use the default pipeline to blend the Alpha component properly?

Problem : I'm drawing semi-transparent surfaces into a texture, then I want to blit that texture into the main frame back buffer. Normally when you use straightforward alpha blending for transparency or antialiasing, it is to blend color components into the back buffer ( which has no alpha channel by deafult ).

However, when blending into a texture that has an alpha component, the usual GL_SRC_ALPHA, GL_ONE_MINUS_DST_ALHPA does not blend the "alpha" component properly :

enter image description here

Notice the problematic zone over the death star at the edge of the red and blue circles; the alpha component is supposed to be completely opaque. Note that the RGB components are blendend as expected.

So I played around with different settings for the BlendFunc and the closest to what I want is by using GL_DST_ALPHA as my dst alpha scaling factor.

enter image description here

There is still a problem with this approach because blending two surfaces with 50% opacity should give a combined surface of 75% opacity. But using OpenGL default formulas, I get (0.50.5) + (0.50.5) = 0.5. More disturbing, if I have 2 surfaces with 40% opacity, I get a LESS opaque surface : (.4*.4)+(.4*.4) = 0.32.

The real formula ( 1 - ( 1 - srcAlpha ) * ( 1 - dstAlpha ) ) would imply that I could use a GL_FUNC_MULT instead of GL_FUNC_ADD in the blend equation for the alpha component. Unfortunately this does not exist.

So I gave up and tried to do a solution using a shader. This becomes complicated really fast if the texture you are rendering to is also the texture you read from. Especially if you render multiple semi-transparent surfaces.

I'm looking for alternative solutions I have not already tried. Any idea is welcome.

EDIT : I also tried GL_ONE as both src and dst scaling factor. this makes the alpha component way too opaque as 1*.5 + 1*.5 will give 1 as the resulting alpha. Makes me wonder if GL_ONE and GL_DST_ALPHA would give better results.

p.s. : I used Anders Riggelsen's blending tool for the images.

double-beep
  • 5,031
  • 17
  • 33
  • 41
  • 1
    Try premultiplied textures (`r*=a, g*=a, b*=a` on texture load or right in the shader) plus `GL_ONE, GL_ONE_MINUS_SRC_ALPHA` blending. It's a classical way to do proper blening in OpenGL. – HolyBlackCat May 30 '16 at 19:37
  • @HolyBlackCat - I'm not loading a texture, as I pointed out in the first paragraph of the "problem" section :"I'm drawing semi-transparent surfaces into a texture". But I will try the combo you proposed thank you. – Jean-Simon Brochu May 30 '16 at 19:45
  • @HolyBlackCat - This is exactly what I was looking for. Who knew it would be that simple. I you post that as an answer, I will accept it. – Jean-Simon Brochu May 30 '16 at 19:49
  • 1
    Not sure if what you're trying to do is exactly the same, but it sounds similar to http://stackoverflow.com/questions/24346585/opengl-render-to-texture-with-partial-transparancy-translucency-and-then-rende. – Reto Koradi May 30 '16 at 22:47

2 Answers2

11

The standard technique to do perfect blending is to use

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

Note that it requires both the source and destination to be premultiplied, and produces premultiplied result.

Premultiplication basically means performing color.rgb *= color.a on the color. Usually you apply it to the textures when you load them, or to the final color in your shader.

Corollary: if alpha == 1, premultiplication makes no difference.


If the source is not premultiplied, but the destination is, use this instead:

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • I was already using GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA for color components. What fixed the issue was using GL_ONE, GL_ONE_MINUS_SRC_ALPHA for the alpha component. Meaning I had to use separate factors using : glBlendFuncSeparate( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA ); – Jean-Simon Brochu May 31 '16 at 12:28
  • @Jean-SimonBrochu That's another way. For some reason I've not realized that same result can be achieved without premultiplication. I've added your solution to the answer. – HolyBlackCat May 31 '16 at 14:32
1

Yes, kind of.

Blending factors ONE_MINUS_DST_ALPHA, ONE may be used for blending alpha correctly. If you have glBlendFuncSeperate you can set different blend factors for color and alpha. Operation is add.

I say kind of because as I recall seperate blend func appeared late in the deprecated API.

Edit: after doing the math those blend factors are mathematically equivalent to HolyBlackCat answer.

Andreas
  • 5,086
  • 3
  • 16
  • 36