Adding Transparency to a Shader

Introduction

Occasionally, you might find it useful to add transparency to a shader that you are working with. By this, I mean creating your shader so that objects that are drawn are partly see-through. This is actually extremely easy to do, and you can quickly add it into any shader. In this tutorial, we will look at how to go about this. Below is a simple example with transparency:

screenshot1.png

Adding Transparency to a Shader

Remember that when you define a technique and its passes, you can specify certain render states. (You can actually specify these render states from inside of an XNA game as well.) We will just need to set a few render states to get transparency going. So in your shader file, where you define your technique and passes, let's add a few lines.

Let's say your technique is currently define like this:

technique Technique1
{
    pass Pass1
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

To make this pass in this technique utilize alpha blending, we will add three lines to make it like this:

technique Technique1
{
    pass Pass1
    {
        AlphaBlendEnable = TRUE;
        DestBlend = INVSRCALPHA;
        SrcBlend = SRCALPHA;
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

The first line indicates that in this pass, we want to enable alpha blending. Now, there are lots of ways that we could do blending, and we will need to specify exactly how to do what we want, which is what the next two lines do. Basically, with any form of blending, including the transparency that we want to do, there are two colors: the color that is already there (the background color, or destination color) and the color that we just created (sometimes referred to as the source color). Normally, we just overwrite the destination color with the source color and call it good. However, that's not going to cut it for transparency.

So instead, we have to specify how the two colors, source and destination, should be combined together to produce the resulting color. In our case, we want the source color to be multiplied by the alpha value of the color, which, in this case, represents the transparency, and the destination color will be multiplied by one minus the alpha value of the color we just created. So this is how we get the two lines: DestBlend = INVSRCALPHA;, which means the destination color will be one minus the sources' alpha value, and SrcBlend = SRCALPHA;, which means the source value will be multiplied by the source's (its own) alpha value. The results are added together and we get transparency! Now, these aren't the only choices. There's plenty of other ones, that give various blending techniques, so feel free to play around with them to get other effects. Here is a list of the available blending functions, which do various things:

  • ZERO
  • ONE
  • SRC_COLOR
  • SRC_ALPHA
  • DEST_ALPHA
  • DEST_COLOR
  • INV_SRC_COLOR
  • INV_SRC_ALPHA
  • INV_DEST_ALPHA
  • INV_DEST_COLOR
  • SRC_ALPHA_SAT
  • BLEND_FACTOR
  • SRC1_COLOR
  • SRC1_ALPHA
  • INV_BLEND_FACTOR
  • INV_SRC1_COLOR
  • INV_SRC1_ALPHA

I usually create a parameter in my shader called Transparency, which is of type float, which can be set in your XNA game. Usually I'll have the default value be 0.5 or something. So I'd have a line in my parameters that looks like float Transparency = 0.5;. In your pixel shader it is easy to do all of the calculations that you originally did, store that to a temporary float4 variable, set the alpha value to Transparency, and return that value. So something like this:

float4 color = saturate(textureColor * (input.Color) + AmbientColor * AmbientIntensity + specular);
color.a = Transparency;
return color;

It is extremely easy to add this into any shader, but I've included a complete example, which is our texture shader from an earlier tutorial, with the transparency added in.

float4x4 World;
float4x4 View;
float4x4 Projection;
float4x4 WorldInverseTranspose;
 
float4 AmbientColor = float4(1, 1, 1, 1);
float AmbientIntensity = 0.1;
 
float3 DiffuseLightDirection = float3(1, 0, 0);
float4 DiffuseColor = float4(1, 1, 1, 1);
float DiffuseIntensity = 1.0;
 
float Shininess = 200;
float4 SpecularColor = float4(1, 1, 1, 1);
float SpecularIntensity = 1;
float3 ViewVector = float3(1, 0, 0);
 
float Transparency = 0.5;
 
texture ModelTexture;
sampler2D textureSampler = sampler_state {
    Texture = (ModelTexture);
    MinFilter = Linear;
    MagFilter = Linear;
    AddressU = Clamp;
    AddressV = Clamp;
};
 
struct VertexShaderInput
{
    float4 Position : POSITION0;
    float4 Normal : NORMAL0;
    float2 TextureCoordinate : TEXCOORD0;
};
 
struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float4 Color : COLOR0;
    float3 Normal : TEXCOORD0;
    float2 TextureCoordinate : TEXCOORD1;
};
 
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
 
    float4 worldPosition = mul(input.Position, World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);
 
    float4 normal = normalize(mul(input.Normal, WorldInverseTranspose));
    float lightIntensity = dot(normal, DiffuseLightDirection);
    output.Color = saturate(DiffuseColor * DiffuseIntensity * lightIntensity);
 
    output.Normal = normal;
 
    output.TextureCoordinate = input.TextureCoordinate;
    return output;
}
 
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    float3 light = normalize(DiffuseLightDirection);
    float3 normal = normalize(input.Normal);
    float3 r = normalize(2 * dot(light, normal) * normal - light);
    float3 v = normalize(mul(normalize(ViewVector), World));
    float dotProduct = dot(r, v);
 
    float4 specular = SpecularIntensity * SpecularColor * max(pow(dotProduct, Shininess), 0) * length(input.Color);
 
    float4 textureColor = tex2D(textureSampler, input.TextureCoordinate);
    textureColor.a = 1;
 
    float4 color = saturate(textureColor * (input.Color) + AmbientColor * AmbientIntensity + specular);
    color.a = Transparency;
    return color;
}
 
technique Textured
{
    pass Pass1
    {
        AlphaBlendEnable = TRUE;
        DestBlend = INVSRCALPHA;
        SrcBlend = SRCALPHA;
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

Also, just to give you an idea of how to use this, you could create a shader that has multiple passes in it. In the first pass, render the entire scene as you usually do, but anything that has any transparency (alpha value < 1), set it to 0, so that it doesn't get rendered at all. In the second pass, do the same thing, but this time, anything that is completely opaque (alpha value == 1), set the value to 0, and render everything else with their proper alpha values. With this approach, all of the solid objects will be drawn first, and anything that is transparent will show the solid objects through it.

There's really a ton of different things that you can do with transparency and blending, so feel free to play around. You can get some very interesting results.


Troubleshooting.png Having problems with this tutorial? Try the troubleshooting page!