65.9K
CodeProject 正在变化。 阅读更多。
Home

开始使用 WPF 中的着色器效果

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (30投票s)

2010年7月15日

CPOL

14分钟阅读

viewsIcon

178664

downloadIcon

4948

您需要了解的所有关于 Windows Presentation Foundation 中的 GPU 加速效果

引言

WPF 的硬件加速效果最早在 .NET 3.5 SP1 中引入。得益于现代图形卡的巨大计算能力,可以创建非常复杂的效果和图形丰富的应用程序,而对性能影响甚微。但是,如果您想利用此功能,首先需要了解一些知识。本文旨在提供您开始使用效果所需的所有信息。

什么是效果?

效果是一个易于使用的 API,用于创建(令人惊讶的)图形效果。例如,如果您希望按钮投射阴影,有几种方法可以完成此任务,但最简单有效的方法是将按钮的“Effect”属性设置为代码或 XAML 中的值。

MyButton.Effect = new DropShadowEffect() { ... };
<Button Name="MyButton" ... >
  <Button.Effect>
    <DropShadowEffect ... />
  </Button.Effect>
</Button>  

如您所见,效果非常易于使用,您不需要任何进一步的解释。当您决定编写自己的效果时,乐趣就开始了...

BitmapEffect、Effect、ShaderEffect... 什么?

首先,有几个 .NET 类共享“Effect”后缀,为了让事情更具混淆性,它们都位于 System.Windows.Media.Effects 命名空间中。然而,并非所有这些类在硬件加速方面都很有用,实际上其中一些完全没用。

BitmapEffect

BitmapEffect 类及其子类最初旨在提供效果的功能。然而,此 API 不使用任何硬件加速,并且在 .NET 4.0 中已被标记为过时。强烈建议避免使用 BitmapEffect 类或其任何子类!

Effect 及其派生类

如上所述,您可以通过设置控件的 Effect 属性来将效果应用于控件(该属性实际上是从 UIElement 继承的,如果您需要知道的话)。现在的问题是……Effect 属性需要设置什么?答案很简单——它是一个 Effect 类型的对象。

Effect 类是所有硬件加速效果的基类。它有三个子类:BlurEffectDropShadowEffectShaderEffect。前两个是 .NET 库中直接包含的即用型效果。ShaderEffect 类是所有自定义效果的基类。

为什么是 BlurEffect 和 DropShadowEffect?

为什么库中只有 2 种完全实现的效果,为什么这 2 种效果不派生自 ShaderEffect?我无法回答第一个问题,但我可以告诉您是什么让 BlurEffectDropShadowEffect 如此特别。

DropShadowEffectBlurEffect 都使用需要多次传递的复杂算法,但通常不可能实现多次传递效果。然而,微软的人员可能在 WPF 渲染引擎的非托管核心深处做了一些肮脏的技巧,创造了这两种效果。

注意:有可能创建一个单次模糊算法,但这种算法与多次模糊相比非常慢。总之,这两种效果以特殊方式实现可能还有更多原因。

它是如何工作的?

如果您想利用硬件加速,首先需要了解整个工作原理。

关于 GPU 架构的几句话

图形处理单元 (GPU) 的架构与 CPU 的架构不同。GPU 不是通用目的的,它们被设计用于对大型数据集执行简单操作。操作以高度并行的方式执行,从而产生出色的性能。

现代 GPU 变得越来越可编程,并且可以在 GPU 上执行的任务范围正在扩大(尽管存在下面描述的几项限制)。在 GPU 上执行的小程序称为着色器。有几种类型的着色器——顶点着色器几何着色器用于渲染 3D 对象(WPF 效果不使用),而像素着色器用于对像素执行简单操作。

甚至还有尝试利用 GPU 的计算能力进行通用编程……不幸的是,存在几项限制,例如单个程序中的指令数量有限、无法处理高级数据结构、内存管理能力有限等。惊人的速度伴随着一些权衡……

像素着色器

像素着色器是一个简短的程序,它定义了在输出图像的每个像素上执行的简单操作。这就是创建各种有趣的基于像素的效果所需的大部分内容。

在编写您的第一个效果之前...

WPF 对象,包括Effects,都是使用 DirectX 引擎渲染的。DirectX 着色器是用 高级着色器语言 (HLSL) 编写的,然后编译成字节码。因此,HLSL 是您需要学习以编写自己的效果之一(有关 HLSL 的更多信息,请参阅本文)。

有些人会告诉您需要下载并安装整个 DirectX SDK 才能编译 HLSL 代码。幸运的是,事实并非如此。您需要下载 Greg Schechter 和 Gerhard Schneider 编写的 Visual Studio 插件。它被称为 Shader Effects BuildTask,您可以从 CodePlex WPF 网站获取。据报道,此插件适用于 Visual Studio 2008 和 2010。

安装插件后,Visual Studio 中将出现一个名为“WPF Shader Effect Library”的新项目模板。此插件最好的地方在于您可以直接在 Visual Studio 中编写 HLSL 代码(尽管没有智能感知支持和语法高亮显示),并且在构建项目时,所有着色器都会自动编译。

第一个简单效果

我们开始吧!如果您已经下载并安装了上面提到的 Shader Effects BuildTask,您可以打开本文附带的项目。

每个效果有 2 部分:一个用 HLSL 语言编写的像素着色器(扩展名为 .fx 的文件)和一个派生自 ShaderEffect 的类(.cs 文件),它充当像素着色器的托管包装器。构建项目时,所有 .fx 文件都会被编译,生成的像素着色器(扩展名为 .ps)将被包含在程序集中。

BuildTask.jpg

如果选择一个 .fx 文件并打开“属性”窗口,您会看到该文件的“生成任务”设置为“Effect”(请参阅右侧的图像)。这确保了效果将被正确编译。重要提示:向项目中添加新效果时,其生成任务属性不会自动设置,您需要手动更改它!

我将描述的效果很简单——它被称为“Transparency”,并有一个参数“Opacity”。它根据此参数使控件半透明。请忽略这样一个效果完全无用的事实……

创建像素着色器

让我们从难的部分开始:首先是像素着色器。“Transparency.fx”包含以下代码

sampler2D implicitInputSampler : register(S0);
float opacity : register(C0);

float4 main(float2 uv : TEXCOORD) : COLOR {
  float4 color = tex2D(implicitInputSampler, uv);
  return color * opacity;
}

前两行包含像素着色器常量。第一个常量是 sampler2D 类型,它引用应用了此效果的图像。是的,我知道效果是应用于控件(不一定是 Image),但“图像”一词指的是目标控件的视觉表示……

另一个常量是我们的自定义输入参数(称为“opacity”),它是 float 类型。尽管此参数的值可以随时间变化,但在像素着色器的范围内,它被视为常量。如上所述,像素着色器对每个像素执行一次,并且一帧中的所有像素需要相同的输入参数——这就是为什么“opacity”被视为常量。

register 关键字用于将每个常量与存储输入值的寄存器关联起来。有几个“图像寄存器”包含输入图像数据,这些寄存器命名为 S0S1S2 等(大多数像素着色器只使用一个这样的寄存器)。还有“浮点寄存器”命名为 C0C1C2 等,这些寄存器存储其他输入参数的值。

着色器的其余部分是算法本身。有一个名为 main 的方法,这是我们着色器程序的入口点。此方法接受一个 float2 类型的参数并返回 float4(具有此名称和签名的函数必须在每个像素着色器中)。此方法的返回类型是一个由 4 个浮点值组成的向量,表示 RGBA 颜色。方法参数是一个二维向量,您可以将其视为“当前像素的 x 和 y 坐标”。实际上,这些值不是基于像素的:左上角的坐标是 (0, 0),右下角由 (1, 1) 表示。

该方法的主体非常简单——通过调用 tex2D 方法找到源颜色,将其乘以透明度并返回。当“*”运算符用于将标量值与向量相乘时,该向量的所有分量都乘以标量。您可能认为只有 alpha 通道应该乘以得到正确结果。然而,DirectX 着色器使用预乘 alpha 通道,这意味着 RGB 通道的值始终乘以 alpha 通道。

Effect 类

让我们看一下“Transparency”效果的另一部分。“Transparency.cs”包含以下类

public class Transparency : ShaderEffect {
  static Transparency() {
    // Associate _pixelShader with our compiled pixel shader
    _pixelShader.UriSource = Global.MakePackUri("Transparency.ps");
  }

  private static PixelShader _pixelShader = new PixelShader();

  public Transparency() {
    this.PixelShader = _pixelShader;
    UpdateShaderValue(InputProperty);
    UpdateShaderValue(OpacityProperty);
  }

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

  public static readonly DependencyProperty InputProperty =
      ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(Transparency), 0);

  public double Opacity {
    get { return (double)GetValue(OpacityProperty); }
    set { SetValue(OpacityProperty, value); }
  }

  public static readonly DependencyProperty OpacityProperty =
      DependencyProperty.Register("Opacity", typeof(double), typeof(Transparency),
        new UIPropertyMetadata(1.0d, PixelShaderConstantCallback(0)));
}

正如您所见,这基本上是一个普通的类,带有几个依赖属性。但是,有几件重要的事情我必须指出。

像素着色器存储在 private static 字段 _pixelShader 中。此字段是 static 的,因为该类的已编译着色器代码的一个实例就足够了。有一个 static 构造函数用于初始化 _pixelShaderUriSource 属性——这基本上告诉 _pixelShader 在哪里查找已编译的着色器字节码。Global.MakePackUri() 方法是一个辅助方法,它将文件名转换为完整的 uri 路径,该路径大致如下:"pack://application:,,,/[assemblyname];component/Transparency.ps"。(我相信您明白为什么需要一个辅助方法)。

必须有一个名为“Input”的 Brush 类型属性。此属性包含输入图像,通常不直接设置——它在我们效果应用于控件时自动设置。相应的依赖属性不是通过调用 DependencyProperty.Register() 来初始化的,而是必须使用 ShaderEffect.RegisterPixelShaderSamplerProperty() 方法。注意此方法的最后一个参数:它是一个整数,对应于 S0 像素着色器寄存器。

另一个属性是我们自定义参数“Opacity”。它像任何其他依赖属性一样声明,唯一区别在于 UIPropertyMetadata 构造函数中的 PropertyChangedCallback 的值。该值必须是 PixelShaderConstantCallback(),并且整数参数必须是对应浮点寄存器的编号(请注意,值“0”对应于寄存器名称 C0)。

最后一件需要解释的重要事情是我们类的构造函数。它设置 PixelShader 属性(这是必需的),并强制着色器更新所有输入值。

关于 HLSL 的几点说明

如您所见,HLSL 语言是一种简单的 C 语言。可以使用常见的 C 运算符(+、-、*、/ 等)以及 许多数学函数。还可以使用代码流控制语句(如 ifwhilefor),完整的列表可以在 此处找到。

在 WPF 像素着色器中最常用的类型是 float 和基于 float 的向量(float2float3float4)。有关 HLSL 向量的详细说明(以及如何使用它们)可以在 此处找到。在当前版本的 WPF 像素着色器中没有 intbool 类型(请参见下表)。

接受的参数类型

下表显示了所有允许的输入类型(在 ShaderEffect 类中定义)和相应的 HLSL 类型(在像素着色器中定义)。目前只允许浮点值。

.NET 类型 HLSL 类型
System.Boolean (C# 关键字 bool) 不可用
System.Int32 (C# 关键字 int) 不可用
System.Double (C# 关键字 double) float
System.Single (C# 关键字 float) float
System.Windows.Size float2
System.Windows.Point float2
System.Windows.Vector float2
System.Windows.Media.Media3D.Point3D float3
System.Windows.Media.Media3D.Vector3D float3
System.Windows.Media.Media3D.Point4D float4
System.Windows.Media.Color float4

更进一步

如果您已阅读以上所有内容,您就了解了效果的基础知识。在您开始创建自己的效果之前,这里有一些您可能想知道的重要事项:

动画

效果是基于 DependencyProperty 的,并且可以像任何其他 WPF 元素一样进行动画处理。

位移

效果的功能远不止改变像素的颜色。请参见以下示例

float4 main(float2 uv : TEXCOORD) : COLOR {
  uv = uv / 2;
  float4 color = tex2D(implicitInputSampler, uv);
  return color;
}

在获取源颜色之前,将 uv 值除以 2,这将把目标控件的左上角四分之一拉伸到控件的整个区域。可以使用更复杂的变换来创建有趣的效果。

您甚至可以创建一个能够正确响应用户输入(如鼠标悬停等)的位移效果——您只需要设置效果类的 Transform 属性即可。

多输入效果

效果可以有几个输入图像来执行高级混合操作。这超出了本文的范围,但您可以在 Greg Schechter 的博客上找到对此技术的详细说明。

常见错误

从 Visual Studio 编译像素着色器的方式会引入几个潜在的错误。这些错误不会导致任何编译时错误,并且有时很难找到。

  • 首先,在库中添加新的 Shader Effect 时,请勿忘记将新的 .fx 文件的“生成任务”设置为“Effect”。如果您忘记这样做,您的着色器将不会被编译,并且您的应用程序在尝试使用该效果时将崩溃。
  • 另一个可能的问题是编译效果如何包含在托管程序集中。如果您更改项目的项目结构,或者重命名效果源文件,请记住在关联的 PixelShader 构造函数中更改文件路径,否则当您的应用程序尝试创建效果实例时将崩溃。
  • 如果向效果添加了新参数,请不要忘记在效果类的构造函数中添加一个新的 UpdateShaderValue 方法调用。否则,您的效果可能会使用错误的默认值。
  • 定义效果参数的默认值时要小心,因为默认值类型必须与参数类型完全匹配。如果一个属性是 double 类型,您不能简单地使用整数文字(例如“1”)作为其默认值,您必须使用双精度值,如“1.0”或“1d”。

推荐资源

Greg Schechter 撰写了一系列出色的文章,内容涉及 GPU 驱动的效果,到目前为止,这是我找到的最佳资源。该系列包括几个示例,解释了如何创建多输入效果等等。

Walt Ritscher 创建了一个很棒的工具,名为 Shazzam,这是一个用于着色器效果的交互式开发工具。Shazzam 允许您编写一个效果,将其应用于任何图像并交互式地更改所有输入参数。它甚至会为您生成相关的 C#/VB 代码。请访问 Shazzam 官方页面了解更多信息。(非常感谢Sacha Barber将 Shazzam 推荐给我。)

Nick Darnell 编写了 WPF ShaderEffect Generator,这是一个 Visual Studio 插件,允许您直接在 VS 中编写和编译着色器,是 Greg Schechter 和 Gerhard Schneider 的 BuildTask 的绝佳替代品。主要区别在于 Nick Darnell 的插件从完成的 HLSL 代码自动生成所有 C# 类(功能与 Shazzam 非常相似)。感谢U-P-G-R-A-Y-E-D-D发布此项目的链接!

Tamir Khason 创建了一个名为 HLSL Tester 的小型程序。如果您是 HLSL 语言的初学者,这个应用程序将非常有帮助。它允许您加载一个位图图像,然后编写简单的像素着色器,进行调试并交互式地将它们应用于图像。唯一的缺点是此应用程序需要安装 DirectX SDK

WPF Pixel Shader Effects Library 是一个高质量的即用型效果的开源库。该库(包括源代码)可以从 CodePlex 下载:http://wpffx.codeplex.com/

示例项目

Transparency 效果以及一个简单的 WPF 测试应用程序可以 此处下载。

结束

就是这样。我希望本文为您提供了创建自己的出色硬件加速效果所需的一切。我建议您关注上面“推荐资源”部分中提到的链接。

我很乐意回答您所有的问题,并非常感谢您的反馈。

版本历史

  • 编辑于 2010-07-24:向推荐资源部分添加了链接
© . All rights reserved.