Playing with Shaders: Creating a water effect

Lately I’ve been looking at a really cool project on Codeplex for Pixel Shaders. The set of shaders in this library is awsome and really easy to use in you application….

All you need to do to consume any Shader is add a reference to the WPFShaderEffectLibrary class library and you can start using the shaders in XAML.What is really important is that you install the Shaders Build Task before trying to build the shader library (if you just add a reference to the ready build dlls you don’t even need to do this).

 

Water

So let’s have a look at how we can use shaders to simulate water on screen.

This is very easy all we need is a ripple shader effect. so something like this

   1: <Window.Resources>

   2:  

   3:    <Storyboard x:Key="waterAnimMain">

   4:        <DoubleAnimation Storyboard.TargetName="rippleMain" Storyboard.TargetProperty="Phase" To="0" From="10" Duration="0:0:2.0"  FillBehavior="Stop" />

   5:        <DoubleAnimation Storyboard.TargetName="rippleMain" Storyboard.TargetProperty="Amplitude" To="0" From="0.6" Duration="0:0:2.0"  FillBehavior="HoldEnd" />

   6:        <DoubleAnimation Storyboard.TargetName="rippleMain" Storyboard.TargetProperty="Frequency" To="0" From="30" Duration="0:0:2.5" FillBehavior="HoldEnd" />

   7:    </Storyboard>

   8:  

   9: </Window.Resources>

  10: <Grid Background="Transparent" >

  11:  

  12:    <Border>

  13:        <Border.Background>

  14:            <LinearGradientBrush EndPoint="0.93,0.925" StartPoint="0.094,0.125">

  15:                <GradientStop Color="#FF272788" Offset="0.112"/>

  16:                <GradientStop Color="#FF090916" Offset="0.721"/>

  17:                <GradientStop Color="#FE222267" Offset="0.28"/>

  18:                <GradientStop Color="#FE131339" Offset="0.453"/>

  19:                <GradientStop Color="#FF04040A" Offset="0.974"/>

  20:            </LinearGradientBrush>

  21:        </Border.Background>

  22:    

  23:    </Border>

  24:  

  25: </Grid>

  26:  

  27: <Window.Effect>

  28:    <shader:RippleEffect Amplitude="0" Frequency="0" Phase="0" x:Name="rippleMain"  />

  29: </Window.Effect>

This is our Shader

   1: <shader:RippleEffect Amplitude="0" Frequency="0" Phase="0" x:Name="rippleMain"  />

And we animate this shader like so to make the effect of water

   1: <Storyboard x:Key="waterAnimMain">

   2:      <DoubleAnimation Storyboard.TargetName="rippleMain" Storyboard.TargetProperty="Phase" To="0" From="10" Duration="0:0:2.0"  FillBehavior="Stop" />

   3:      <DoubleAnimation Storyboard.TargetName="rippleMain" Storyboard.TargetProperty="Amplitude" To="0" From="0.6" Duration="0:0:2.0"  FillBehavior="HoldEnd" />

   4:      <DoubleAnimation Storyboard.TargetName="rippleMain" Storyboard.TargetProperty="Frequency" To="0" From="30" Duration="0:0:2.5" FillBehavior="HoldEnd" />

   5:  </Storyboard>

We invoke this animation by having a DispatcherTimer trigger the animation every now and then and changing the Center property of the Shader to a random point from 0 to 1.

This already gets us very close but it still does not feel like real water. The trick is to animate to ripple effects at the same time. This will make the ripple effects expand together and thus making the control which has the shader applied look like water. Yet the problem is that you can only apply ONE shader per control. BUT you can have another shader on the parent control and that would still apply the shader on all children (I explain this in this article).

   1: <Window.Resources>

   2:  

   3:     <Storyboard x:Key="waterAnimMain">

   4:         <DoubleAnimation Storyboard.TargetName="rippleMain" Storyboard.TargetProperty="Phase" To="0" From="10" Duration="0:0:2.0"  FillBehavior="Stop" />

   5:         <DoubleAnimation Storyboard.TargetName="rippleMain" Storyboard.TargetProperty="Amplitude" To="0" From="0.6" Duration="0:0:2.0"  FillBehavior="HoldEnd" />

   6:         <DoubleAnimation Storyboard.TargetName="rippleMain" Storyboard.TargetProperty="Frequency" To="0" From="30" Duration="0:0:2.5" FillBehavior="HoldEnd" />

   7:     </Storyboard>

   8:  

   9:     <Storyboard x:Key="waterAnimSub">

  10:         <DoubleAnimation Storyboard.TargetName="rippleSub" Storyboard.TargetProperty="Phase" To="0" From="10" Duration="0:0:2.0"  FillBehavior="Stop" />

  11:         <DoubleAnimation Storyboard.TargetName="rippleSub" Storyboard.TargetProperty="Amplitude" To="0" From="0.6" Duration="0:0:2.0"  FillBehavior="HoldEnd" />

  12:         <DoubleAnimation Storyboard.TargetName="rippleSub" Storyboard.TargetProperty="Frequency" To="0" From="30" Duration="0:0:2.5" FillBehavior="HoldEnd" />

  13:     </Storyboard>

  14:  

  15: </Window.Resources>

  16: <Grid Background="Transparent" >

  17:  

  18:     <Border>

  19:         <Border.Background>

  20:             <LinearGradientBrush EndPoint="0.93,0.925" StartPoint="0.094,0.125">

  21:                 <GradientStop Color="#FF272788" Offset="0.112"/>

  22:                 <GradientStop Color="#FF090916" Offset="0.721"/>

  23:                 <GradientStop Color="#FE222267" Offset="0.28"/>

  24:                 <GradientStop Color="#FE131339" Offset="0.453"/>

  25:                 <GradientStop Color="#FF04040A" Offset="0.974"/>

  26:             </LinearGradientBrush>

  27:         </Border.Background>

  28:         <Border.Effect>

  29:             <shader:RippleEffect Amplitude="0" Frequency="0" Phase="0" x:Name="ripple" Center="{Binding ElementName=main, Path=(local:MouseBehaviour.LastMouseUp)}" />

  30:         </Border.Effect>

  31:     </Border>

  32:  

  33:     <Grid.Triggers>

  34:  

  35:         <EventTrigger RoutedEvent="UIElement.MouseUp">

  36:             <BeginStoryboard Storyboard="{StaticResource waterAnim}"/>

  37:             <BeginStoryboard Storyboard="{StaticResource waterAnimMain}"/>

  38:         </EventTrigger>

  39:     </Grid.Triggers>

  40:  

  41:     <Grid.Effect>

  42:         <shader:RippleEffect Amplitude="0" Frequency="0" Phase="0" x:Name="rippleSub"  />

  43:     </Grid.Effect>

  44:  

  45: </Grid>

  46:  

  47: <Window.Effect>

  48:     <shader:RippleEffect Amplitude="0" Frequency="0" Phase="0" x:Name="rippleMain"  />

  49: </Window.Effect>

And that’s it. Now we have a perfect water look thanks to the Codeplex Pixel Shader library 🙂

I created a sample app that shows all this. The app also has another feature so that you can touch the water by using the mouse and the water would ripple from the point you touch (of course this is with Behaviours so that you can even reuse it 😀 )

Happy coding 😀

DOWNLOAD SOURCE CODE

Advertisements

Blend 3 and Sketchflow

Blend 3 and SketchFlow are out and let me tell you they ROCK…. here are some good links on this subject

Behaviors
http://electricbeach.org/?p=171

List of Behaviors from the Expression Blend website
http://gallery.expression.microsoft.com/en-us/site/search?f[0].Type=RootCategory&f[0].Value=behaviors

Demo of Blend 3 and SketchFlow
http://msdn.microsoft.com/en-gb/ee341352.aspx

Videos on Sketchflow
http://www.microsoft.com/belux/MSDN/nl/chopsticks/default.aspx?id=1325

Blend 3 list of new features
http://blogs.msdn.com/expression/archive/2009/07/12/overview-of-new-features-in-expression-blend-3-sketchflow-part-iii.aspx
http://geekswithblogs.net/tkokke/archive/2009/07/10/9-little-new-things-in-expression-blend-3.aspx