Vous êtes sur la page 1sur 43

Microsoft Windows Presentation Foundation

® ™

HLSL Single Input Shader Effect – Part 1: Create and Implement


Effect

Create a simple, single input HLSL shader Effect and utilize it in WPF application. Make effect animatable
using Parameters and C# Dependency Properties
Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies,
organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious, and no association with any real company,
organization, product, domain name, e-mail address, logo, person, place, or event is intended or should be inferred. Complying with all applicable copyright laws is the
responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or
transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of
Microsoft Corporation.
The names of manufacturers, products, or URLs are provided for informational purposes only and Microsoft makes no representations and warranties, either expressed,
implied, or statutory, regarding these manufacturers or the use of the products with any Microsoft technologies. The inclusion of a manufacturer or product does not imply
endorsement of Microsoft of the manufacturer or product. Links are provided to third party sites. Such sites are not under the control of Microsoft and Microsoft is not
responsible for the contents of any linked site or any link contained in a linked site, or any changes or updates to such sites. Microsoft is not responsible for webcasting or
any other form of transmission received from any linked site. Microsoft is providing these links to you only as a convenience, and the inclusion of any link does not imply
endorsement of Microsoft of the site or the products contained therein.
Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Unless expressly
provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other
intellectual property.
Copyright © 2008 Microsoft Corporation. All rights reserved.
Microsoft are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.
The names of actual companies and products mentioned herein may be the trademarks of their respective owners.

Version 1.0
Inroduction

Windows Presentation Foundation (WPF) allows you to easily create rich, dynamic user experiences which can make the presentation of any
information more compelling. WPF users will be treated to a plethora of new features and improvements with the release of .Net Framework 3.5
Service Pack 1! Service Pack 1 adds support for High Level Shading Language (HLSL) shaders with the ShaderEffect class, allowing an almost
unlimited range of visual effects to be applied to WPF content. HLSL shaders are implemented on the GPU, and thus offer significant performance
and visual advantages to their software-rendered counterparts. The lab will guide you through the development of a simple, single image input
HLSL shader effect. The objectives of this lab are as follows:

 Teach you how to develop and compile an HLSL shader


 Create a WPF project which utilizes custom created shaders
 Animate effect by passing parameters to an HLSL program using a managed code Effect subclass
Estimated time to complete this lab : 1 hour

Contents:
A. Develop HLSL program
1. Using Visual Studio Template
2. Without Visual Studio Template
B. Testing the HLSL program
C. Create a New WPF Application
D. Develop the Application
E. Utilize HLSL Effect in the Application
F. Animate Shader Effect with Parameters

Prerequisites:
The following are required to run this Mini-Lab:

 Windows Vista; Windows XP; Windows Server 2003; Windows Server 2008
 Microsoft .Net Framework 3.5 Service Pack 1 (230 MB)
 Visual Studio 2008 Express (C#) (35-70 MB)
OR
Visual Studio 2008 Professional Edition (3389.6 MB)
 DirectX SDK (458 MB)
 Shader Effect Visual Studio Project Template and Build Task (optional but highly recommended)
 HLSL Test Environment: HLSLTester (optional)

IMPORTANT: The steps in this lab are intended to provide an overview of the technology presented. They are not intended to, and may not follow
Microsoft best practices or guidance on the technology presented
A-1. Develop HLSL Program
Using Visual Studio Template

1. Launch Visual Studio 2008


a. Start  All Programs  Microsoft Visual Studio 2008 Microsoft Visual Studio 2008
2. Start a New Project
a. File  New  Project

b. Select .Net Framework 3.5 from the drop-down menu on the upper right-hand side. Under the Project Types left-hand list select
Visual C#  Windows. Under the Templates right-hand list select WPF Shader Effect Library from My Templates section. Name
the Project ShaderEffectLibrary.
3. Select the Load project normally radio button on the Security Warning dialogue window.
4. Once the Visual Studio project is open, change the name of the .fx and .cs files from Effect1 to WaveEffect. Open the WaveEffect.fx file.
5. Select all the existing text and delete it. We need to retrieve the screen (image) color information to be used by our shader effect. Add an
HLSL parameter of type sampler2D named Input, which will use value present in a register s0, to WaveEffect.fx:
sampler2D Input : register(s0);

6. Add the main function header for the program. It will take as input a vector containing 2 floats named uv corresponding to the x and y
coordinate values of a pixel and output a vector containing 4 floats corresponding to the color composition values (Red, Green, Blue,
and Alpha) of that pixel. Copy and paste the highlighted code into WaveEffect.fx:

sampler2D Input : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR


{}

7. The HLSL program will apply an arithmetic operation to all of the pixels of an arbitrary image. We wish to use an arithmetic operation
which will reposition each pixel in a repetitive wave pattern and produce a sort of wave distortion effect. In arithmetic, the outputs of the
sine and cosine functions produce graphical wave patterns which oscillate values from -1 to 1. We will utilize these arithmetic functions to
calculate the new position of our pixels. Also, the sine and cosine functions map to the x and y component values of a coordinate
respectively; this is important because we will calculate the new value of a pixels x coordinate using the pixel’s y component as an offset
and vice-versa for the pixel’s y coordinate. We can also dictate the length (distance from wave peak to dip) and width (peak to peak). We
will use the values 200 for length and 0.01 for width. Copy and paste the highlighted code inside the main function:

sampler2D Input : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR


{
uv.y = uv.y + (sin((uv.x)*200)*0.01);
uv.x = uv.x + (cos((uv.y)*200)*0.01);
}

8. To complete our program we must return a value, more specifically a 4 float vector representing the color composition of a particular pixel.
To do so, add a variable of type float4 called Color. Set the value of the variable such that the color of the pixel at the original uv.x and
uv.y coordinates is the color of the pixel at the newly calculated uv.x and uv.y coordinates. This can be done by using the HLSL tex2D
function, passing the input sampler state (Input) and input texture coordinates (uv) as arguments. The tex2D function will return a 4 float
vector representing the color composition of the pixel located at the texture coordinates, represented by the 2 float vector argument
passed, of the sampler state. Once Color has been set to the value at the calculated position, return it so that the pixel at the original
uv.xy will have a new color composition. Copy and paste the highlighted code inside the main function:

sampler2D Input : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR


{
float4 Color;
uv.y = uv.y + (sin((uv.x)*200)*0.01);
uv.x = uv.x + (cos((uv.y)*200)*0.01);
Color = tex2D(Input, uv.xy);
return Color;
}

9. Now let’s add a C# wrapper for our HLSL program


10. Open the WaveEffect.cs file from the solution explorer on the right-hand side.
11. Select all the existing text and delete it. Copy and paste the following. Generally speaking, this managed code will set-up a Dependency
Property used to pass the screen color information to our HLSL shader and creates a URI for referencing the byte-code. For a more
detailed explanation, consult Greg Schechter’s blog entry
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;

namespace ShaderEffectLibrary
{
public class WaveEffect : ShaderEffect
{
static WaveEffect()
{
_pixelShader.UriSource = Global.MakePackUri("WaveEffect.ps");
}

public WaveEffect()
{
this.PixelShader = _pixelShader;

// Update each DependencyProperty that's registered with a shader register. This


// is needed to ensure the shader gets sent the proper default value.
UpdateShaderValue(InputProperty);
}

public Brush Input


{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}

// Brush-valued properties turn into sampler-property in the shader.


// This helper sets "ImplicitInput" as the default, meaning the default
// sampler is whatever the rendering of the element it's being applied to is.
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(WaveEffect), 0);

private static PixelShader _pixelShader = new PixelShader();

}
}

12. Now we will test our HLSL file to ensure it works. The following steps in section B briefly instruct you on how to test HLSL for compilation
errors using the HLSLTester application. Section B is optional; proceed to section C if you wish to skip it.
A-2. Develop HLSL Program
Without Visual Studio Template
1. Open a text editor, Notepad will be used for this example
a. Start  All Programs  Accessories  Notepad

2. We need to retrieve the screen (image) color information to be used by our shader effect. Add HLSL parameter of type sampler2D
named Input which will use value present in a register s0 to notepad document:
sampler2D Input : register(s0);

3. Add the main function header for the program. It will take as input a vector containing 2 floats named uv corresponding to the x and y
coordinate values of a pixel and output a vector containing 4 floats corresponding the color composition values (Red, Green, Blue, and
Alpha) of that pixel. Copy and paste the highlighted code into your document:

sampler2D Input : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR


{}
13. The HLSL program will apply an arithmetic operation to all of the pixels of an arbitrary image. We wish to use an arithmetic operation
which will reposition each pixel in a repetitive wave pattern and produce a sort of wave distortion effect. In arithmetic, the outputs of the
sine and cosine functions produce graphical wave patterns which oscillate values from -1 to 1. We will utilize these arithmetic functions to
calculate the new position of our pixels. Also, the sine and cosine functions map to the x and y component values of a coordinate
respectively; this is important because we will calculate the new value of a pixel’s x coordinate using the pixel’s y component as an offset
and vice-versa for the pixel’s y coordinate. We can also dictate the length (distance from wave peak to dip) and width (peak to peak). We
will use the values 200 for length and 0.01 for width. Copy and paste the highlighted code inside the main function:

sampler2D Input : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR


{
uv.y = uv.y + (sin((uv.x)*200)*0.01);
uv.x = uv.x + (cos((uv.y)*200)*0.01);
}

14. To complete our program we must return a value, more specifically a 4 float vector representing the color composition of a particular pixel.
To do so, add a variable of type float4 called Color. Set the value of the variable such that the color of the pixel at the original uv.x and
uv.y coordinates is the color of the pixel at the newly calculated uv.x and uv.y coordinates. This can be done by using the HLSL tex2D
function, passing the input sampler state (Input) and input texture coordinates (uv) as arguments. The tex2D function will return a 4 float
vector representing the color composition of the pixel located at the texture coordinates, represented by the 2 float vector argument
passed, of the sampler state. Once Color has been set to the value at the calculated position, return it so that the pixel at the original
uv.xy will have a new color composition. Copy and paste the highlighted code inside the main function:
sampler2D Input : register(s0);

float4 main(float2 uv : TEXCOORD) : COLOR


{
float4 Color;
uv.y = uv.y + (sin((uv.x)*200)*0.01);
uv.x = uv.x + (cos((uv.y)*200)*0.01);
Color = tex2D(Input, uv.xy);
return Color;
}

1. Now that we have finished writing our HLSL code for the effect, we will now compile it into byte-code that our WPF app can use. For this
part of the lab you must have the DirectX SDK installed.
2. Save the HLSL code written in Notepad with as WaveEffect.fx to an arbitrary location. Make sure to remember this location, as you will
have to reference it later.
3. Open the command line editor
a. Start  Run
b. In the “Open:” text field type cmd, and select OK:
4. Navigate to the location where you saved the .fx HLSL file previously
5. Now we will use fxc.exe available through the DirectX SDK to compile our HLSL code. This is accomplished using the following
command line argument:
a. fxc /T ps_2_0 /E main /Fo<name or HLSL file>.ps <name of HLSL file>.fx
6. This command says to compile to the Pixel Shader 2.0 profile, and to look for the entrypoint named "main".
7. If the compilation is successful, you will receive a message indicating success as well as the location of a newly created .ps file. The
following is a sample image of the command and success message assuming the file was named WaveEffect.fx:

8. Now that we have produced the pixel shader byte-code, let’s create a visual studio project for our shader effect. Start Visual Studio.
9. Go to File  New  Project and under NET Framework 3.5 and select the C# class library project template.
10. Enter ShaderEffectLibrary as the Name of the project.
11. Now let’s add the WaveEffect.fx and WaveEffect.ps files that we created earlier to our project. Right-click on the
ShaderEffectLibrary project icon.
a. Add  Existing Item…
12. Navigate to the WaveEffect.fx and WaveEffect.ps files and select them to be added. Also rename the Class1.cs file to WaveEffect.cs.
13. Select the WaveEffect.ps file in the Solution Explorer window. Set the Build Action of the file to Resource.
14. Let’s add a class to facilitate adding the byte-code into the build component. We will use “pack://” URI to have the byte-code live in
same packaging as the C#. Greg Schechter’s blog does a good job of explaining this.
15. Right-click on the ShaderEffectLibrary project icon.
a. Add  New Item…
16. When the Add New Item Dialogue box appears select the Code option under Visual C# Items in the Categories: window. Select the
Class template and name it EffectLibrary.cs.
17. Right-click on the ShaderEffectLibrary project icon and select Add Reference.
18. Select the .NET tab and select PresentationCore, PresentationFramework, and WindowsBase.
19. Open the EffectLibrary.cs file. Select all of the contents inside the class and delete them. Copy and paste the following code for
creating a URI for byte-code:
using System;
using System.Windows;
using System.Reflection;

namespace ShaderEffectLibrary
{
internal static class Global
{
public static Uri MakePackUri(string relativeFile)
{
string uriString = "pack://application:,,,/" + AssemblyShortName + ";component/" + relativeFile;
return new Uri(uriString);
}

private static string AssemblyShortName


{
get
{
if (_assemblyShortName == null)
{
Assembly a = typeof(Global).Assembly;

// Pull out the short name.


_assemblyShortName = a.ToString().Split(',')[0];
}

return _assemblyShortName;
}
}

private static string _assemblyShortName;


}
}

20. Now let’s add a C# wrapper for our HLSL program


21. Open the WaveEffect.cs file from the solution explorer on the right-hand side.
22. Select all the existing text and delete it. Copy and paste the following. Generally speaking, this managed code will set-up a
Dependency Property used to pass the screen color information to our HLSL shader and calls the method we just added in
EffectLibrary.cs to create a URI for referencing the byte-code. For a more detailed explanation, consult Greg Schechter’s blog entry
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;

namespace ShaderEffectLibrary
{
public class WaveEffect : ShaderEffect
{
static WaveEffect()
{
_pixelShader.UriSource = Global.MakePackUri("WaveEffect.ps");
}

public WaveEffect()
{
this.PixelShader = _pixelShader;

// Update each DependencyProperty that's registered with a shader register. This


// is needed to ensure the shader gets sent the proper default value.
UpdateShaderValue(InputProperty);
}

public Brush Input


{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}

// Brush-valued properties turn into sampler-property in the shader.


// This helper sets "ImplicitInput" as the default, meaning the default
// sampler is whatever the rendering of the element it's being applied to is.
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(WaveEffect), 0);

private static PixelShader _pixelShader = new PixelShader();

}
}

23. Now we will test our HLSL file to ensure it works. The following steps in section B briefly instruction you on how to test HLSL for
compilation errors using the HLSLTester visual studio project. Section B is optional; proceed to section C if you wish to skip it.
B. Testing the HLSL Program (Optional)
1. Using the HLSLTester link presented earlier in the document download HLSLTester.zip available near the bottom of the page:

2. Once you have finished saving the zip file, unzip the contents to a location of your preference.
3. Navigate to and open the HLSLTester Executable Application:
a. HLSLTester  HLSLTester  Bin  Debug  HLSLTester (Application)
4. We can now load an image with which to test our HLSL Program. Select the Open button in the upper left-hand corner of the HLSLTester
window. Navigate to any image you would like to use as your test subject. We will use the “Toco Toucan” image available in the sample
pictures folder on most windows vista platforms:
5. Now that we have an image with which to test our code, we can simply paste our HLSL code in place of the sample code in the lower left-
hand text area of the window. If you have followed the steps exactly you should not receive any compilation errors or warnings in the
lower right-hand text area and your image should exhibit the wave distortion effect described earlier:
6. The HLSLTester is useful for doing some simple debugging before attempting to compile your HLSL into byte-code. Once you become
more comfortable developing HLSL code you may find this tool to be unnecessary or even find a better tool altogether.
C. Create a New WPF Application
1. Start Visual Studio
2. Go to File  New  Project and under NET Framework 3.5 choose WPF Application.
3. Enter HLSLEffectLab as the Name of your project
D. Develop the Application
1. We will now develop a simple WPF application that will utilize our HLSL effect. The app will we will build will display a panel of images.
When the mouse enters the area of a particular image, it will display in the upper-half of our app window and apply our HLSL effect to the
image to create the illusion that the image is waving on the screen.
2. Let’s start by creating the app without the HLSL effect. This will basically be the same app as described above, excluding the waving
effect being applied once an image has the mouse enter its area.
3. We must first add the images we will use to the Visual Studio project. Right-click on the HLSLEffectLab project icon.
a. Add  New Folder

4. Name the Folder Images


5. Right-click on the Images folder.
a. Add  Existing Item…
6. Navigate to where the windows sample images are stored and select the Desert Landscape, Dock, Green Sea Turtle, Humpback Whale,
Oryx Antelope, and Waterfall images to be added to the folder.
7. The files representing the images we will use should now be present if you expand the Flags folder.
8. Now let’s add the XAML for our app. Open the Window1.xaml file and select all the existing text and delete it. Copy and paste the
following:
<Window x:Class="HLSLEffectLab.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Waving Image" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Name="MainImage" Stretch="Fill" Grid.Row="0"/>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Name="Image1" Source="Images/Desert Landscape.jpg" Stretch="UniformToFill" Grid.Column="0" Grid.Row="1"/>
<Image Name="Image2" Source=" Images /Dock.jpg" Stretch="UniformToFill" Grid.Column="1" Grid.Row="1"/>
<Image Name="Image3" Source=" Images /Green Sea Turtle.jpg" Stretch="UniformToFill" Grid.Column="2" Grid.Row="1"/>
<Image Name="Image4" Source=" Images /Humpback Whale.jpg" Stretch="UniformToFill" Grid.Column="3" Grid.Row="1"/>
<Image Name="Image5" Source=" Images /Oryx Antelope.jpg" Stretch="UniformToFill" Grid.Column="4" Grid.Row="1"/>
<Image Name="Image6" Source=" Images /Waterfall.jpg" Stretch="UniformToFill" Grid.Column="5" Grid.Row="1"/>
</Grid>
</Grid>
</Window>

9. This XAML code places a grid in the parent Window. That Grid is divided into two equally sized rows: the first row contains an Image
named MainImage and the second row contains another Grid. The second Grid is divided into six equally sized columns each containing
an image formatted to uniformly fill the area of that column. The resulting app window should looks as follows.
10. Next lets and the functionality to make MainImage use the image our mouse is over as its source. If you examine the XAML from step 8,
you will notice that the images placed in the six columns of the inner most Grid have their Source properties set. We did not set the
Source of MainImage because, as described earlier, we want MainImage to take on the source of the image whose area our mouse has
entered. This means we must functional logic to the code-behind file for our XAML. Because we want this logic to execute whenever our
mouse enters one of the images area we must set a routine to be called when the MouseEnter action occurs for each image. Add the
highlighted XAML to Window1.xaml:


<Image Name="Image1" Source="Flags/Desert Landscape.jpg" Stretch="UniformToFill" Grid.Column="0" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
<Image Name="Image2" Source="Flags/Dock.jpg" Stretch="UniformToFill" Grid.Column="1" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
<Image Name="Image3" Source="Flags/Green Sea Turtle.jpg" Stretch="UniformToFill" Grid.Column="2" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
<Image Name="Image4" Source="Flags/Humpback Whale.jpg" Stretch="UniformToFill" Grid.Column="3" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
<Image Name="Image5" Source="Flags/Oryx Antelope.jpg" Stretch="UniformToFill" Grid.Column="4" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
<Image Name="Image6" Source="Flags/Waterfall.jpg" Stretch="UniformToFill" Grid.Column="5" Grid.Row="1" MouseEnter="Image_MouseEnter"/>

11. In the Solution Explorer window expand the Window1.xaml file to reveal the Window1.xaml.cs file. Open Window1.xaml.cs.
12. We want to add logic that will set the source of MainImage to be the source of whichever image calls the Image_MouseEnter routine.
Select all the existing text and delete it. Copy and paste the following C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace HLSLEffectLab
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Image_MouseEnter(object sender, MouseEventArgs e)
{
MainImage.Source = ((Image)sender).Source;
}
}
}

13. The app should now reflect the added functionality. If we place our mouse in the area of the green sea turtle (third image from left) the
resulting app window should look as follows:
14. Our app is almost complete. Now we will add the HLSL effect to be applied to MainImage.
E. Utilize HLSL Effect in the Application
1. Now that we have developed an HLSL effect and a simple WPF application we can utilize our effect to be used in the app. Our goal is to
animate the image displayed in the upper-half of the window when its source is set with our HLSL effect and create the illusion that the
image is waving on the screen. In this section we will simply apply our wavy effect to MainImage without animation. We will implement
the animation in the next and final section.
2. Let start by adding our HLSL effect project to the current solution’s project space.
3. Right-click on Solution ‘HLSLEffectLab’ (1 Project)
a. Add  Existing Project…

4. Navigate to the ShaderEffectLibrary project and, if prompted, select the Load project normally radio button on the Security Warning
dialogue box. The ShaderEffectLibrary project should now be viewable as an extension of the current solution.
5. We must now add the ShaderEffectLibrary project as a reference to the HLSLEffectLab project.
6. Right-click on the HLSLEffectLab project icon and select Add Reference…
7. Select the Projects tab and select ShaderEffectLibrary.

8. Now we must add lines to the C# and XAML in order to begin using ShaderEffectLibrary in our code. Add the Following lines the
namespace declarations of Window1.xaml and the using directives of Window1.xaml.cs respectively:
using System;
<Window x:Class="HLSLEffectLab_.Window1" using System.Collections.Generic;
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" using System.Linq;
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" using System.Text;
xmlns:eff="clr-namespace:ShaderEffectLibrary;assembly=ShaderEffectLibrary" using System.Windows;
Title="Waving Flag" Height="300" Width="300"> using System.Windows.Controls;
… using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Effects;
using ShaderEffectLibrary;

9. We are now completely set-up to utilize our HLSLEffect in the app. Now we can add logic so as to execute our effect whenever the
mouse enters the area of any image. Modify the Image_MouseEnter method by adding the highlighted logic to Window1.xaml.cs:


public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Image_MouseEnter(object sender, MouseEventArgs e)
{
MainImage.Source = ((Image)sender).Source;
MainImage.Effect = new ShaderEffectLibrary.WaveEffect();
}
}

10. The app should now reflect the added functionality. If we place our mouse in the area of the green sea turtle (third image from left) the
resulting app window should look as follows:

11. Now we will add the final piece of functionality to our app, animating the HLSL effect.
F. Animate HLSL Effect with Parameters
1. To animate MainImage we will utilize the WPF animation system to animate a dependency property of a framework element. We will
modify the Effect property of Image. Add the highlighted code to Window1.xaml:


</Grid.RowDefinitions>
<Image Name="MainImage" Stretch="Fill" Grid.Row="0" >
<Image.Effect>
<eff:WaveEffect x:Name="waveEff"/>
</Image.Effect>
</Image>
<Grid Grid.Row="1">

2. We must add a new using directive to take utilize the WPF animation system, add the highlighted line to the using directives of
Window1.xaml.cs:


using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Navigation;

3. Next we will add a method to perform the animation. Add the following method to Window1.xaml.cs:
private static void DoubleAnimHelper(IAnimatable subject, DependencyProperty dp, double from, double to, int seconds)
{
DoubleAnimation timeline = null;
timeline = new DoubleAnimation(from, to, new Duration(new TimeSpan(0, 0, seconds)))
{
AutoReverse = false
};

subject.BeginAnimation(dp, timeline);
}

4. As mentioned earlier, our plan is to animate a dependency property. Currently our HLSL effect has no parameter and dependency
property with which to do so; let’s add one now. Add the highlighted code to WaveEffect.fx and WaveEffect.cs respectively (changes to
the expressions calculating uv.y and uv.x were made to include the new parameter and produce a more fluid wave effect using new
constant values). If you chose not to use the shader effect project template and build task, you will need to re-compile WaveEffect.fx file
the is part of the ShaderEffectLibrary project as detailed in section A-2:
sampler2D Input : register(s0);
float frequency : register(C0);

float4 main(float2 uv : TEXCOORD) : COLOR


{
float4 Color;
uv.y = uv.y + (sin((uv.x + frequency)*17.54389695)*0.004210526);
uv.x = uv.x + (cos((uv.y + frequency)*17.54389695)*0.004210526);
Color = tex2D( implicitInput, uv.xy);
return Color;
}


public WaveEffect()
{
this.PixelShader = _pixelShader;

// Update each DependencyProperty that's registered with a shader register. This


// is needed to ensure the shader gets sent the proper default value.
UpdateShaderValue(InputProperty);
UpdateShaderValue(WavinessProperty);
}



public double Waviness
{
get { return (double)GetValue(WavinessProperty); }
set { SetValue(WavinessProperty, value); }
}

public static readonly DependencyProperty WavinessProperty =


DependencyProperty.Register("Waviness", typeof(double), typeof(WaveEffect), new
UIPropertyMetadata(0.0, PixelShaderConstantCallback(0)));

5. Now we must only make a small modification to Window1.xaml.cs and we will be able to animate our effect. Modify the
Image_MouseEnter method by removing the green highlighted code and adding the yellow highlighted code:


private void Image_MouseEnter(object sender, MouseEventArgs e)
{
MainImage.Source = ((Image)sender).Source;
MainImage.Effect = new ShaderEffectLibrary.WaveEffect();
DoubleAnimHelper(waveEff, WaveEffect.WavinessProperty, -0.3, 0.3, 3);
}

6. Your resulting app window should now exhibit the functionality described at the beginning of the lab. If you had any issues with
developing any of the four files (five if you chose not to use the shader effect project template and build task), a complete copy of each is
attached at the end.
Window1.xaml
<Window x:Class="HLSLEffectLab_.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:eff="clr-namespace:ShaderEffectLibrary;assembly=ShaderEffectLibrary"
Title="Waving Image" Height="300" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Name="MainImage" Stretch="Fill" Grid.Row="0">
<Image.Effect>
<eff:WaveEffect x:Name="waveEff"/>
</Image.Effect>
</Image>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Name="Image1" Source="Images/Desert Landscape.jpg" Stretch="UniformToFill" Grid.Column="0" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
<Image Name="Image2" Source="Images/Dock.jpg" Stretch="UniformToFill" Grid.Column="1" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
<Image Name="Image3" Source="Images/Green Sea Turtle.jpg" Stretch="UniformToFill" Grid.Column="2" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
<Image Name="Image4" Source="Images/Humpback Whale.jpg" Stretch="UniformToFill" Grid.Column="3" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
<Image Name="Image5" Source="Images/Oryx Antelope.jpg" Stretch="UniformToFill" Grid.Column="4" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
<Image Name="Image6" Source="Images/Waterfall.jpg" Stretch="UniformToFill" Grid.Column="5" Grid.Row="1" MouseEnter="Image_MouseEnter"/>
</Grid>
</Grid>
</Window>
Window1.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ShaderEffectLibrary;

namespace HLSLEffectLab_
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Image_MouseEnter(object sender, MouseEventArgs e)
{
MainImage.Source = ((Image)sender).Source;
DoubleAnimHelper(waveEff, WaveEffect.WavinessProperty, -0.3, 0.3, 3);
}

private static void DoubleAnimHelper(IAnimatable subject, DependencyProperty dp, double from, double to, int seconds)
{
DoubleAnimation timeline = null;
timeline = new DoubleAnimation(from, to, new Duration(new TimeSpan(0, 0, seconds)))
{
AutoReverse = false
};

subject.BeginAnimation(dp, timeline);
}
}
}
WaveEffect.fx
//--------------------------------------------------------------------------------------
//
// WPF ShaderEffect HLSL Template
//
//--------------------------------------------------------------------------------------

//-----------------------------------------------------------------------------------------
// Shader constant register mappings (scalars - float, double, Point, Color, Point3D, etc.)
//-----------------------------------------------------------------------------------------
float frequency : register(C0);

//--------------------------------------------------------------------------------------
// Sampler Inputs (Brushes, including ImplicitInput)
//--------------------------------------------------------------------------------------
sampler2D implicitInput : register(S0);

//--------------------------------------------------------------------------------------
// Pixel Shader
//--------------------------------------------------------------------------------------
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 Color;
uv.y = uv.y + (sin((uv.x + frequency)*17.54389695)*0.004210526);
uv.x = uv.x + (cos((uv.y + frequency)*17.54389695)*0.004210526);
Color = tex2D( implicitInput, uv.xy);
return Color;
}
WaveEffect.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;

namespace ShaderEffectLibrary
{
public class WaveEffect : ShaderEffect
{
#region Constructors

static WaveEffect()
{
_pixelShader.UriSource = Global.MakePackUri("WaveEffect.ps");
}

public WaveEffect()
{
this.PixelShader = _pixelShader;

// Update each DependencyProperty that's registered with a shader register. This


// is needed to ensure the shader gets sent the proper default value.
UpdateShaderValue(InputProperty);
UpdateShaderValue(WavinessProperty);
}

#endregion

#region Dependency Properties

public Brush Input


{
get { return (Brush)GetValue(InputProperty); }
set { SetValue(InputProperty, value); }
}

// Brush-valued properties turn into sampler-property in the shader.


// This helper sets "ImplicitInput" as the default, meaning the default
// sampler is whatever the rendering of the element it's being applied to is.
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty("implicitInput", typeof(WaveEffect), 0);

public double Waviness


{
get { return (double)GetValue(WavinessProperty); }
set { SetValue(WavinessProperty, value); }
}

public static readonly DependencyProperty WavinessProperty =


DependencyProperty.Register("Waviness", typeof(double), typeof(WaveEffect), new UIPropertyMetadata(0.0,
PixelShaderConstantCallback(0)));

#endregion

#region Member Data

private static PixelShader _pixelShader = new PixelShader();

#endregion

}
}
EffectLibrary.cs
using System;
using System.Windows;
using System.Reflection;

namespace ShaderEffectLibrary_
{
internal static class Global
{
public static Uri MakePackUri(string relativeFile)
{
string uriString = "pack://application:,,,/" + AssemblyShortName + ";component/" + relativeFile;
return new Uri(uriString);
}

private static string AssemblyShortName


{
get
{
if (_assemblyShortName == null)
{
Assembly a = typeof(Global).Assembly;

// Pull out the short name.


_assemblyShortName = a.ToString().Split(',')[0];
}

return _assemblyShortName;
}
}

private static string _assemblyShortName;


}
}

Vous aimerez peut-être aussi