Pixel Shaders

Pixel shaders are a compiled set of software instructions that calculate the color of the pixels and executed on the GPU.  The instructions are written in HLSL (High Level Shader Language).  Pixel Shaders allow for the development of custom effects. All elements, that derive from UIElement, have the Effect property that allows the element to render with the connected pixel shader.

In this tutorial, we will show you how to use the built-in shaders and develop custom effects for your applications.

 

Preparing for Pixel Shader Development

You will need the latest version of DirectX SDK to compile .fx files.  This tutorial uses the November 2008 DirectX SDK, which can be downloaded from Microsoft's Download Site.  After installing the SDK, you will need to add the directory path (C:\Program Files (x86)\Microsoft DirectX SDK (November 2008)\Utilities\bin\x86) to the PATH environment variable.

To simplify the process of developing shaders, I have developed a project that runs a batch script to compile the shaders to use in the application.  The project can be downloaded here.

 

Built-in Pixel Shaders

Silverlight 3 has two built-in pixel shaders, Blur and Drop Shadow, that can be applied to XAML elements.  Both effects can be found in the System.Windows.Media.Effects library.

The following code snippet shows the XAML approach to add an effect to an image.

<Image x:Name="image" Source="Images/Waterfall.jpg" Stretch="Uniform" CacheMode="BitmapCache" >
    <Image.Effect>
        <BlurEffect />
    </Image.Effect>
</Image>

 

The following code snippet shows the code approach.

image.Effect = new BlurEffect();

 

The BlurEffect class uses the Radius property to render the blur strength. The default value of the radius is 5.

Blur Effect
<BlurEffect Radius="10" />

 

The DropShadowEffect class contains more properties to customize the drop shadow's strength and color.

Drop Shadow Effect
<DropShadowEffect BlurRadius="10" ShadowDepth="10" />

 

Custom Pixel Shaders

  1. Open the PixelShaderStart project.  The sample project has the basis of developing pixel shaders.  You must have the DirectX SDK installed and the environment variable path set to the DirectX SDK's bin directory.  The project uses a build event to run the compile.cmd script, which compiles the included .fx files.

    Project

  2. Open the compile.cmd file. This batch script compiles the .fx files using the fxc compiler.  The file currently has a single shader (Constant).  To compile additional shaders, copy and paste the first line and make changes to the line with the new shader file.
    fxc /T ps_2_0 /E main /Fo Shaders\Constant.ps Shaders\Constant.fx
  3. Open the Constant.fx file.  This file is the basic constant shader that returns the input color. 
    sampler2D input : register(s0);
     
    float4 main(float2 uv : TEXCOORD) : COLOR
    {
        float4 color = tex2D(input, uv);
        return color;
    }
  4. Open ConstantEffect.cs file.  This class creates a custom effect class that initializes the PixelShader object with the compiled shader.
    public class ConstantEffect : CustomEffect
    {
        private static PixelShader pixelShader;
     
        static ConstantEffect()
        {
            pixelShader = new PixelShader();
            pixelShader.UriSource = new Uri("Effects/Shaders/Constant.ps", UriKind.RelativeOrAbsolute);
        }
     
        public ConstantEffect()
        {
            this.PixelShader = pixelShader;
            UpdateShaderValue(InputProperty);
        }
    }
  5. Open the MainPage.xaml.cs file.  The image's effect is initialized to the ConstantEffect class.
    image.Effect = new ConstantEffect();

 

Create the Inverse Shader

The Inverse Shader calculates the inverse of the color pixels.

  1. Right-click on the Shaders folder in the Solution Explorer and select Add New Item.
  2. Select Text File template and name it Inverse.fx.
  3. Add the following code snippet to the file.  The shader takes the color input and inverses the rgb value. 
    sampler2D input : register(s0);
     
    float4 main(float2 uv : TEXCOORD) : COLOR
    {
        float4 color = tex2D(input, uv);
     
        float4 complement;
        complement.rgb = 1 - color.rgb;
        complement.a = color.a;
     
        return complement;
    }
  4. The file needs to be saved with the appropriate encoding to compile with the fxc tool.  Select Save File As in the File Menu.
  5. Select Save with Encoding in the Save dialog.
  6. Select Western European (Windows) - Codepage 1252 in the Advanced Save Options dialog.
  7. Add the following code snippet to compile.cmd file.
    fxc /T ps_2_0 /E main /Fo Shaders\Inverse.ps Shaders\Inverse.fx
  8. Compile the project.   This will run the compile.cmd script and compile all of the .fx files into .ps files.
  9. Select Show All Files in the Solution Explorer.  The Inverse.ps file has been created yet it is not included in the project.
  10. Right-click Inverse.ps file and select Include in Project.
  11. Change the file's Build Action to Content
  12. Right-click on the Effects folder in the Solution Explorer and select Add New Item.
  13. Select Class template and name it InverseEffect.cs.
  14. Add the following code snippet to the file.  The class points to the pixel shader's relative file path.
    using System;
    using System.Windows.Media.Effects;
     
    namespace PixelShaderTest.Effects
    {
        public class InverseEffect : CustomEffect
        {
            private static PixelShader pixelShader;
     
            static InverseEffect()
            {
                pixelShader = new PixelShader();
                pixelShader.UriSource = new Uri("Effects/Shaders/Inverse.ps", UriKind.RelativeOrAbsolute);
            }
     
            public InverseEffect()
            {
                this.PixelShader = pixelShader;
                UpdateShaderValue(InputProperty);
            }
        }
    }
  15. Update MainPage.xaml.cs with the new effect.
    image.Effect = new InverseEffect();
  16. Compile and run the program.  The image is now inversed. How cool is that?

    Inverse

 

Applying the Inverse Shader on a Layout

The Inverse Shader can be applied to all items that derive from UIElement.   To apply on multiple items, the shader can be added the parent layout's Effect property.

  1. Set the Grid's x:Name property to parent in MainPage.xaml.
  2. Add a Button to the parent Grid.
    <Grid x:Name="parent" Background="White">
        <Image Margin="20" x:Name="image" Source="Images/Waterfall.jpg" Stretch="Uniform" CacheMode="BitmapCache" />
        <Button Content="Pixel Shaders" Width="100" Height="25" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
    </Grid>
  3. Apply the effect on the parent Grid in MainPage.xaml.cs.
    parent.Effect = new InverseEffect();
  4. Compile and run the program. All of the elements, including the parent Grid have been inversed.

    Inverse Before Inverse After

 

Conclusion

Pixel Shaders are used to create custom bitmap effects that are executed on the GPU. The shaders can be applied to all XAML elements. The DirectX SDK is needed to create custom pixel shaders.