百叶窗动画是制作PPT时常用的动画之一,本文将通过实现百叶窗动画效果的例子介绍在WPF中如何使用ShaderEffect。ShaderEffect使用高级着色器语言(High Level Shading Language,HLSL)事先制作好并且已经编译过的效果。先看下百叶窗动画实现效果:
[WPF]使用HLSL实现百叶窗动效-LMLPHP

准备工作与实现

  • 编写和编译HLSL代码,创建ShaderEffect。由于HLSL有自己的语言语法,本文不做讨论。这里使用一个已有的的HLSL文件,也是后边将介绍的一个HLSL编辑器工具Shazzam Shader Editor中的案例。
  • 定义像素着色器,在UI元素中使用像素着色器,并通过动画设置百叶窗动画。
    百叶窗效果的像素着色器代码中:
public class BlindsShader : ShaderEffect
{
    public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(BlindsShader), 0);
    public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register("Progress", typeof(double), typeof(BlindsShader), new UIPropertyMetadata(((double)(30D)), PixelShaderConstantCallback(0)));
    public static readonly DependencyProperty NumberOfBlindsProperty = DependencyProperty.Register("NumberOfBlinds", typeof(double), typeof(BlindsShader), new UIPropertyMetadata(((double)(5D)), PixelShaderConstantCallback(1)));
    public static readonly DependencyProperty Texture2Property = ShaderEffect.RegisterPixelShaderSamplerProperty("Texture2", typeof(BlindsShader), 1);
    public BlindsShader()
    {
        PixelShader pixelShader = new PixelShader();
        pixelShader.UriSource = new Uri("/WPFTest;component/Shader/ShaderSource/BlindsShader.ps", UriKind.Relative);
        this.PixelShader = pixelShader;

        this.UpdateShaderValue(InputProperty);
        this.UpdateShaderValue(ProgressProperty);
        this.UpdateShaderValue(NumberOfBlindsProperty);
        this.UpdateShaderValue(Texture2Property);
    }
    public Brush Input
    {
        get
        {
            return ((Brush)(this.GetValue(InputProperty)));
        }
        set
        {
            this.SetValue(InputProperty, value);
        }
    }
    /// <summary>The amount(%) of the transition from first texture to the second texture. </summary>
    public double Progress
    {
        get
        {
            return ((double)(this.GetValue(ProgressProperty)));
        }
        set
        {
            this.SetValue(ProgressProperty, value);
        }
    }
    /// <summary>The number of Blinds strips </summary>
    public double NumberOfBlinds
    {
        get
        {
            return ((double)(this.GetValue(NumberOfBlindsProperty)));
        }
        set
        {
            this.SetValue(NumberOfBlindsProperty, value);
        }
    }
    public Brush Texture2
    {
        get
        {
            return ((Brush)(this.GetValue(Texture2Property)));
        }
        set
        {
            this.SetValue(Texture2Property, value);
        }
    }
}

BlindsShader.ps是编译好的HLSL文件,Progress表示百叶窗叶片打开的进度,NumberOfBlinds是百叶窗叶片的数量,Texture2是百叶窗叶片的纹理(通常使用一个纯色的图片)。

使用百叶窗效果时,只需在resources中添加着色器和动画,并对目标UI元素的Effect设置为百叶窗动画。为了展示效果,本例用图片111.jpg作为grid的背景,用纯色图片blinds.jpg作为叶片纹理。在grid的加载时触发动画设置百叶窗叶片打开的进度。

<Window.Resources>
    <ImageBrush x:Key="imageBrush" ImageSource="111.jpg" />
    <ImageBrush x:Key="blindsBrush" ImageSource="blinds.jpg" />
    <local:BlindsShader x:Key="BlindsShader"
                        NumberOfBlinds="4"
                        Progress="0"
                        Texture2="{StaticResource blindsBrush}" />
    <Storyboard x:Key="DefaultBlindsShaderStoryboard" FillBehavior="HoldEnd">
        <DoubleAnimation Storyboard.TargetProperty="(UIElement.Effect).(local:BlindsShader.Progress)"
                            From="0"
                            To="100"
                            Duration="00:00:1.5" />
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Effect)">
            <DiscreteObjectKeyFrame KeyTime="00:00:1.5" Value="{x:Null}" />
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>
<Grid Background="{StaticResource imageBrush}" Effect="{StaticResource BlindsShader}">
    <Grid.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard x:Name="sbLoaded" Storyboard="{DynamicResource DefaultBlindsShaderStoryboard}" />
        </EventTrigger>
    </Grid.Triggers>
</Grid>

Shazzam Shader Editor

可以使用任何一款编辑器编写HLSL,然后使用fxc.exe命令行工具编译(visual studio 2022或者Windows SDK for Windows中含有该工具)。但是Shazzam Shader Editor是一个免费的专门为 WPF 实现像素着色器而设计的一款编辑器,使用它来编写像素着色器,可以自动生成WPF中的ShaderEffect。

Shazzam Shader Editor已经好久没有维护了,其官网似乎也没了。原本开源在CodePlex上,而 CodePlex 已经关闭。但JohanLarsson 将其 Fork 到了 GitHub 上,https://github.com/JohanLarsson/Shazzam。
也可以通过百度网盘获取安装包。

打开Shazzam Shader Editor,左侧显示着色器示例和全局设置(默认折叠)。选中具体的着色器后,右侧区域上方显示着色其效果,下方选项卡分别显示HLSL代码编辑窗口、预览调节窗口、生成的C#代码和生成的VB代码。
[WPF]使用HLSL实现百叶窗动效-LMLPHP

HLSL代码编辑窗口

HLSL代码文件是以.fx作为后缀名。编译后的文件后缀名是.ps。编辑窗口中可以编辑修改代码,按下F5就可以编译你的HLSL代码,并在界面上方预览效果。编辑器中会高亮关键词和方法,双击不要松开鼠标会弹出相应的提示。如何编写HLSL代码可以查阅HLSL and Pixel Shaders for XAML Developers这本书,Shazzam Shader Editor中左侧示例中的Tutorial也是配合该书使用的。

预览调节窗口

在这里可以设置各种预览参数,预览HLSL代码的效果。
[WPF]使用HLSL实现百叶窗动效-LMLPHP

生成的C#代码

这里是Shazzam Shader Editor自动生成的用C#编写的ShaderEffect,本文前边提到的百叶窗效果的像素着色器代码也就是从这里直接拷贝过去的。这里的代码默认的命名空间是Shazzam.Shaders,代码缩进是用Tab。可以在主窗体左侧的全局设置中修改。
[WPF]使用HLSL实现百叶窗动效-LMLPHP

生成的VB代码

这里和生成C#代码一样,只是提供VB语言编写的ShaderEffect。

在WPF中使用用HLSL

Shazzam Shader Editor编译HLSL后会生成XXX.psXXX.csXXX.vb三个文件,并保存在%LocalAppData%\Shazzam\GeneratedShaders目录下的XXXEffect目录中。这里的XXX就是你定义的HLSL的名称。
在WPF中使用时,需把XXX.ps文件以Resource的形式添加到工程中,然后把XXX.cs文件添加到工程,并根据项目结构,修改XXX.cs中引用XXX.ps文件的路径即可。

09-08 00:40