Saturday, May 30, 2009

Advanced Alpha Blending with HLSL

Alpha blending is the process of combining an overlying image with an underlying one to create the appearance of partial transparency. Most of graphics programs (like Photoshop, Corel Paint Shop Pro and so on) can blend the layers. In addition these programs provide several advanced blend modes where you can manipulate color values (for example, result of a blending is multiplication of layer’s colors). I know two ways to perform alpha blending with modern graphics hardware. You can use fixed pipeline’s functions and set proper alpha blending modes (see, http://msdn.microsoft.com/en-us/library/bb172252(VS.85).aspx) and maybe it would be enough for you. Using these render states you can customize the fixed blending equation:

Result = overlying * (SrcBlend) (BlendOp)
        underlying * (DestBlend)
Another way is to use programmable pipeline i.e. shaders. This way provides full freedom. In this article I will describe advanced alpha blending with shaders. In simple case (when underlying layer has alpha = 1, no transparency) to perform blending (in terms of HLSL):
rgb = overlying.rgb * overlying.a + 
        (1 - overlying.a) * underlying.rgb;
But what if underlying layer is partially transparent as well? Our equation must be
float3 rgb = overlying.rgb * overlying.a + 
(1 - overlying.a) * underlying.a * underlying.rgb;
Another question is how to compute result alpha. The question is:
float alpha = underlying.a + 
            (1-underlying.a)*overlying.a;
Maybe you hear about ‘premultiplied alpha’. Premultiplied alpha allows to simplify these equation to:
// Here all rgb = rgb * a;
float3 blended = overlying.rgb + 
       ((1-overlying.a)*underlying.rgb);
To get premultiplied format you can turn you graphics pipeline in appropriate mode or just multiply in begin of shader and divide before return. I have written this function:
// Performs "over"-type blending 
// (colors must be premultiplied by alpha)
float4 blend(float4 overlying, float4 underlying)
{
 float3 blended = overlying.rgb + 
        ((1-overlying.a)*underlying.rgb); 
 float alpha = underlying.a + 
         (1-underlying.a)*overlying.a;
 return float4(blended, alpha);
}
As you can see equations for RGB and A are the same and you can perform blending in just one line! But what about different blend modes like ‘multiply’, ‘darken’, ‘lighten’ and so on? Here I do not have definite answer. I use the following function:
// Performs advanced "over"-type blending
// (blended is the result of advanced blending 
// (for ex, overlying.rgb * underlying.rgb))
float4 blend(float3 blended, 
             float4 overlying, 
             float4 underlying)
{
 // Special case
 if(overlying.a == 0) blended = overlying.rgb;
 if(underlying.a == 0) blended = overlying.rgb;
 
 return blend(float4(blended, overlying.a), 
              underlying);
}
Graphics applications like Adobe Photoshop or Corel Paint Shop uses more comlex equations (see my further post).

No comments: