In the first part of series, we wrote our first Shader Program. Now it is time to compile it so that we could use it in our WPF Application.
HLSL Compiler
To compile your HLSL code to a format that could be used with .Net WPF application, you would require the fxc compiler. For Windows 7 and earlier editions, you would need to install DirectX SDK in order to get fxc.exe. For Windows 8 and above, this comes as part of the Windows SDK.
The compiler can found under the location ‘Program Files (x86)\Windows Kits‘. If we had saved our previous written code (from previous part of this series) as RedTint.hlsl, we would need to use following command to compile it.
fxc /T ps_2_0 /E main /Fo RedTint.ps RedTint.hlsl
This would give the necessary ps file, or Pixel Shader file which we could now refer in our WPF Application. Now that we have our Pixel Shader file, we will move on to our WPF application.
Creating our Test Application
We will keep our text application simple. The XAML code is shown below.
<Grid Margin="10,10,10,10"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="4*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label Grid.Column="0" Grid.Row="0" Content="Normal Button" HorizontalAlignment="Center"/> <Button Grid.Column="0" Grid.Row="1" Content="Click Me"/> <Label Grid.Column="1" Grid.Row="0" Content="Button with Shader" HorizontalAlignment="Center"/> <Button Grid.Column="1" Grid.Row="1" Content="Click Me"/> </Grid>
Also enclosed is a screenshot of the application. The purpose of our application would be apply our shader to one of the buttons, while leaving the other button untouched, so that we could easily demonstrate the difference (or rather impact of shader). We will also add our RentTint.ps file to our project as a resource.
Defining Custom Effect
To use the compiled shader application, we need to load it to the rendering engine input stream. This is accomplished with the help of ShaderEffect class in .Net. The ShaderEffect base class makes it possible to wrap the HLSL pixel shader and apply them our applications XAML elements.
public class RedTintEffect: ShaderEffect { public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(RedTintEffect), 0); public Brush Input { get => ((Brush)(GetValue(InputProperty))); set => SetValue(InputProperty, value); } public RedTintEffect() { PixelShader pixelShader = new PixelShader(); pixelShader.UriSource = new Uri(@"path_to_ps_resource", UriKind.Absolute); PixelShader = pixelShader; UpdateShaderValue(InputProperty); } }
We have defined our custom effect, namely RedTintEffect, derieved from ShaderEffect. Let’s examine the code closely beginging with the constructor.
public RedTintEffect() { PixelShader pixelShader = new PixelShader(); pixelShader.UriSource = new Uri(@"path_to_ps_resource", UriKind.Absolute); PixelShader = pixelShader; UpdateShaderValue(InputProperty); }
The first step we need to is to initialize an object of PixelShader (which is a protected member of ShaderEffect class) and refer our ps file using the UriSource property. This would enable the ShaderEffect to communicate with the GPU.
The next step is to create a Dependency Property of Type Brush, calling it “Input”. This is a special property and would contain the input image. There lies a difference in the way we register the Dependency Property as well. Instead of the usual DependencyProperty.Register(), we would be using ShaderEffect.RegisterPixelShaderSamplerProperty() method.
public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(RedTintEffect), 0); public Brush Input { get => ((Brush)(GetValue(InputProperty))); set => SetValue(InputProperty, value); }
One of the important points to note in the above code is the final parameter of ShaderEffect.RegisterPixelShaderSamplerProperty() method. The Integer value 0 corresponds to the S0 pixel shader register in our HLSL code. Here is the refered code from HLSL source.
sampler2D input: register(s0);
That’s all we need. We can now go back to our XAML and apply the effect.
Applying Shader
The update code is as follows.
<Grid Margin="10,10,10,10"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="4*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Label Grid.Column="0" Grid.Row="0" Content="Normal Button" HorizontalAlignment="Center"/> <Button Grid.Column="0" Grid.Row="1" Content="Click Me"/> <Label Grid.Column="1" Grid.Row="0" Content="Button with Shader" HorizontalAlignment="Center"/> <Button Grid.Column="1" Grid.Row="1" Content="Click Me"> <Button.Effect> <shadereffects:RedTintEffect/> </Button.Effect> </Button> </Grid>
We have added our newly added custom Effect to one of the button. Let’s hit F5 and verify our changes.
As observed in the screenshot, the Button to which the Effect was applied has turned Red, while the other Controls remain the same. Do observe that even the Forecolor of Text within the button has turned Red. This is true to our definition of our Shader, since it returns Red irrespective of the input. We will look into more complex scenarios in later posts where we will introduce conditional color changes.
You can access the entire source code described in Part 1 and Part 2 of this series from my Github
Dearest, this small tutorial and the source code on GIT has been fundamental to let me develop a small effect that changes fully recolors a transparent PNG icon. I’ve only one small suggestion: to reference the .ps file using the pack uri format, like this, so that it will be loaded directly independently from the absolute path:
pixelShader.UriSource = new Uri(“pack://application:,,,/ShaderExample001;component/Shader/RedTint.ps”);
Thank you!!!
M
LikeLike
Hey, thank you Mauro. I will soon update this. Cheers
LikeLike