DirectX10 Tutorial 6: Blending and Alpha Blending


It’s been ages since my last DirectX 10 tutorial and I apologize, I’ve been buried under a ton of work and haven’t had much free time lately. This is going to be a very short tutorial on pretty much the final stage in the rendering pipeline: color blending. If you recall, each frame displayed on the screen is simply a bitmap that gets displayed and updated multiple times a second. This bitmap is called the frame buffer, now the frame buffer is technically the image we see at any given point and the back buffer (assuming you are double buffering) is what you actually draw to (referred to as your render target) and only once you finish drawing do you display the back buffer to the screen by swapping the frame buffer and the back buffer by using the Present member of the DX10 swapchain class.

Now think back to the depth testing tutorial where we displayed that cube and had to enable depth testing for it to render properly. Now a  cube is made up of 6 sides with 2 faces per side, so that is 12 triangles we have to draw for each cube. The graphical API draws one triangle at a time to the back buffer, and used the depth buffer to check whether it can overwrite a pixel in the back buffer if the new pixel to be drawn is in front of it. If this test passes then the API is given permission to overwrite that pixel’s value but its not as simple as that!

There are cases that you wish to make use of the previous pixel when drawing the new pixel, the most common reason for this is to draw transparent objects. If a triangle is semi transparent then you need to use the color of the object behind it to draw the new object. For example: if we have a red quad that takes up the entire screen, then we draw a semi-transparent smaller blue quad on top of it, the blue quad should now look purple since the object behind it is purple. Simply put blending combines the pixel color in the render target with the new pixel color to be drawn at that position.

The Blending Equation

How does it combine the two pixel colors? It uses a very simple equation:

Final color =

( src pixel color * src pixel blend factor )

some blending operator (+/-/*)

(  dest pixel color *  dest pixel blend factor )

In graphics terminology the new pixel to be drawn is termed the source (SRC) pixel and the backbuffer pixel is the destination (DEST) pixel. The blending operation is a basic mathematical operation like addition or subtraction, and the two blend factors are user set.

Before I carry on with the blending equation and so on, lets just quickly cover the topic of transparency. As you may have noticed so far colors have 4 components:  red, green, blue and an alpha component, the first three are obvious but what is the alpha value? Well that value defines how opaque (solid) or transparent a color is. A value of 1 is completely solid and a value of 0 is completely transparent. The reason that the range is from 0 to 1 should be quite obvious, to get the final color for every pixel in the image we multiply its color values by the alpha channel, so if the alpha is 1 then the color value remains unchanged and the lower the alpha the duller the color will be. So if I had a rather dirty window and I represented it using a colored quad, a good alpha value is 0.3, its mostly see through but not completely.

Now coming back to the blending equation, here is an example from the DirectX SDK tutorials:

Blending Example

In this case we are blending two textures together (think of it as if we are drawing one textured quad in front of another textured quad. The blending equation is now telling us to multiple each new pixel with its alpha value and then add it to the destination pixels color value. The combination of the image with its alpha value results in around a quarter of the image being fully transparent when combined with the destination value, the result is pretty self explanatory.

Direct3D Blending

The Direct3D 10 blending Equation as a Flow Chart

The above image represents the direct3d10 blending operation in flow chart form, as directX is always performed on the color values,  Source 1 refers to the data source for source pixel blending factor and source 2 is the data source for  destination pixel blending factor.  How Direct3D does blending is as follows: the source pixel (Src) is always the fragment coming from the pixel shader’s output and the dest pixel is always the pixel from the current active render target. Remember: the Source 1 and Source 2 in the flow chart are actually the blending factors’ data sources.

So how do we define this blending equation in code? Well its extremely simple, direct3D has a blend state structure: ID3D10BlendState which is a standalone structure which describes the state of the flow chart. To create this blend state, we need to first fill out a D3D10_BLEND_DESC structure which describes the various options of the blend state. This again gets confusing if you look at the SDK docs as they say this about the D3D10_BLEND_DESC structure:

struct D3D10_BLEND_DESC
{
    BOOL AlphaToCoverageEnable;
    BOOL BlendEnable[8];
    D3D10_BLEND SrcBlend;
    D3D10_BLEND DestBlend;
    D3D10_BLEND_OP BlendOp;
    D3D10_BLEND SrcBlendAlpha;
    D3D10_BLEND DestBlendAlpha;
    D3D10_BLEND_OP BlendOpAlpha;
    UINT8 RenderTargetWriteMask[8];
}
  • SrcBlend -This blend option specifies the source blending factor data source and includes an optional pre-blend operation.
  • DestBlend – This blend option specifies the destination blending factor data source and includes an optional pre-blend operation.
  • SrcBlendAlpha – This blend option specifies the source alpha blending factor data source and includes an optional pre-blend operation. Blend options that end in _COLOR are not allowed.
  • DestBlendAlpha – This blend option specifies the destination alpha  blending factor data source and includes an optional pre-blend operation. Blend options that end in _COLOR are not allowed.

There are two more parameters (SrcBlendAlpha & DestBlendAlpha) which define  the blending factors for the source and destination alpha channels. The blending of the alpha values is done separately from the blending of color values. Why is this important since your final image is a flat bitmap where you don’t need any transparency?

Well, you wont always be rendering to the backbuffer, for example  you might be procedurally generating a texture which may need transparency and so your render target would be a texture and not the backbuffer. Lets say for example you want your scene to have dynamic clouds, so you use a random noise generator (eg.  perlin noise function) to render some random white blobs with onto a empty texture. Obviously clouds have varying densities so you want the cloud to be thinner/semi transparent in some areas and thicker/solid in others, now you need different alpha values for the different sections. Now what happens when you draw a new clouds over an already drawn cloud? The clouds will overlap and so the two clouds’ alpha values need to be blended together and so this is where the whole  alpha channel blending (dont get this confused with alpha blending) comes in useful.

So moving along, how do we create a custom blend state?
ID3D10BlendState* g_pBlendState = NULL;

D3D10_BLEND_DESC BlendState;
ZeroMemory(&BlendState, sizeof(D3D10_BLEND_DESC));

BlendState.BlendEnable[0] = TRUE;
BlendState.SrcBlend = D3D10_BLEND_SRC_ALPHA;
BlendState.DestBlend = D3D10_BLEND_INV_SRC_ALPHA;
BlendState.BlendOp = D3D10_BLEND_OP_ADD;
BlendState.SrcBlendAlpha = D3D10_BLEND_ZERO;
BlendState.DestBlendAlpha = D3D10_BLEND_ZERO;
BlendState.BlendOpAlpha = D3D10_BLEND_OP_ADD;
BlendState.RenderTargetWriteMask[0] = D3D10_COLOR_WRITE_ENABLE_ALL;

pd3dDevice->CreateBlendState(&BlendState, &g_pBlendState);

 

So now we’ve created our blendState but how do we use it? It really really simple, we just use the OMSetBlendState function of our D3D device. This sets up the blending stage to the state that we have described in our blend state object.

pd3dDevice->OMSetBlendState(g_pBlendState, 0, 0xffffffff);

The first parameter is obviously the blend state, the second is an array of blend factors to use in the special case when we want to do per color channel blending, while the third parameter is a multisample mask defining which bits can be written to during blending using multisample render targets. (basically ignore it for now and set it to all f’s)

Lazy Man’s Guide to Blend States

Now that wasn’t exactly a lot of work to set up the blending state, but for those of you that are even lazier, you can set the blend state directly in your HLSL FX file. The first step in doing this is to create a blendState Struct and fill it out (note the lack of the D3D10_BLEND_ and D3D10_BLEND_OP_ prefixes on the blend factors and ops):

BlendState SrcAlphaBlendingAdd
{
    BlendEnable[0] = TRUE;
    SrcBlend = SRC_ALPHA;
    DestBlend = INV_SRC_ALPHA;
    BlendOp = ADD;
    SrcBlendAlpha = ZERO;
    DestBlendAlpha = ZERO;
    BlendOpAlpha = ADD;
    RenderTargetWriteMask[0] = 0x0F;
};

Also note that we set the RenderTargetWriteMask to 0x0F, this is the same numerical value as D3D10_COLOR_WRITE_ENABLE_ALL. To set the pipeline to use that state, we need to bind it to a pass in a technique by doing the following (take note that the parameters for the SetBlendState function are the same as for the OMSetBlendState function):

technique10 render
{
    pass P0
    {
        SetVertexShader( CompileShader( vs_4_0, VS() ) );
        SetGeometryShader( NULL );
        SetPixelShader( CompileShader( ps_4_0, PS() ) );

        SetBlendState( SrcAlphaBlendingAdd, float4( 0.0f, 0.0f, 0.0f, 0.0f ), 0xFFFFFFFF );
    }
}

And voila, that’s all there is to it, its a lot simpler and will make your application code look a lot neater. It also makes more sense to put it in the FX file, since different effects will have different blending states and it will be a lot of effort to bind blending states to specific effects/techniques in your application code.

Alpha Blending

And that’s all there is to blending but we’re not done yet, we still need to cover alpha blending. Alpha blending is a blending technique that allows for the combination of two colors allowing for transparency effects. The alpha blending equation is as follows:

final color = src color * src alpha + dest color * (1-src alpha)

What this does is linearly interpolate between the two colors according to the alpha value. If a src pixel has an alpha of 0.8 it contributes 80% of the final color while the destination pixel only contribute 20% of the final color of the new pixel color. This obviously means the lower the source pixel alpha the larger the contribution of the destination pixel.

I’ve created a test application that draws two textured quads, one smaller than the other, with the smaller quad in front of the larger one as seen in the following picture:

No Alpha Blending

Now the smaller quad is textured with a PNG that has an alpha channel but it has no effect since we are using the default blend state (simple color overwrite). In the next screenshot I’ve set my pixel shader to just draw the alpha values of both textures instead of the color values (simply by modifying the return color of the pixel shader by setting the red channel to the alpha value, the other channels to 0 and finally the alpha channel to 1) the result of that is:

The alpha values of the textures

Now the black values are areas where the alpha value is set to 0, I.E. fully transparent, so if we set the blend state to the alpha blending equation described above (and undo our pixel shader hack), the result is:

Alpha Blending

And BOOM! there we have it: Alpha Blending made simple. :)

Example Source Code

About these ads

About Bobby
I'm a programmer at Ubisoft. My work interests include Animation and Artificial Intelligence. All opinions are my own!

11 Responses to DirectX10 Tutorial 6: Blending and Alpha Blending

  1. bobobobo says:

    Where’d you find that D3D sample picture?

    • Bobby says:

      cant really remember to be honest, I think it was from a tutorial on how to create alpha channels in photoshop or something like that…

  2. Sam says:

    Nice tutorial. Having a bit of trouble though. Alpha blending works fine when there is nothing behind it (i.e. on solid colour background), but when I put another texture behind it, the transparency stops working and where it should be transparent is replaced by the colour of the background.

    • Myself says:

      Hy, im having exactly the same problem. With several sprites (round ones for example, with a png over a quad) they overlap their transparents areas with the background color. Does it have to be with the depth testing or with the blend state? Thanks in advance, this are one OF the best tutorials I,ve found!

  3. Garth Dawson says:

    Hi,

    If I change

    int windowWidth = 800;
    int windowHeight = 600;

    to

    int windowWidth = 1024;
    int windowHeight = 768;

    when I build and run and then exit, the program seems to exit, but it is still running and must be closed using task manager. Could you please help me understand why that is happening?

    Thanks, Garth

  4. dminik says:

    Hello. Thank you for this tutorial. Could you, please, recommend any books, where equations, you used, could be found? Or some explanation? I can’t just copy stuff=) I would like to understand, what I’m doing exactly.

  5. Eugene says:

    Thanks for this tutorial. It’s 2013 and it’s still useful.

  6. Nice Tut, thanks. Seemingly it’s still helping in 2014!
    It’s a bit euphemistic though to call the DX10 code neat, since in DX9 i could bind blend modes conditionally using the ternary operator and also shorten the code using the component blend mode’s integer representation, see:

    technique TBlendSources
    {
    pass p0A {
    AlphaBlendEnable = true;
    VertexShader = compile vs_1_0 VS();
    PixelShader = compile ps_3_0 PS_A();
    SrcBlend = ab==0 ? 5 : 9;
    DestBlend = ab==0 ? 6 : 4;
    }

    pass p1B {
    AlphaBlendEnable = true;
    VertexShader = compile vs_1_0 VS();
    PixelShader = compile ps_3_0 PS_A();
    SrcBlend = ab==1 ? 2 : 9;
    DestBlend = ab==1 ? 6 : 4;
    }
    }

    Now guess what i fear how this will end up looking in DX10!

  7. I couldn’t refrain from commenting. Exceptionally
    well written!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: