This article is compatible with the latest version of Silverlight.
This is part 6 of the series “Silverlight in Action”.
Here we’re sharing our experience from the amazing CompletIT web site.
Introduction
In this article, we’ll examine one very interesting approach for creating Rich UI. It’s a program that manipulates the UI just before the actual rendering on the screen – and it’s called PixelShader. Its name says that this program is processing the Pixels (of a frame that Silverlight renders), and Shader stands for a program that calculates the rendering effect, most of the time - on the GPU.
Pixel shaders in brief:
- Pixel shaders are written in HLSL (High Level Shader Language) and are usually saved in “.fx” files.
- The code is compiled using DirectFX compiler into “.ps” file.
- In Silverlight, the compiled effect is wired by inheriting the ShaderEffect base class.
- After that you can apply this effect on UIElements, using the UIElement.Effect property.
- At runtime, the effect is executed on each pixel, rendered for that Element(bitmap) and we end up with a new bitmap that is visualized on the screen.
Writing pixel shader effects is very tricky. The HLSL is not very common language and somehow it might be confusing. In order to feel more comfortable during reading this article, we recommend that you to take a look at this article – it’s about shader basics and contains stuff that you’ll need to know in order to create custom pixel shaders.
And let’s see how to create a “Book folding Pixel Shader effect” like this one:
A snapshot of folding the page content
Try the folding by yourself. Just click on the Image.
Implementation
The ExampleFolding.fx
Pixel shaders can define global input variables by using the so called registers (There is one parameter that is compulsory - sampler2D input - this is the input bitmap). In our example, we’ll need one more parameter (left) that will be the left position of the element after folding, relative to the original element position. It’ll vary from 0 to 0.5. This can also be the folding angle, but for the sake of simplicity, we’ll use left as an input parameter. At the end, when we run the actual animation on this parameter from 0 to 0.5 and back to 0, we’ll have a folding effect.
Having the left parameter, we’ll use a simple math to transform the current point passed to the main function of the shader (uv). Once we have transformed the point, we’ll get the pixel for that point from the original bitmap – the input parameter.
The transformation of the current point is done by, first, transforming the bitmap bounds to fit in the new bounds (having smaller width) and, second, by interpolating the point, considering the angle of folding.
sampler2D input : register(s0);
float left : register(c0);
float4 transform(float2 uv : TEXCOORD) : COLOR
{
float right = 1 - left;
float2 tuv = float2((uv.x - left) / (right - left), uv.y); // transforming the curent point (uv) according to the new boundaries.
float tx = tuv.x;
if (tx > 0.5)
{
tx = 1 - tx;
}
float top = left * tx;
float bottom = 1 - top;
if (uv.y >= top && uv.y <= bottom)
{
float ty = lerp(0, 1, (tuv.y - top) / (bottom - top)); //linear interpolation between 0 and 1 considering the angle of folding.
return tex2D(input, float2(tuv.x, ty)); // get the pixel from the transformed x and interpolated y.
}
return 0;
}
float4 main(float2 uv : TEXCOORD) : COLOR
{
float right = 1 - left;
if(uv.x > left && uv.x < right)
{
return transform(uv);
}
return 0;
}
Something that might be very interesting is that during folding, we can see behind the element with the applied effect. This is due to the following code:
float4 main(float2 uv : TEXCOORD) : COLOR
{
float right = 1 - left;
if(uv.x > left && uv.x < right)
{
return transform(uv);
}
return 0;
}
We’re returning 0 for all points outside the new boundaries and 0 means transparent color.
The ExampleFoldingEffect.cs
This is the class that wires the ExampleFolding.ps and the UIElements. It has two Dependency properties for the shader parameters (the input and left).
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;
namespace FoldingEffect
{
public class ExampleFoldingEffect : ShaderEffect
{
public static readonly DependencyProperty InputProperty = RegisterPixelShaderSamplerProperty("Input", typeof(ExampleFoldingEffect), 0);
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(double), typeof(ExampleFoldingEffect), new PropertyMetadata(0.0, PixelShaderConstantCallback(0)));
public ExampleFoldingEffect()
{
PixelShader pixelShader = new PixelShader
{
UriSource =
new Uri("/FoldingEffect;component/ExampleFolding.ps", UriKind.Relative)
};
this.PixelShader = pixelShader;
this.UpdateShaderValue(InputProperty);
this.UpdateShaderValue(LeftProperty);
}
public Brush Input
{
get
{
return ((Brush)(this.GetValue(InputProperty)));
}
set
{
this.SetValue(InputProperty, value);
}
}
public double Left
{
get
{
return ((double)(this.GetValue(LeftProperty)));
}
set
{
this.SetValue(LeftProperty, value);
}
}
}
}
Applying the Effect
Applying ExampleFoldingEffect is as easy as applying any other effect (like built-in DropShadowEffect).
<Border BorderBrush="Black"
BorderThickness="2"
Margin="10"
Padding="5">
<Image Source="Desert.jpg" />
<Border.Effect>
<FoldingEffect:ExampleFoldingEffect x:Name="foldingEffect" />
</Border.Effect>
</Border>
Animating the Effect
The shader itself is static if applied once. If we apply the effect for example with a value of 0.25, we’ll see the static half-folded image and that’s it. In order to accomplish the result of animated folding, we’ll have to create an animation over that effect and more precisely – over the Left dependency property. In this way, on each frame of the animation, the effect will be applied again with updated Left property, so the image will be more/less folded.
<Storyboard x:Name="foldAnimation">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="foldingEffect"
Storyboard.TargetProperty="Left">
<EasingDoubleKeyFrame KeyTime="00:00:00.5"
EasingFunction="{StaticResource easeOut}"
Value=".5" />
<EasingDoubleKeyFrame KeyTime="00:00:01"
EasingFunction="{StaticResource easeIn}"
Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
Conclusion
Pixel shaders are very powerful Silverlight features that allow us to manipulate UI rendering on a pixel level. Once that you are comfortable with HLSL, you’ll be able to do amazing things with ease. There are also a lot of great examples out there on the web, built for WPF or Silverlight. You can also browse and examine some of them by using the Shazzam tool, which also gives you a very good environment for creating pixel shaders.
Stay tuned for more articles from this series coming up next week.