2

I am drawing brush strokes to an framebuffer object using the method described in the answer of this question:

opengl - blending with previous contents of framebuffer

This method correctly alpha - blends different OpenGL drawing operations into one FBO and makes sure the alpha stays correct.

It uses

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

to mix the OpenGL drawing operations (in my case brush strokes) into FBO and uses

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

to draw the final FBO (in my case a Photoshop like layer) back to the screen which removes the premultiplied alpha used in the method. This works fine for me when blitting the FBO to the screen.

However, when I want to read out the content of the FBO I cannot get rid of the premultiplied alpha. I.e. when I use

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

to draw the layer into another FBO and than use glReadPixel() to read out the content of this FBO, the content is still premultiplied.

In other words, when I draw the FBO to the screen, blending and removing the prem. alpha works, when doing the same thing and drawing into an FBO, it fails.

Here is resulting (wrong) image when I read out the FBO:

enter image description here

and here is the correct result when drawing the layer directly to the screen:

enter image description here

Thanks for any help.

Community
  • 1
  • 1
Markus Moenig
  • 1,264
  • 1
  • 12
  • 16
  • stackoverflow is not the place to talk about "basically". Be precise, show code, go into detail, because otherwise people are just going to ignore your question as clearly not enough information to offer help on and move on. – Mike 'Pomax' Kamermans Jan 22 '16 at 04:32
  • Thanks Mike. I updated it a bit, when reading the original question and it's solution described in the link the problem I have is quite clear. I just did not want to replicate the whole thread in my question again ... . Hope thats fine. – Markus Moenig Jan 22 '16 at 05:01
  • I doubt it is - if you talk about code you use in your question, show the code you use (and remember to syntax-tag it as such). Show an [mcve](/help/mcve), explain where your problem diverges from the question and answer you reference. If you have two different contexts with the same instructions, that's the code you want to show people so they can spot obvious shortcomings/errors/etc – Mike 'Pomax' Kamermans Jan 22 '16 at 05:04
  • 1
    How did you generate the wrong result image file? You called gl.readPixels on the FBO which has premultiplied alpha in it. So you've now got a Uint8Array of premultiplied pixels. How do you then convert to PNG? Also PNG is un-premultiplied so you'd have to un-premultiply manually before saving to PNG. – gman Jan 22 '16 at 10:28
  • Actually I copy the FBO with the prem. alpha to another FBO which has been cleared with 0, 0, 0, 0. I use glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) for the copy, somehow however the prem. alpha is still there (it works when copying to the screen). I do not want to remove the prem. alpha manually as it may take a long time on large FBOs .. . – Markus Moenig Jan 22 '16 at 11:46

1 Answers1

5

I'm not sure what you mean by "get rid of the premultiplied alpha". If you want the picture to be un-premultiplied then you have to manually unpremultiply it either in JavaScript or in a shader with something like

gl_FragCoord = vec4(color.a > 0. ? color.rgb / color.a : vec3(0), color.a);

The reason it works when you draw to the canvas is because by default the canvas expects premultiplied alpha. Rendering with ONE,ONE_MINUS_SRC_ALPHA is drawing with premultiplied alpha and keeping the result premultiplied

Example:

Un-premultiplied    Red = 0.7  Alpha = 0.2

    convert to premultiplied   Red = Red * Alpha

Premultiplied       Red = 0.14 Alpha = 0.2

    blend with ONE,ONE_MINUS_SRC_ALPHA

                    DstRed = 0.0  DstAlpha = 0.0

    newPixelRed    = Red(0.14) * ONE + DstRed(0.0) * (1 - Alpha(0.2))
    newPixelRed    = 0.14 +            0.0 * 0.8
    newPixelRed    = 0.14

    newPixelAlpha  = Alpha(0.2) * ONE + DstAlpha(0.0) * (1 - Alpha(0.2))
    newPixelAlpha  = 0.2 + 0.0 * 0.8
    newPixelAlpha  = 0.2

So newPixelRed and newPixelAlpha area exactly what they were before you drew.

The only way to get newPixelRed to go back to its unpremultiplied state is to divide by alpha

newPixelRed = Red(0.14) / Alpha(0.2)
newPixelRed = 0.14 / 0.2
newPixelRed = 0.7            <- The original red before it was premultiplied

You can only do that in a shader or JavaScript. Blending alone won't do it.

gman
  • 83,286
  • 25
  • 191
  • 301
  • Thanks for the answer. Your fragment shader works great. You are right, the canvas expects prem. alpha, that was the logical error on my side as I thought what has to work FBO -> Screen has to work FBO -> FBO which is certainly not the case here. Thanks alot! – Markus Moenig Jan 23 '16 at 01:48