PDA

View Full Version : Transparency math, unexpected result!



Verdagon
02-06-2011, 03:46 AM
Hi all, I just found something interesting... so I'm trying to combine two colors and their alpha values to give me another value, with a standard "over" operation, like the http://upload.wikimedia.org/math/3/c/3/3c377902304f3e4c105ad360abbbc180.png in http://en.wikipedia.org/wiki/Alpha_compositing. (http://en.wikipedia.org/wiki/Alpha_compositing)

To make it happen, I use code like this:
result.rgb = over.rgb * over.a + under.rgb * under.a * (1.0 - over.a);
result.a = 1.0 - (1.0 - over.a) * (1.0 - under.a)

So, if I put the color (1.0, 1.0, 1.0, 0.5) over (0.0, 0.0, 0.0, 0.0), I would expect to get (1.0, 1.0, 1.0, 0.5) back, but running through the math, I get:


result.rgb = (1.0, 1.0, 1.0) * 0.5 + (0.0, 0.0, 0.0) * 0.0 * (1.0 - 0.5);
result.a = 1.0 - (1.0 - 0.5) * (1.0 - 0.0);
which gives me (0.5, 0.5, 0.5, 0.5).

If I lay a color over nothing, shouldn't I get the original color back? Shouldn't the result of this operation be (1.0, 1.0, 1.0, 0.5)?

Could someone please explain this to me? I have a feeling I'm missing something very basic in combining colors...

Thanks!

ZbuffeR
02-06-2011, 04:46 AM
As you multiply incoming color by its alpha, here it is 0.5, so this result is expected.

Alfonse Reinheart
02-06-2011, 04:57 AM
Since you didn't seem to read it the first time, I'll just quote what I said in your other thread (http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Main=56117&Number=2908 90#Post290890).


The equation you quoted is correct if you are only looking at two objects being blended together. When you have layers upon layers, you have to composite them one at a time. In doing so, each layer's color gets its own alpha multiplied into itself (Ca * alpha_a). So the destination color already had its alpha factored in; there's no need to do it twice.

Verdagon
02-06-2011, 05:18 AM
I actually did read it the first time, I just have a hard time wrapping my mind around this math... its very unintuitive to me. Odd that I can understand calculus but colors can get the best of me.

I read somewhere that if I discard the multiplying by the alpha, I have to do it beforehand, if I want correct blending. But they also said that when I do this, I lose the "order" of multiple layers. So I'm kind of nervous about taking anything out of the equation.

If I change this

result.rgb = over.rgb * over.a + under.rgb * under.a * (1.0 - over.a);
to this

result.rgb = over.rgb + under.rgb * under.a * (1.0 - over.a);
and I try (1, 1, 1, .5) with (1, 1, 1, .5), I get (1.25, 1.25, 1.25, .75), when I would expect (1, 1, 1, .75).

If I change it to

result.rgb = over.rgb * over.a + under.rgb * (1.0 - over.a) and try (1, 1, 1, .5) with (0, 0, 0, 0), I get (.5, .5, .5, .5), when I would expect (1, 1, 1, .5).

Isn't there an equation that can handle all these cases?

V-man
02-06-2011, 08:06 AM
You should use the mix() instead of this

result.rgb = over.rgb * over.a + under.rgb * under.a * (1.0 - over.a);

ZbuffeR
02-06-2011, 10:17 AM
Yeah if fact you are simply confused because of the under.a term, remove it and it will be easier.

Verdagon
02-06-2011, 03:02 PM
The glsl spec says that mix() is x*(1-a)+y*a; in my terms thats over.rgb*over.a + under.rgb*(1-over.a), so it's mine without the under.a term.

But wont this fail if I mix (1, 1, 1, .5) and (0, 0, 0, 0)? It would incorrectly give me (.5, .5, .5, .5).

Alfonse Reinheart
02-06-2011, 03:08 PM
But wont this fail if I mix (1, 1, 1, .5) and (0, 0, 0, 0)? It would incorrectly give me (.5, .5, .5, .5).

Two things:

1: If "under.a" was in the computation, you'd get the same thing. Which is part of the reason why you don't need it.

2: That is the correct value.

Verdagon
02-06-2011, 03:23 PM
But it isnt the correct value, I get a gray out of this. I wanted a half-transparent white.

Anyway, I did a bit of number-crunching, and I came up with this formula:

overpower = over.a
underpower = under.a * (1 - over.a)
overcontribution = overpower / (overpower + underpower)
undercontribution = underpower / (overpower + underpower)
result.rgb = over.rgb * overcontribution + under.rgb * undercontribution;

Photoshop seems to agree with me, because it gave me the same results for (1, 1, 1, .5) over (1, 0, 0, .5), which was (1, .67, .67, .75)

Alfonse Reinheart
02-06-2011, 04:31 PM
But it isnt the correct value, I get a gray out of this. I wanted a half-transparent white.

There's no such thing as "half-transparent white". Colors don't come with a transparency value.

If you have a non-premultiplied RGBA vector of (1, 1, 1, 0.5), the actual color of this is (0.5, 0.5, 0.5). That is, this is what you would see.

Similarly, while your blend function for "(1, 1, 1, .5) over (1, 0, 0, .5)" gives you "(1, .67, .67, .75)" back, the actual color is (1, 0.5, 0.5). Which is what you get if you do a straight linear interpolation between the source and destination based on the source alpha.

The reason OpenGL doesn't support what you're trying to do in it's blending stage is because OpenGL is designed primarily for rendering colors. Not for rendering transparency. The way you normally deal with transparency is as follows.

First, you clear the screen. Then you render everything that is opaque. After that, you render all transparent objects in order from farthest from the camera to nearest.

Therefore, the destination color in a blending operation, the "under" as you keep calling it, will refer to either the background color (the one you cleared the screen to), an opaque color, or the composite between the background or opaque opaque colors and some number of previously-rendered transparent colors.

The destination alpha doesn't mean anything. It doesn't get factored into the blend equation. This is why a linear interpolation based on the source ("over") alpha works.

If the destination color is the background color, then it's effectively the same as if its opaque. There's nothing "behind" the background to see, so there's no value there to have blended with.

If the destination color is an opaque color, then the original alpha for this rendering was 1.0. So the "under.a" is 1.0, which doesn't affect the equation. Linear interpolation gets you a proper color.

If the destination was a composite of an opaque/background color and some number of layered colors, it still doesn't matter. Because the composite of all of these layers is still fully opaque, and therefore has an effective alpha of 1.0.

To put it another way, you don't blend two transparent objects together. You're always blending a transparent object with an opaque object.

Verdagon
02-06-2011, 04:50 PM
Thanks for your help guys, I really appreciate the time you're putting into helping me; without your help I'd never have been able to get this far. You guys are my saviors, heh.

So, I see what you're saying; seemingly all of the time, I'll be blending a transparent object onto a completely opaque object, which means I can disregard the under's alpha value. But in my case, I can't.

In my program, I'm rendering a "scene texture" and a "lights texture". The scene texture will be all opaque. However, the lights texture will be mostly transparent, with patches of opaque bright colors (the lights).

However, I'm also doing some order-independent transparency. My aim is to be able to have half-transparent lights be able to stack on top of more half-transparent lights, if I have to. So for this lights texture, I have a background for which the alpha value is very important. I'm assembling and combining all these semitransparent colors with other semitransparent colors on the lights texture, and only later will I combine the resulting semitransparent colors with the opaque scene texture.

So for my purposes, I won't only be blending transparent colors onto opaque colors, I'm blending a bunch of transparent colors together first, and then later blending the result with opaque colors.

ZbuffeR
02-06-2011, 05:20 PM
blending a bunch of transparent colors together first, and then later blending the result with opaque colors
Instead, what about :
1) render opaque colors, no blending
2) use pure additive blending, as is common for lights, and which is order-independant :
glBlendFunc(GL_SRC_ALPHA,GL_ONE)
3) render all the lights