Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

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

Discussion in 'Shaders' started by Deleted User, Feb 6, 2013.

  1. Deleted User

    Deleted User

    Guest

    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
     
    Last edited by a moderator: Feb 6, 2013
    Lyrcaxis likes this.
  2. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    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.
     
  3. Lulucifer

    Lulucifer

    Joined:
    Jul 8, 2012
    Posts:
    358
    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
     
  4. Deleted User

    Deleted User

    Guest

    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.
     
    Lyrcaxis and jersonlatorre like this.
  5. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    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.
     
    Lyrcaxis likes this.
  6. Deleted User

    Deleted User

    Guest

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

    Thanks
     
  7. Lulucifer

    Lulucifer

    Joined:
    Jul 8, 2012
    Posts:
    358
    your formula is pretty cool ,I like it:)
     
  8. AntonPetrov

    AntonPetrov

    Joined:
    Dec 27, 2013
    Posts:
    63
    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
     
    smash-ter likes this.
  9. jersonlatorre

    jersonlatorre

    Joined:
    Jul 20, 2015
    Posts:
    1
    Best answer! it works for me :)
     
  10. quizcanners

    quizcanners

    Joined:
    Feb 6, 2015
    Posts:
    109
    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):
    1. [code=CSharp]float4 LerpTransparent(float4 col1, float4 col2, float transition)
    2. {
    3.     float4 col;
    4.     col.rgb = lerp(col1.rgb * col1.a, col2.rgb * col2.a, transition);
    5.     col.a = lerp(col1.a, col2.a, transition);
    6.     col.rgb /= col.a + 0.001;
    7.     return col;
    8. }
    9.  
    10. float4 BlendTransparent(float4 col1, float4 col2)
    11. {
    12.     float4 col;
    13.     col.rgb = lerp(col1.rgb * col1.a, col2.rgb, col2.a);
    14.     col.a = lerp(col1.a, 1, col2.a);
    15.     col.rgb /= col.a + 0.001;
    16.     return col;
    17. }
     
  11. ancientfear

    ancientfear

    Joined:
    Feb 28, 2017
    Posts:
    2
    Thanks a lot
    Thanks a lot!
     
  12. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,350