9
\$\begingroup\$

I've been having a lot of trouble trying to get a OpenGL blend function to work as I'd expect it to with like what I'd expect (or from any sensible image editing program). As an example, I'll use these two images to blend (bit difficult to see on white backgrounds so the colors are labeled):

Images to be blended

Test Images

This is what I expect to happen (and what happens in paint.net):

Expected Result

Paint.Net Image Test

Obviously opengl's default blend function makes it look like this (very wrong):

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

OpenGL Normal Blending Test

After a ton of testing, this is the closest I could get to creating a "good" blend function:

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)

OpenGL Custom Blend Test

Looking back at the original expected result though, you'll notice that some of the colors are a bit dimmer than they should be (the middle left part). Specifically, they are premultiplied to half their color value (because of the .5 alpha), and I can't seem to make a function that does not do this (without causing odd blending issues with the barely visible red transparent part).

Does anyone know a solution to this issue? One that I had was to use premultiplied alpha in the sources (while I dont want to do this because it requires extra work to convert every color I use in my game to premultiplied or just write some stuff in each shader) and do it like that:

glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA) (No premultiplication)

OpenGL Custom Blend Test

Obviously thats wrong too, but this is actually the only correct result I've gotten so far:

glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA) (Premultiplied inputs)

OpenGL Custom Blend Test

Only problem is, how would I get rid of the premultiplication to display it on the screen? It would probably require an additional render cycle for each thing I blend and that seems way too complex for this issue, so I'm still looking for an answer (its interesting that I cant find anything on this, because OpenGL is so widely used that I'd image someone else ran into this problem).

Sources:

Online blend function testing - http://www.andersriggelsen.dk/glblendfunc.php
Bottom Layer Image - https://i.sstatic.net/XjLNW.png
Top Layer Image - https://i.sstatic.net/9CN6w.png

\$\endgroup\$
2
  • \$\begingroup\$ All screenshots have been taken from Anders Riggelsen's online Visual glBlendFunc + glBlendEquation Tool. In future, that sort of thing would be useful to mention when asking for help. \$\endgroup\$ Commented Sep 6, 2014 at 5:01
  • \$\begingroup\$ @TrevorPowell Dang I knew I forgot to say something (I was really planning on putting that at the bottom). Either way its just what I was using to easily test blend modes, it should be standard for any other OpenGL sort of thing (even though I can see other people wanting to use it). \$\endgroup\$
    – Lemon Drop
    Commented Sep 6, 2014 at 6:03

2 Answers 2

6
\$\begingroup\$

In your first example (glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)), you're telling it to blend colors based on the alpha of the image which gets drawn on top (the "Foreground" one). So when you're blending 50% between the foreground color (38,50,168) and the background color (38,50,168) in the bottom left corner of the image, you unsurprisingly get exactly the same color (38,50,168). Because that cross-blend is exactly what you told OpenGL to do.

Judging by your "expected" image, you don't want to do a cross-blend between the two colors -- you want to overlay the foreground color over the full-strength background color, not cross-blend between them.

To do that, you want to use glBlendFuncSeparate (since you're treating the background's color differently than its alpha), and specify that during the blending operation, the background alpha value should be treated as 100%.

working

To make it easier to view, here's the final RGB values being output: RGB

And here's the final transparency values being output. alpha

(That '100%' transparency area should actually be labelled as 99.7% transparent, and the bottom right '50%' area should be labelled as 49.7% transparent, both due to the red-tinted bit on the right side the first image. Also, sorry for listing numbers using "transparency" rather than opacity; possibly a little confusing.)

If there are specific problems, please point out the area of either the RGB or the alpha image which are producing incorrect values.

\$\endgroup\$
4
  • \$\begingroup\$ Shouldn't the source alpha coefficient be GL_ONE as well? Otherwise the alpha equation looks like dest_alpha = src_alpha * src_alpha + 1 * dest_alpha \$\endgroup\$
    – bcrist
    Commented Sep 6, 2014 at 5:35
  • \$\begingroup\$ Well if you look at it that middle left piece is still a lot darker than it should be, also flipping the layers makes it look like: i.imgur.com/rigBNz6.png (that barely visible red is there to test for stuff like that \$\endgroup\$
    – Lemon Drop
    Commented Sep 6, 2014 at 6:32
  • \$\begingroup\$ You're blending it at 50% against (0,0,0). Of course it's darker than when you blend it at 50% against (255,255,255). \$\endgroup\$ Commented Sep 6, 2014 at 7:03
  • \$\begingroup\$ @TrevorPowell Shouldn't that not matter at all if the alpha is 0? Like the equations I am going for are like: finalAlpha = srcAlpha + destAlpha * (1 - srcAlpha) and finalColor = ((srcColor * srcAlpha) + (destColor * destAlpha * (1 - srcAlpha))) / finalAlpha The Color * Alpha bit in the color calculation can be done in the shaders by outputting premultiplied values, but theres no way to divide by the finalAlpha to get rid of the premultiplication. (Taken from here) \$\endgroup\$
    – Lemon Drop
    Commented Sep 6, 2014 at 19:28
1
\$\begingroup\$

I had the exact same problem when I wanted to render a set of overlapping framebuffer objects like layers. The problem is Open GL blending functions is linear and it works when destination background is opaque. But actual blending equation for blending two transparent layers is not a linear equation and contains a division part.

out_color = {src_color * src_alpha + dest_color * dest_alpha * (1-src_alpha)} / out_alpha

I don't think it is possible to do this with current OpengGL implementations. So as a work around I had to user a pixel shader to manually calculate the output of each pixel.

\$\endgroup\$
1
  • \$\begingroup\$ Yeah what I did was just pre-multiply the alpha on the textures rather than doing some workarounds, it is easy to do that with some libraries (they'll just premultiply textures on load) \$\endgroup\$
    – Lemon Drop
    Commented Jan 29, 2016 at 8:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .