Saturday 20 April 2013

Shader effects in .NET


          Starting with .NET 3.5 SP1 hardware accelerated effects can imbue your WPF applications with superpowers.
You assign shaders to any UIElement via the Effect property.
The way to do that is myButton.Effect = myEffect, where:
  • myEffect can be any object derived from System.Windows.Media.Effects
  • it can be BlurEffect, DropShadowEffect or ShaderEffect
  • first 2 are build-in shaders and you can use them straight away(MyButton.Effect = new BlurEffect() { ... };) and ShaderEffect is the fun and customisable one
  • do not use BitmapEffect or any derived classes as it is obsolete and badly designed a ShaderEffect has 2 parts : a shader written in HLSL(a text file that contains some HLSL code and has the .fx extension) and a C# wrapper (.cs class file) that will allow you to use the shader in your .NET application
I provide full code example for a wpf application that does color manipulation (RGB gain, brightness, saturation, contrast, gamma ) at https://github.com/Pzkgw/WpfColorManipulationShader. Please feel free to use it.

To create a ShaderEffect :

  • compile the .fx text file containing the shader code and obtain a binary .px file 
  • write the C# wrapper

To use a ShaderEffect:

  • the C# wrapper will define what properties are connected to the shader
  • a property is connected to a certain register and the shader will use the same register. Registers used for sharing constants start with C letter (eg: C0,C1 ..), and the ones used for sharing images(samplers) start with S. The first one available is S1, as S0 will be reserved for the input image.
  • in the following example both the shader and the wrapper will use the saturation information from the C0 register

        static readonly DependencyProperty SaturationProperty =
            DependencyProperty.Register("Saturation", typeof(double), typeof(ColorManiPulatorEffect),
            new UIPropertyMetadata(1.0, PixelShaderConstantCallback(0))/* assigned to sampler register C0 */);

        double Saturation
        {
            get { return (double)GetValue(SaturationProperty); }
            set { SetValue(SaturationProperty, value); }
        }
  • in case you need to send a image or a LUT to the shader :
           - create a WritableBitmap

        wBmp = new WriteableBitmap(256, 1, 96, 96, PixelFormats.Rgb24, null);

- make a buffer to hold image information you need to send and add the information(in this case a 3 x 256 LUT):
        byte[] buff = new byte[768];



        - send information to graphics board
            wBmp.WritePixels(new Int32Rect(0, 0,
                wBmp.PixelWidth, wBmp.PixelHeight),
                buff, wBmp.PixelWidth * wBmp.Format.BitsPerPixel / 8, 0);

            LutColor = new ImageBrush(wBmp);

       - define the dependency property:
        static readonly DependencyProperty LutColorProperty =
            ShaderEffect.RegisterPixelShaderSamplerProperty("LutColor",
            typeof(ColorManiPulatorEffect), 1 /* assigned to sampler register S1 */);

        Brush LutColor
        {
            get { return (Brush)GetValue(LutColorProperty); }
            set { SetValue(LutColorProperty, value); }
        }


How to write the shader:

  • i suggest you use a separated tool to write the shader, one that allows you to view real time what your shader looks like on a target image before you start the integration in your .Net application
  • i used Fx Composer from https://developer.nvidia.com/fx-composer, it's a bit more than what you need for a basic shader develop but it's very good at what it does and it allows you to visualy test your shader 
  • however a simple shader can be written using notepad, adding some simple code and saving the file with a fx extension :
sampler2D inSampler : register(S0);//input image, sent using register S0
float brightness : register(C0);//input value, sent using register C0

float4 main(float2 uv : TEXCOORD) : COLOR {//declaration of shader body
  float4 color = tex2D(inSampler, uv);//get the source color at the current uv texture coordinates 
//as a vector of 4 float values r,g,b,a
  return color * brightness;//multiply all the values with the brightness
}

Compile that .fx file and get a .px file:

  •  go to Visual Studio\Tools\External Tools , click add and introduce the details: 
 Title : Directx compiler Command : Path to fxc.exe(fx compiler) in DxSdk instalation Arguments : /DWPF /Od /T ps_2_0 /E $(ItemFileName)PS /Fo$(ProjectDir)$(ItemFileName).ps $(ItemPath)Initial directory : $(TargetDir)Uncheck "Close on exit" to make sure output of fx compilation is visible Add fx file to solution explorer and select it , and use the new added tool
  • Resulted ps file must be added to solution and set "Build action" to Resource(!!!)
  • alternative, you can also try addins : 
  1. for build: HLSL Shader Build Task
  2. for syntax(optional): NShader

No comments:

Post a Comment