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 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:
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.
On Oct 30 2009 11:50 AMBy jprosise
PingBack from http://isilverlight.cn/jeff-prosises-blog-reflection-shader-for-silverlight-3/
This post was mentioned on Twitter by ittyurl: New at IttyUrl: http://ittyurl.net/lcNL.ashx jeff prosise's blog : reflection shader for silverlight 3
I don't have the DirectX SDK on my pc.
Is it possible to download the WetFloorEffect.ps file ?
Better yet, you can compile FX files into PS files online:
http://silverlightuk.blogspot.com/2009/03/silverlight-web-pixel-shader-compiler.html
It would be a good idea if the url to use the compiler(http://www.voxpeeps.com/slpixelshadercompiler) works.
But it does not work
Thank you for submitting this cool story - Trackback from DotNetShoutout
@malbaladejo
There is an alternative to the online version Jeff mentioned.
Shazzam Pixel Shader Utility.
You can copy Jeff's fx code into Shazzam and it will create the PS file and the C#/VB file for you.
http://shazzam-tool.com
http://blog.shazzam-tool.com
Hi Jeff,
Great post ... this is exactly what I was looking for. Also, thanks for pointing me to Shazaam, that is a very impressive tool.
I was wondering if you could post your code. The problem I see is that the PixelShader HLSL code you posted doesn't match the the C# class that you posted. The C# class has a reflectionDepth and SourceHeight properties while the HLSL code you posted has an RelativeHeight property.
Even if you just could post the HLSL code in a comment that matches the C# code, I wuold be very grateful.
Thanks again!
...Ed
There's no mismatch. The code should compile and run just fine. (It came directly from my VS project.) I expose a dependency property named ReflectionDepth from the shader class and map it to register c0. Then, in the HLSL, I refer to register c0 using the name RelativeHeight. It really doesn't matter whether I use the same name in the C# and the HLSL, although it would arguably improve readability if I did.
BTW, I will publish the whole shebang after (maybe before) TechEd Europe. The projects aren't quite ready for sharing yet because I have some cleanup to do.
Nevermind Jeff, I had created a class with Shazaam using your HLSL code and the properties it generated in the C# were different. When I ran with your class it worked great ... sorry about that.
One question I did have was, what in the HLSL controls the opacity of the reflected image?
It's this line right here:
return tex2D(input, pos) * pos.y;
This statement multiplies the A, R, G, and B components of the returned color by pos.y, which will be between 0.0 and 1.0 since HLSL uses normalized coordinates.
Cool ... Thanks Jeff. I'm going to play more with this to get a better feel for it. This post was extremely helpful.
Thanks again for being so responsive.
...Ed
Thank you for submitting this cool story - Trackback from iAwaaz-News-by-People
This was incredible. I downloaded "Shazzam", customized your simple reflection HLSL, included the *.PS file in my Silverlight assembly as a resource per your instructions here -http://www.wintellect.com/CS/blogs/jprosise/archive/2009/03/25/silverlight-3-s-new-pixel-shaders.aspx
... and I was good to go, making a simple reflective affect on a regular old insurance-claims application UI widget. Makes coding FUN! I love Silverlight!
Great post. Trying to set the SourceHeight for the effect by using databinding. I would think that this would be a natural thing to do. I get a XAMLParseException. Anybody else get the same thing? Any explanation?
Useful Links 404