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

7 thoughts on “Playing with Shaders: Creating a water effect

  1. Pingback: Dew Drop – August 28, 2009 | Alvin Ashcraft's Morning Dew

  2. Hi i dled the source code and got this run time error:

    Assembly ‘…\WaterDemo\ShaderEffectLibrary\obj\Debug\ShaderEffectLibrary.dll’ doesn’t contain any UserControl types.

  3. Ah well. nearly got it running.

    Error: error X5608: Compiled shader code uses too many arithmetic instruction slots (65). Max. allowed by the target (ps_2_0) is 64.

    C:BLAHBLAH\Projects\WaterDemo\ShaderEffectLibrary\ShaderSource\Ripple.fx

  4. Pingback: WPF water effect surface with shaders | 我爱源码网

Leave a comment