Front-to-back Blending

Hi,

I’ve been looking for a way to make transparency/translucency work in my program and after I decided that depth sorting would be way to slow to do on a per-frame basis, I stumbeled upon the Dual Depth Peeling algorithm which seems to be the right thing for me. However, I have problems understanding the equation used for front-to-back blending introduced in the document. In the section “Under Blending” (middle of the document) equations are given for back-to-front blending of two fragments with a background color.

But I don’t understand how to get the equations for front-to-back blending from these example equations. Also, I tried to apply the equations listed afterwards to the same set of fragments in front-to-back order, but got different results for the color values of the resulting fragment. I guess these equations should give the same results for both directions, so there’s something I didn’t understand.
So could someone please explain to me how to get from back-to-front equations to front-to-back ones?

Thanks in advance!

Considering the color channels of the source fragments are premultiplied by the alpha channel, you then have to set the blending function using GL_ONE_MINUS_DST_ALPHA for the source factor and GL_ONE for the destination factor.

The equations at the bottom of that page ARE the front-to-back equations.
Cdst = Adst (Asrc Csrc) + Cdst
Adst = (1-Asrc) Adst
The given OpenGL commands:
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE,GL_ZERO,GL_ONE_MINUS_SRC_ALPHA);
Produce the equations:
Cdst = Adst (Csrc) + Cdst
Adst = (1-Asrc) Adst
The missing multiply by Asrc is done in the fragment shader by muliplying the final RGB color of the fragment by the Alpha (transparency) of the fragment.

Your framebuffer pixelformat must have an Alpha channel (RGBA format).
You must set the framebuffer alphachannel to 1.0 before starting, and the color to black, ie. use glClearColor(0.0,0.0,0.0,1.0)
If you have overlapping opaque objects behind the transparent objects then you will need a depth pre-pass of the opaque objects to predetermine the frontmost opaque object.
Turn off depth writes during rendering.
Turn off the depth test for the transparent objects only.
The opaque objects are rendered last with the depth test turned on (so only the frontmost is rendered) and with Alpha=1.0 (or else change the blending to do Cdst = Adst Csrc + Cdst

I know that these are the equations I’m searching for, but I want to understand how to calculate them in general. For example, if I chose a different equation for back-to-front blending, how would I know a front-to-back equation with the same results? Simply swapping the source and destination factors doesn’t seem to work as I see from the example.

It’s not that I really wanted to choose a different blending equation, but I don’t like implementing algorithms I don’t understand.

Premultiplied alpha is what you’re looking for.

From Jim Blinn, “What Is a Pixel?,” IEEE Computer Graphics and Applications, vol. 25, no. 5, pp. 82-87, Sep./Oct. 2005:

A complete analysis of the compositing operation [4] [5] shows that it’s convenient to store pixel colors with the alpha value already premultiplied into the color values. The various compositing operations become more mathematically regular when expressed in terms of the premultiplied form. In particular, the alpha component resulting from an over operator has the same formulation as the RGB components. This form also simplifies association between multiple layers of objects since

(F over G) over B = F over (G over B )

This means that, when represented in this form, a stack of images can easily be composited either front to back, or back to front.

[4] T. Porter and T. Duff, “Compositing Digital Images,” Proc. Siggraph 84, ACM Press, 1984, pp. 253-259.
[5] J.F. Blinn, “Compositing, Part 1: Theory,” IEEE Computer Graphics and Applications, vol. 14, no. 5, 1994, pp. 83-87.

For more information on the theory, you might take a look at the article cited above. This article is also a good start.

Just write it down, this is like a sequence in mathematics :

  • blend(src,dst)
    becomes in front to back :
  • blend(blend(blend(blend(blend(a,b),c),d),e),f)

Replace blend() by the actual formula to understand what is happening.

Front-to-back algorithms can rarely be converted to back to front and vice-versa. Subtraction is not commutative for example. Multiplication may exhibit different precision issues depending on the order.

I’ll take a look at premultiplied alpha once I understood this reverse blending. After all I want to implement different ways of order independent transparency for different situations, so I’ll still try to implement dual depth peeling first.

I just hacked together a small snippet of C+±code to test these blending equations, but I still get different results, so I guess there’s still something I didn’t understand about this kind of blending:

struct Fragment
{
	Fragment(double c, double a) : c(c), a(a) {}
	double c, a; // c = color, a = alpha
};


// Back to front
Fragment blendB2F(Fragment src, Fragment dst)
{
	double newC = src.a*src.c + (1.0-src.a)*dst.c; // Asrc*Csrc + (1-Asrc)*Cdst
	double newA = 1.0;
	return Fragment(newC, newA);
}


// Front to back
Fragment blendF2B(Fragment src, Fragment dst)
{
	double newC = dst.a*(src.a*src.c) + dst.c; // Adst*(Asrc*Csrc) + Cdst
	double newA = (1.0-src.a)*dst.a; // (1-Asrc)*Adst
	return Fragment(newC, newA);
}


int main(int argc, char** argv)
{
	Fragment f1(0.3, 0.4);
	Fragment f2(0.6, 0.9);
	Fragment bg(0.0, 1.0);

	Fragment b2f = blendB2F(f1, blendB2F(f2, bg));
	Fragment f2b = blendF2B(blendF2B(f1, f2), bg);

	printf("B2F: (%f, %f)
", b2f.c, b2f.a);
	printf("F2B: (%f, %f)
", f2b.c, f2b.a);

	return 0;
}

Which gives me

B2F: (0.444000, 1.000000)
F2B: (0.382320, 0.460000)

So what am I doing wrong?

P.S.: Sorry if my questions are a bit basic. I’m still quite new to OpenGL and 3D graphics in general and trying to understand some of the concepts.

Sorry my pseudocode above is wrong, it should be :

blend(dst,src)
blend(blend(blend(blend(blend(a,b),c),d),e),f)

With ‘a’ being the background and ‘b,c,d,e,f’ the sequence of fragments, whatever the drawing order.

You are confusing the “background color” with the “clear color”, and the “Fragment f2b = blendF2B(blendF2B(f1, f2), bg)” statement is using the source as the destination buffer.
The “background color” is the color of the frontmost opaque fragment.
The “clear color” is the initial value of the destination buffer.
In B2F the background color can be the clear color if no opaque fragments have been written to that pixel.
But in F2B the clear color is the first thing written and so must be in FRONT of all the other fragments.
Hence you cant clear with anything other than (0.0,0.0,0.0,1.0) in F2B mode.

If we add a clear color to your program:
Fragment cc(0.0, 1.0);

And change the background to an actual color (But still A=1.0 so that its opaque):
Fragment bg(0.2, 1.0);

Then change the calls to:
Fragment b2f = blendB2F(f1, blendB2F(f2, blendB2F(bg, cc)));
Fragment f2b = blendF2B(bg, blendF2B(f2, blendF2B(f1, cc)));

Note that cc is now always the initial dst and the fragments are processed in reverse order, bg-f2-f1 for B2F and f1-f2-bg for F2B.
blendB2F(bg, cc) just replaces cc with bg because its opaque, so dst=(0.2, 1.0)
blendB2F(f2, dst) = (0.9*0.6)+((1-0.9)0.2) = 0.56
blendB2F(f1, dst) = (0.4
0.3)+((1-0.4)*0.56) = 0.456

Now for F2B;
Note that the value in dst.a has a different meaning to src.a
src.a is how opaque the fragment is.
0.0 = completely transparent
1.0 = fully opaque
dst.a is the product of the inverse of the transparency of all of the fragments that have been written to the pixel on the screen.
0.0 = An opaque fragment was written so nothing behind can be seen.
1.0 = Nothing visible has been written to this pixel yet.

blendF2B(f1, cc)
newC = (1.00.40.3)+0.0 = 0.12 which is just the color of f1 multiplied by its own alpha.
newA = (1-0.4)*1.0 = 0.6

blendF2B(f2, dst)
newC = (0.60.90.6)+0.12 = 0.444
newA = (1-0.9)*0.6 = 0.06

blendF2B(bg, dst)
newC = (0.061.00.2)+0.444 = 0.456
newA = (1-1)*0.06 = 0.0

So using both methods we get a result of 0.456

OK, I think I understand. So for B2F you don’t actually need a clear color because the opaque background is processed first, and if you blend the background over the clear color, the clear color is just replaced by it (assuming the background is fully opaque). For F2B you need the clear color because the result of blending the first fragment over the clear color is not the same as directly using the first fragment: WITH a clear color, the first fragment written would be multiplied by it’s alpha (src.a*src.c) and it’s alpha would be inverted afterwards (1.0-src.a).

So, just in theory to be sure I understood everything, if I premultiplied the fragment colors by their alpha (src.a*src.c becomes src.c), I would just need to invert the first fragments alpha and get the same results without a clear color?
Well, I’ve tested it in my program and at least for my examples it seems to work.

I’ve also taken a look at premultiplied alpha for transparency. As I understand, premultiplied alpha is associative, but not commutative, so I would still need some kind of depth sorting. But I’ll try using premultiplied alpha instead of two separate blending equations for B2F/F2B.

Thanks for your help!

So for B2F you don’t actually need a clear color because the opaque background is processed first
Yes, if you are sure you will be drawing to every pixel on the screen; eg. an indoor scene, or a skybox that fills the background.
Of course you still need to clear the depth buffer if you are using one, and you should be for the opaque objects because it saves the effort of sorting them, and you dont have to worry about cases where sorting is difficult, as explained here

if I premultiplied the fragment colors by their alpha (src.asrc.c becomes src.c), I would just need to invert the first fragments alpha and get the same results without a clear color?
Yes, the F2B clear color just multiples by 1 and adds 0, so the equations simplify to:
newC = 1
AsrcCsrc + 0 = AsrcCsrc
newA = (1-Asrc)*1 = 1-Asrc

Changing the blend equation for the first transparent layer to avoid clearing the color buffer wont work in practice where different parts of the objects overlap each other, as different parts of the same triangle could be in different layers.
It would only work if you had one large transparent object in the foreground that every other transparent object was always behind.

The only reason that src.asrc.c is done in the fragment shader is because the C = dst.a(src.a*src.c) + dst.c equation is too complex and cant be done with any available glBlendEquation/glBlendFunc combination.

I’ve also taken a look at premultiplied alpha for transparency. As I understand, premultiplied alpha is associative, but not commutative, so I would still need some kind of depth sorting. But I’ll try using premultiplied alpha instead of two separate blending equations for B2F/F2B.
There is a non-sorted technique where you use pre-multiplied alpha and just add the fragment colors together, but this gives an inaccurate result and can be completely wrong when objects with different alpha values are placed in a particular order.