WPF Custom Effects

When we talk about custom bitmap effects, two languages come into picture as .Net and High Level Shader Language (HLSL). The good thing about HLSL is, it targets the Graphics Processor Unit, instead of CPU. Writing custom bitmaps isn't very difficult but getting started is difficult because there are lot of resources available on internet which makes very difficult to filter the useful things. In this article, we will try to write our first bitmap effect.

Now to write the custom bitmap effect, one has to perform two operations:
- Write the HLSL program that defines the effect
- Write the wrapper class to make it available in a .NET program

I will try to make this article as simple as possible. The custom effect we will implement is admittedly boring - it simply colors a rendered area red - but the principles are the same for more complex HLSL programs.

HLSL in Visual Studio/Notepad
If you want to write programs in HLSL you need to download and install the latest DirectX SDK. This provides you not only with an HLSL compiler but also help files and examples that make things easier. HLSL file can be created and edited using Notepad and can be further complied into shader byte code using command line compiler, fxc. Same can be done in Visual Studio.

Setting up the file
All you have to do is to start a new WPF project. Add a new directory to the project and add into the directory a new text file. If you call the directory Shader and the text file shader1.fx then you can follow the rest of this article step-by-step. You can use names of your own choice but it is easier to name the text file with a .fx extension.

The file that you create also has to save in basic ASCII format and Visual Studio uses Unicode. If you try to compile the file saved as Unicode then the compiler won't be able to make any sense of it and you will end up with an error message "entry point main cannot be found". To set the file to save in ASCII, open it in the editor, click Save AS, click the arrow on the Save button and select Save With Encoding.

From the list of possible encodings select Western European (DOS) Code page 850 - which is essentially ASCII. From this point on the file will be saved and loaded using ASCII. Notice that if you add another text file or use the same text file in another project you have to go through this process of setting the encoding again.


Let’s try the simplest possible HLSL pixel shader program:

float4 main(float2 uv:TEXCOORD):COLOR
{
vector<float,4>color={1,0,0,1};
return color;
}


Don't worry too much for the moment what it all means - just use it as a way to test that you have the development environment set up. You can see that it consists of a main function, i.e. the entry point for the HLSL program, and it returns color, which is a four element vector set to {1,0,0,1} - the elements correspond to R,G,B and A respectively and so the color set is 100% red and is fully opaque since Alpha=1.

Setting up the compiler
Now you can enter HLSL commands directly into the file you have added to the project. You could now move to the command line to compile the HLSL into shader byte code but it is easier to set up an external tool to do the same job via Visual Studio.

If you are using the Express edition of Visual Studio you can't set up an external command unless you first add the External Tools menu. To do this first use the command Tools, Customize. In the dialog box that appears select the Commands tab. With the Menu bar option selected pick Tools from the drop-down list next to the selection box. Next click the Add Command button and select Tools in the Categories window and then External Tools in the Command window. Click Ok and close and you should now see External Tools as an option in the Tools menu. You can now continue with the instructions as for Visual Studio 2010.

Use the command Tools, External Tools and click the Add button when the dialog box appears. Into the dialog box inter the following:
Title: Fxc
Command: C:\Program Files\Microsoft DirectX SDK (February 2010)\
Utilities\bin\x86\fxc.exe
Arguments:/T ps_2_0  /E main/Fo$(ItemFileName).ps $(ItemFileName).fx
Initial directory:$(ItemDir)

and tick the Use Output window box.

If you are using C# Express you also need to make the new external tool an addition to the menu. To do this you go through roughly the same steps - first select Tools, Customize, then click the Command Tab, select the Tools menu in the drop down list next to the Menu Bar selection, use Add Command to add External Command1 and customize it, using the Modify Selection button. so that its name is Fxc. After this the new option will appear in the Tools menu as in the case of full Visual Studio.

Of course you need to change the Command entry to specify the location of the fxc.exe file but the correct path is usually similar to the example. The arguments specify that the file to be used is the current file with  a .fx extension and the result of the compilation should have the same name but end in .ps. The output file is stored in the same directory as the current file. The other parameters passed to the compiler specify that you are using a 2.0 shading model and the entry point is called main. You can find additional compiler parameters documented in the DirectX help file.

If you don't want to use the external tools facility in Visual Studio you can use the command line. Simply start the DirectX command prompt and enter:

>fxc /T ps_2_0 /E main/Foshader1.ps shader1.fx"
But you will have to provide the full path to the /fx file where ever it has been stored.

Compiling your first shader
Now that we have Visual Studio set up we can enter the test shader:
float4 main(float2 uv:TEXCOORD):COLOR
{
vector<float,4>color={1,0,0,1};
return color;
}

and save it as shader.fx in a new directory called shader.  You have to remember to manually save the shader .fx file before you try to compile it because the compiler works on the file not on the contents of the editor and there is no auto save before compile facility.
To compile the shader simply select fxc from the Tools menu while the fx file is the current file i.e. the one you are working on in the editor. You should see the compilation succeeded message in the Output window. If you don't then check that you have entered the test program correctly and check that you have set the coding to ASCII - see earlier.

Note that if you make any changes to the .fx file you have to save it and run the compiler to generate a new .ps file before any changes are reflected in any C# code that uses it.

The custom effect
till here we have the HLSL pixel shader compiled correctly and stored as shader1.ps in the Shader directory. Now it's the time to write C# code to make use of it. First we need to add namespaces:
using System.Windows.Media.Effects;
using System.Windows.Media.Media3D;
using System.IO;


To create a custom effect you have to create a derived class from ShaderEffect. The very minimum that this has to do is to create an instance of PixelShader that wraps your shader code and store a reference to this in the new classes PixelShader property. This is most easily achieved in the constructor as:
public class BlankEffect : ShaderEffect
{

public BlankEffect()
{
  PixelShader pixelShader =
               new PixelShader();
  Uri uri = new Uri(
    Directory.GetCurrentDirectory() +
@"\..\..\Shader\shader1.ps", UriKind.Absolute);
  pixelShader.UriSource = uri;
  this.PixelShader = pixelShader;
}
}

This first creates an instance of PixelShader, sets its UriSource property to the location of the shader1.ps file and then sets the BlankEffect PixelShader property to reference the new instance. That's all you have to do to create a custom effect.
Now we can try it out by adding a button to the form and change its Click event handler to read:
private void button1_Click( object sender, RoutedEventArgs e)
{
    button1.Effect = new BlankEffect();
}

If you now run the program and click on the button

the result should be a red rectangle where the button should be displayed as:

As admitted at the start of this article this isn't exactly impressive but, if you think about the complexity of the steps we needed to take to get to this stage, the fact that it all works is very impressive!

Remember if you want to make any changes to the shader you have to edit the .fx file, save it, compile it to generate the .px file and then run the project again. Now with the mechanics of editing, compiling and using an HLSL program we can move on and learn more about how pixel shaders work and more about writing HLSL. We will have  to modify the new C# class to take account and make use of the features we introduce, but the basic ideas and procedures remain unchanged.

Hope by this time, you might have got an idea of custom effects. Happy coding !!!

By Shweta Lodha   Popularity  (1910 Views)