what is the formula for blending two transparent textures in a shader?

I need to blend three transparent textures in one shader to be drawn in one call, so that the result is the same as if I drew each layer one by one with SrcAlpha OneMinusSrcAlpha.

Can someone help me out with the formula?
R = ?
G = ?
B = ?
A = ?

Thanks

1 Like

Traditional complementary alpha blending (like what you get from the factors you mentioned above) is just a linear interpolation between foreground and background by foreground alpha:

output = lerp(foreground.rgb, background.rgb, foreground.a)

So at a high level, what you want is:

output = lerp(texture1.rgb, background.rgb, texture1.a)
output = lerp(texture2.rgb, output.rgb, texture2.a)
output = lerp(texture3.rgb, output.rgb, texture3.a)

Unfortunately, you can’t apply the math so straightforwardly in a shader without use a GrabPass. The issue is that all your calculations depend on background.rgb, which you don’t have access to until the blending stage.

Luckily, there is a compositing trick called premultiplied alpha which allows us to do everything we want without knowing the background colour until blending time. Premultiplied alpha textures have their RGB channels already multiplied by their alpha before being sent to the shader. With premultiplied alpha, your standard blending equation becomes:

output = background.rgb*(1 - foreground.a) + foreground.rgb

Note that in an actual single-texture shader, you would use Blend One OneMinusSrcAlpha to accomplish both the multiplication and addition.

Composing this with three textures results in:

output = ((background.rgb*(1 - texture1.a) + texture1.rgb)*(1 - texture2.a) + texture2.rgb)*(1 - texture3.a) + texture3.rgb

We can expand this to:

output = background.rgb*(1 - texture1.a)(1 - texture2.a)(1 - texture3.a)

  • texture1.rgb*(1 - texture2.a)*(1 - texture3.a)
  • texture2.rgb*(1 - texture3.a)
  • texture3.rgb

This neatly isolates the background colour and its factors, allowing you to add them afterward by putting the destination factors into your fragment shader’s alpha output and using the inverted premultiplied alpha blend coefficients of One SrcAlpha.

If you look back at the definition of premultiplied alpha, you might also notice that it isn’t strictly necessary in order to achieve the correct result. You can expand each of the textureN.rgb terms and do the alpha multiplication in the shader.

6 Likes

As far as i know.when the blend operate comes to alpha ,SrcAlpha,SrcColor refers the same thing ,the alpha of the Src, and like so ,Alpha and Color is the same thing for alpha operation

Thanks for tha answers! but in the mean time I have been digging and testing, and found a formula that seems to work.

I take the bottom layer image I want to pre-compose, and use it as the destination. Then I blend the rest of the layers on top of it one at a time using the above formula. When I draw the final result, everything seems to look right when adjusting the alpha of the three textures.

2 Likes

That’s a pretty expensive way of getting a slightly less correct result. Pre-multiplying your alpha and using the formula I showed is much cheaper (no divisions or conditionals) and works perfectly with filtering and mipmaps.

1 Like

True that divs are evil… I will take another look at your solution tomorrow.

Thanks

your formula is pretty cool ,I like it:)

Just a penny to the @Daniel-Brauer 's great answer.

In case you have a vertex color and would like to modulate output of a shader.

We can rewrite this equation:

output = background.rgb*(1 - texture1.a)(1 - texture2.a)(1 - texture3.a)

  • texture1.rgb*(1 - texture2.a)*(1 - texture3.a)
  • texture2.rgb*(1 - texture3.a)
  • texture3.rgb;

as a common alpha-blending equation with alpha premultiplied in fragment.rgb:

output = background.rgb * (1 - fragment.a) + fragment.rgb;

where:

fragment.rgb = texture1.rgb*(1 - texture2.a)*(1 - texture3.a)

  • texture2.rgb*(1 - texture3.a)
  • texture3.rgb;

fragment.a = 1 - (1 - texture1.a)(1 - texture2.a)(1 - texture3.a);

so to introduce modulation by vertex color we should write:

fragment.rgb = (texture1.rgb*(1 - texture2.a)*(1 - texture3.a)

  • texture2.rgb*(1 - texture3.a)
  • texture3.rgb) * color.rgb * color.a; // premultiplied alpha, remember?

fragment.a = (1 - (1 - texture1.a)(1 - texture2.a)(1 - texture3.a)) * color.a;

and the alpha blending equation will be:

BlendOp = ADD
SrcBlend = ONE (because of premultiplied alpha everywhere)
DestBlend = ONE_MINUS_SRC_ALPHA

1 Like

Best answer! it works for me :slight_smile:

So I was trying to do this and came up with a method. I wanted to see if there was an easier/standard/optimal way to blend transparent layers in the shader and get the correct Alpha on the exit. From the look, it may be doing the same as the formula above.

I’ll post it, they seem to work as well, maybe it will be useful to someone. I’m very confident in Transparent Lerp, as it went through rigorous testing. As for the blend, looks correct I think:

[code=CSharp]float4 LerpTransparent(float4 col1, float4 col2, float transition)
{
    float4 col;
    col.rgb = lerp(col1.rgb * col1.a, col2.rgb * col2.a, transition);
    col.a = lerp(col1.a, col2.a, transition);
    col.rgb /= col.a + 0.001;
    return col;
}

float4 BlendTransparent(float4 col1, float4 col2)
{
    float4 col;
    col.rgb = lerp(col1.rgb * col1.a, col2.rgb, col2.a);
    col.a = lerp(col1.a, 1, col2.a);
    col.rgb /= col.a + 0.001;
    return col;
}

Thanks a lot

Thanks a lot!

here is cool list of shader blend functions too,