Tuesday, March 24, 2009

Writing custom Pixel shader effects.

What are Pixel shaders

Pixel shaders are programs which operates on each pixel before displaying in the screen.Pixel shaders are not part of Silverlight or WPF.They are more related to the Graphics cards and DirectX.They use a language called HLSL (High Level Shader Language) for programming.It has more similarities to C than C#.More details on HLSL is available here.

Coding Pixel Shader : Negative.Fx

Coding in HLSL refers some registers and all.Here is the code for a Negative Effect.

sampler2D implicitInput : register(s0);

float4 PS(float2 uv : TEXCOORD) : COLOR
{
float4 color = tex2D(implicitInput, uv);

float4 result;

result.r=1-color.r;
result.g=1-color.g;
result.b=1-color.b;
result.a=color.a;

return result;
}



We are getting the color value of pixel in float4 located at uv and returning a color (result)which is inverse of the given color.

Compiling a Pixel Shader

The extension of the Pixel shader files will be .FX.We need to have DirectX SDK installed in our machine in order to compile HLSL (*.fx) source code to *.ps bytecode that can be loaded using Silverlight’s PixelShader class. Fxc.exe is the HLSL compiler used to compile *.fx HLSL shader files. The following command shows how to compile Negative.fx:


Current folder : [Install drive]:\Program Files\Microsoft DirectX SDK (November 2008)\Utilities\bin\x86 (Change the month according to the version you have)


fxc.exe /T ps_2_0 /E PS /FoNegative.ps Negative.fx

/T ps_2_0 :Compiles against the ps_2_0 target pixel shader level that WPF currently supports
/E PS : Marks the function 'PS' as the entry point
/FoNegative.ps Negative.fx : Negative.fx as input and output to Negative.ps. Note that there is no space between /Fo and Greyscale.ps

The command above can be used as pre-build event of a Visual Studio project to automate the build process.


Creating Custom Shader Effect class


Add the output .ps file as Resource in the project.Once we add the .ps file we can start writing the .Net interface class for that.It is derived from the ShaderEffect class and we need to add some things there.See the below code.



public class Negative : ShaderEffect
{
private static PixelShader _pixelShader = new PixelShader()
{
UriSource = new
Uri("pack://application:,,,/WpfCustomEffect;component/Negative.ps",
UriKind.RelativeOrAbsolute)
};

public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(Negative), 0);
public Brush Input
{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}

public Negative()
{
this.PixelShader = _pixelShader;
UpdateShaderValue(InputProperty);
}
}



PixelShader is the protected property in ShaderEffect class and that should be initialized with a new instance which refers to our .ps file.Then we creates a dependency property which is of type Brush and associates to the shader.Creating dependency property is a bit different here.We need to use ShaderEffect.RegisterPixelShaderSamplerProperty method in order to create the DP.


Using Custom Pixel Shader


We can use the new Effect from either XAML or C# code behind.



<Image x:Name="img"
Source="Autumn.jpg">
<Image.Effect>
<effect:Negative />
</Image.Effect>
</Image>
<Button Grid.Row="1"
Click="Button_Click"
Content="Click to change">
<Button.Effect>
<effect:Negative />
</Button.Effect>
</Button>




private void Button_Click(object sender, RoutedEventArgs e)
{
if (img.Effect == null)
{
img.Effect = new Negative();
}
else
{
img.Effect = null;
}
}



Sample can be downloaded from here.

3 comments:

  1. Hi Joy,
    Is there a solution to print an image which has a pixel shader effect? I mean the effected image to be printed. It might be a wrong question because the effect does not manipulate the image source but the GPU jsut render the image that way.

    Thanks in advance

    ReplyDelete
  2. I think this is possible.Have you tried taking snapshot of the image with effect?you will get exactly what is there in screen.That means you can print the same too.I am on vacation.Will be posting after that...
    Joy

    ReplyDelete
  3. thanks a lot!
    the trick is UpdateShaderValue(InputProperty) even for parameterless effect

    ReplyDelete