james_lohr's picture

Blend Function when rendering to an FBO

Hello,

When rendering normally to the screen, I would use:

GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);

Now, I'm trying to generate an FBO that I later render to the screen, and which has translucent items in it. I first clear the FBO:

GL.ClearColor(1.0f, 1.0f, 1.0f, 0f) ;

If I now render to my FBO using:

GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);

Then this doesn't work for translucent content, because it blends with the white I cleared the FBO to in the first place.

Another thing I've noticed is that if I use BlendingFactorSrc.SrcAlpha at all, then the transparency is "doubled up". As in, if I were to render a texture (e.g. a bit of text) to my FBO, and then render my FBO using GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha) to the screen at 100% opacity, the text will appear narrower because the edge opacity will be reduced.

So far the best results I've obtained have been rendering to the FBO using:

GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.Zero);

..and then rendering the FBO using GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha).

This makes sense in that my FBO now contains the exactly what the texture I rendered to it contained previously, so I'm effectively rendering the original texture.

The major floor with this is that I cannot have overlapping items in my FBO.

The behaviour I would like (which I'm starting to realise may actually be impossible) is that, if I render two textures to my FBO and then render my FBO to the screen, it will look the same as if I were simply to have rendered both textures directly to the screen. Is this even possible? - Having now looked at the maths behind blending I'm starting to believe that it's not possible, but perhaps there is some trickery that can be used?

Thanks.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
james_lohr's picture

After a bit of research, it turns out that it is actually impossible to achieve this exactly; however, there is a work-around that gives almost perfect results.

The long and the short of it is that you need to:

1) Clear your FBO using:

GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f);

2) Render to your FBO using:

GL.BlendFuncSeparate(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha, BlendingFactorSrc.One, BlendingFactorDest.One);

3) Then, when rendering your FBO to the screen, use:

GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha);

If you want to understand why this works, then you can read about it in more detail here:

http://stackoverflow.com/questions/2171085/opengl-blending-with-previous...

So, my new question becomes:

Am I limiting myself by depending upon GL.BlendFuncSeparate?

james_lohr's picture

Oh, one other caveat:

When rendering your FBO texture to the screen, you need to take into account the blend function (GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDest.OneMinusSrcAlpha)) if you want to apply other effects.

For example, suppose you wish your FBO to be rendered to the screen with a gradient from transparent to opaque.

Using "normal" blending, you would simply set the opacity at the the transparent vertices to:

GL.Color4(1f,1f,1f,0f);

Unfortunately this doesn't work with your FBO texture because the blend function is using BlendingFactorSrc.One, and so even when the opacity is zero, the source colours will still be blended. In this particular case the solution is to use:

GL.Color4(0f,0f,0f,0f);

Or, more generally:

GL.Color4(a,a,a,a);

Where a is the alpha you wish to achieve.

...I have no idea what you would need to do to tint it to a particular colour.

james_lohr's picture

>>It can be done properly. Learn premultiplied alpha.

No, that is exactly what the code I have given does. It is not mathematically identical to conventional blending. That is to say that it does not exactly achieve my objective which I defined as:

The behaviour I would like (which I'm starting to realise may actually be impossible) is that, if I render two textures to my FBO and then render my FBO to the screen, it will look the same as if I were simply to have rendered both textures directly to the screen.

...though you can certainly argue that it is superior and therefore "proper" :)

blinsc's picture

I'll be perfectly honest, I did not test the snippets of code you posted, however, I had a similar issue where I was using an empty FBO in which I was rendering multiple textures layered on top of each other then saving the result as a TIFF. As a result, like you, none of the blending combinations worked how I wanted (I was trying to duplicate the Normal blending mode found in Photoshop where you are drawing two translucent layers on top of each other against a transparent background). However, after much searching (thanks to the guys on the GIMP project for steering me in the right direction), I found a document published by Adobe that explains their blending function (it was actually from a PDF specification but they use the same formulas in Photoshop). From there, it was just a matter of writing a simple shader to handle the blending. For this, I render everything to a texture, then copy that texture to another and use the copy as the input texture on the next pass. Of course, there is a tiny bit of a performance hit... but here's the shader (along with the formula) I used:

tex0 is the input texture, tex1 is the new layer

varying vec2 texCoord;

uniform float screen_w;
uniform float screen_h;
uniform sampler2D tex0;
uniform sampler2D tex1;

void main()
{
vec4 foreColor = texture2D(tex1, texCoord);
vec4 backColor = texture2D(tex0, gl_FragCoord.xy / vec2(screen_w, screen_h));

float As = foreColor.a;
float Ab = backColor.a;

vec3 Cs = foreColor.rgb;
vec3 Cb = backColor.rgb;

gl_FragColor.a = Ab + As - (Ab * As);
gl_FragColor.rgb = (1.0 - (As / gl_FragColor.a)) * Cb + (As / gl_FragColor.a) * ((1.0 - Ab) * Cs + Ab * Cs);
}

Here's the Adobe PDF I used for the formulas:

http://www.adobe.com/content/dam/Adobe/en/devnet/acrobat/pdfs/PDF32000_2008.pdf

Section 11.3.3 Basic Compositing Formula & Section 11.3.7.3 Result Shape and Opacity outline why the formulas work.

Using this, I am able to get results that are identical, to the pixel, to Photoshop, which in my case was exactly what I wanted. Hopefully it helps you out or at least gives you an idea to get what you want.