I’ve become enamored with Silverlight behaviors lately because they provide a clean and easy-to-use mechanism for encapsulating complex behavioral logic and applying it to XAML elements. And I’m equally enamored with Silverlight pixel shaders, which allow similar encapsulation of complex visual effects implemented using Microsoft’s High-Level Shader Language, better known as HLSL.

Last spring, I blogged about a technique for creating reflections programmatically using WriteableBitmap. For TechEd Europe week after next, I decided to go one step futher and create a pixel shader that does the same. Called WetFloorEffect, my shader can be applied in XAML the same way built-in shaders such as DropShadowEffect and BlurEffect are applied:

<custom:PenguinUserControl>

  <custom:PenguinUserControl.Effect>

    <custom:WetFloorEffect SourceHeight=”300″ />

  </custom:PenguinUserControl.Effect>

</custom:PenguinUserControl>

SourceHeight is a dependency property that tells the shader how tall the object you’re reflecting is. Generally, you have to play with SourceHeight a little to get the bottom of the object you’re reflecting (in this example, a user control) to line up with the top of the reflection generated by the shader. Here’s the output from this example:

WetFloorEffect with Full Reflection

WetFloorEffect also exposes a dependency property named ReflectionDepth, which determines the depth (height) of the reflection relative to the height of the object being reflected. Valid values range from 0.0 (0%) to 1.0 (100%). The following example produces a reflection that is half the height of the object being reflected:

<custom:PenguinUserControl>

  <custom:PenguinUserControl.Effect>

    <custom:WetFloorEffect SourceHeight=”300″ ReflectionDepth=”0.5″ />

  </custom:PenguinUserControl.Effect>

</custom:PenguinUserControl>

And here’s the output:

WetFloorEffect with Half Reflection

The shader itself is written in HLSL and looks like this:

sampler2D input : register(s0);

float RelativeHeight : register(c0);

 

float4 main(float2 pos : TEXCOORD) : COLOR

{

    if (pos.y > 0.5)

    {

        pos.y = 0.5 – ((pos.y 0.5) / RelativeHeight);

        return tex2D(input, pos) * pos.y;

    }

    return tex2D(input, pos);

}

Once compiled into a PS file, the shader is encapsulated into a Silverlight effect with the following class definition:

public class WetFloorEffect : ShaderEffect

{

    public static readonly DependencyProperty ReflectionDepthProperty =

        DependencyProperty.Register(“ReflectionDepth”,

        typeof(double), typeof(WetFloorEffect),

        new PropertyMetadata(1.0, PixelShaderConstantCallback(0)));

 

    public double ReflectionDepth

    {

        get { return ((double)(GetValue(ReflectionDepthProperty))); }

        set

        {

            if (value <= 0.0)

                value = 0.00001; // Avoid divide-by-zero errors in HLSL

            if (value > 1.0)

                value = 1.0;

            SetValue(ReflectionDepthProperty, value);

        }

    }

 

    public static readonly DependencyProperty SourceHeightProperty =

        DependencyProperty.Register(“SourceHeight”,

        typeof(double), typeof(WetFloorEffect),

        new PropertyMetadata(0.0, OnSourceHeightChanged));

       

    public double SourceHeight

    {

        get { return (double)GetValue(SourceHeightProperty); }

        set

        {

            if (value < 0.0)

                throw new ArgumentOutOfRangeException

                    (“SourceHeight”, “SourceHeight cannot be negative”);

            SetValue(SourceHeightProperty, value);

        }

    }

 

    static void OnSourceHeightChanged(DependencyObject obj,

        DependencyPropertyChangedEventArgs e)

    {

        ((WetFloorEffect)obj).PaddingBottom = (double)e.NewValue;

    }

       

    public WetFloorEffect()

    {

        PixelShader = new PixelShader() { UriSource =

            new Uri(“/CustomShaderDemo;component/WetFloorEffect.ps”,

            UriKind.Relative) };

        UpdateShaderValue(ReflectionDepthProperty);

        UpdateShaderValue(SourceHeightProperty);

    }

}

There’s a lot going on here, but the gist of it is that WetFloorEffect wraps the compiled HLSL code, which is embedded in the generated assembly as a resource. One item of interest is the call to PixelShaderConstantCallback when the ReflectionDepth dependency property is registered; this maps the value of the property to register c0 in the HLSL, which is in turn mapped to the variable named RelativeHeight. Another point of interest is that the value of SourceHeight is written straight through to the shader’s PaddingBottom property, which is inherited from ShaderEffect. This effectively expands the canvas on which the shader can draw downward by the specified number of pixels.

I’ll explain all this and more during my “Cool Graphics, Hot Code” talk at TechEd. And I’ll have lots of other goodies to share, too.