使用像素着色器实现WPF位图效果 .Net3.5sp1 Beta






4.68/5 (14投票s)
演示如何使用.Net3.5 Sp1 Beta中提供的最新类来编写位图效果。
引言
本文将介绍一种使用显卡硬件像素着色器实现硬件加速位图效果的替代开发方法。本文以及所有相关代码,以及C#和WPF上的许多其他文章都可以在我的博客 这里 找到。
希望您喜欢我的第一篇代码项目文章!请温柔对待!;)
软件先决条件
首先,如果您还没有安装,您将需要 DirectX SDK。它包含了编译HLSL文件为运行时所需的字节码的所有重要编译器工具。我们将把这个工具链接到devstudio环境,但不是通过自定义构建步骤。如果您阅读了Greg的评论,微软的工程师们正在忙于为这个目的修复一个很好的MSBuild目标,所以我不会花太多时间将其硬塞进这个环境。
接下来,我们需要一个工具来帮助我们在开发过程中可视化我们的着色器。为此,我们需要回到显卡制造商的开发者页面。
由于我使用的是nVIDIA 8800GTX,我使用nVIDIA的 FX Composer 2.5。FX Composer 2.5是他们最新的着色器开发工具(目前也处于Beta阶段 - 嘿,你能看出我是Beta软件的粉丝吗?!哈哈!)并且可以从他们的开发者网站免费下载。
我使用nVIDIA硬件已经有一段时间了,因此我一直使用FX Composer来探索他们的硬件,但如果您是ATI用户,他们也有一个执行类似功能的工具—— Render Monkey。
接下来,您需要下载 .Net3.5sp1 和 Visual Studio Service Pack 1 Beta。
请注意,这是Beta软件,并且正如我所遇到的情况一样,可能会 完全搞乱您的系统。不过,我设法 解决了我的问题,但不要在生产环境的机器上安装这些!您已被警告!
好的,软件方面就介绍到这里。
知识先决条件。
您需要了解什么?嗯,显然您需要对C#和WPF有很好的理解。
如果您真的想从头开始理解如何编写着色器,您需要对HLSL有合理的理解,并且对视频硬件如何工作有很好的基础。由于我们(可能)不会处理顶点着色器,所以不需要太多理解与渲染相关的3D内容,但是,一如既往,了解幕后发生的事情会很有用。
以下是一些入门的好链接。
请记住,由于我们现在使用的许多图形技术都属于游戏开发领域,因此您不妨查阅游戏开发网站和论坛。例如,nVIDIA有一个相对活跃的 HLSL/着色器开发 论坛。
最后,如果您真的想了解着色器,亚马逊上有一些很棒的书籍。
![]() |
Shader X6: Advanced Rendering (Shaderx) 作者:Wolfgang Engel 阅读更多关于这本书的信息... |
![]() |
Shaders for Game Programmers and Artists (Premier Press Game Development) 作者:Sebastien St-Laurent 阅读更多关于这本书的信息... |
最后,虽然比较旧,但仍然非常有用的
![]() |
The COMPLETE Effect and HLSL Guide 作者:Sebastien St-Laurent 阅读更多关于这本书的信息... |
在网上搜索这些作者的文章也很有价值。
总之,说得再多不如行动,展示代码!
像素着色器101
在本篇文章中,我们将编写一个简单的去饱和着色器。人们已经习惯于将灰色的UI元素识别为已禁用状态,而偏离这一标准肯定会造成混淆。使用像素着色器,您可以将任何WPF元素显示为禁用状态,而不是必须费力地将颜色动画化为灰色,或者制作不同的控件(甚至更糟糕的是位图!)来显示禁用状态。我之前写过一个 软件版本 的这个着色器,但由于是软件(而且是托管代码!),速度非常慢,所以希望这些硬件加速版本能够提高性能,并使其在更大的区域内可用。现在可以使用像素着色器效果(例如这些)来使整个应用程序窗口变灰(想想Windows XP关机屏幕会使您的桌面变灰)。
首先,我们将看看如何设置我们的环境。
环境设置
为此,我们需要配置devstudio的外部工具之一来调用Microsoft的HLSL编译器fxc.exe,它随DirectX SDK一起安装。
在这里,我们有一个新的外部工具设置为从Devstudio中的“工具”菜单调用FXC.EXE。这意味着当我们选择着色器文件时,我们可以在不离开环境的情况下执行该工具并将其编译成着色器。虽然不如自定义构建步骤集成,但至少我们不必退出,希望Greg的团队能在这方面帮助我们。
对于命令,请在DirectX SDK安装文件夹中找到FXC.EXE可执行文件。由于我使用了DirectX SDK的默认安装选项,我的FXC位于以下路径:
C:\Program Files (x86)\Microsoft DirectX SDK (March 2008)\Utilities\Bin\x64\fxc.exe
接下来,我们需要添加参数来让FXC编译我们的着色器。我的参数如下:
/DWPF /Od /T ps_2_0 /E $(ItemFileName)PS /Fo$(ProjectDir)$(ItemFileName).ps $(ItemPath)
您可以通过在命令行中键入fxc来查看FXC接受的参数。对于我的参数,我们有:
- /DWPF
在着色器文件中定义了宏常量“WPF”。我们可以使用它来确定着色器文件中哪些代码部分应该为WPF项目编译,而不是用于FXComposer文件。我们稍后会回到这一点。 - /T ps_2_0
指示编译器以着色器配置文件2.0进行编译。 - /E $(ItemFileName)PS
这设置了我们着色器的入口点。在这里,我将.Fx文件的文件名与PS连接起来,因此在我们的着色器代码中,WPF的入口点将如下所示:
DesaturatePS - /Fo$(ProjectDir)$(ItemFileName).ps
指示FXC的输出放在项目文件夹中,与将加载编译后字节码的类放在一起。
以上就是从devstudio内部执行该工具时的命令行参数。
接下来,我们将启动FX Composer并开始开发着色器。
我的FX Composer看起来是这样的:
在这里,我们可以看到完成的着色器,对FX Composer中提供的标准 犹他茶壶 进行了去饱和处理。
我不会详细介绍如何使用FX Composer,它是一个相当复杂的工具,我们只需要使用其工具集的极小部分即可达到我们的目的。
如果您下载了本文配套的解决方案文件,请打开位于“Shaders”子文件夹中的fx composer项目。
这将加载着色器代码,以及我已应用于茶壶的一些预设材质,以便我们测试我们的工作。
以下是一些提示:
- 在“渲染”窗口中使用鼠标,通过按住ALT+左键单击来旋转对象,使用鼠标滚轮进行缩放。
- 渲染窗口的工具栏包含重置按钮,如果您搞乱了!单击对象,然后选择“缩放选定对象范围”按钮。
- 在主工具栏上,单击“编译”按钮以重新编译并立即可视化您对着色器代码所做的任何更改。
- 在“材质”窗口中,单击材质(效果)进行选择,然后在属性窗口(右上角)中修改其参数。对于“去饱和”效果,有一个“饱和度”滑块可以实时去饱和茶壶。
- 您可以在“材质”窗口中右键单击,从几个默认着色器创建新的着色器进行尝试和修改。添加新着色器后,只需单击并将其拖到渲染窗口即可将其应用于对象或整个场景。后处理着色器如果不正确地转换对象,可能会导致出现一个巨大的、混乱的茶壶。按Ctrl+Z撤销着色器应用,或在材质窗口中选择着色器并按Delete。
我们的着色器是利用FX Composer编写的,并且因为我们可以传入宏常量,所以我们可以使用完全相同的着色器代码传递给FXC编译器。这意味着我们可以两全其美,唯一的缺点是需要传递一些命令行参数!
在不深入研究FX Composer中着色器代码的其他部分的情况下,有几点值得指出。
首先,着色器仅输入变量的条件编译。
#ifndef WPF float Saturation < string UIWidget = "slider"; float UIMin = 0.0f; float UIMax = 1.0f; float UIStep = 0.01f; string UIName = "Saturation"; > = 0.5f; #else float Saturation : register(c0); #endif
这里我们看到了在命令行编译器参数中使用/DWPF的用途。我们对其中一个着色器参数执行条件编译。
对于FX Composer,它只是被定义为一个浮点数(带有一个显示在属性窗口中的UI元素);然而,当我们从devstudio内部编译时,我们提供了一个HLSL语义,以确保我们的饱和度来自一个特定的寄存器,而这个寄存器恰好是我们C#包装类中的依赖属性所设置的。
我们还对传入的纹理数据采样器做了同样的处理。
#ifndef WPF sampler2D implicitSampler = sampler_state { texture = implicitTexture; AddressU = Clamp; AddressV = Clamp; MagFilter = Linear; MipFilter = POINT; MinFilter = LINEAR; MagFilter = LINEAR; }; #else sampler2D implicitSampler : register(s0); #endif
然后,在C#包装类内部,Greg博客中描述的隐式画笔(implicitBrush)被传递到采样器寄存器s0中。在这里,如果我们不在C#包装类内部,我们就使用FX Composer中设置的纹理来覆盖渲染窗口,以便我们立即看到效果。
最后,我们来看实际的像素着色器代码。
float4 DesaturatePS(float2 uv : TEXCOORD0) : COLOR { float3 LuminanceWeights = float3(0.299,0.587,0.114); float4 srcPixel = tex2D(implicitSampler, uv); float luminance = dot(srcPixel,LuminanceWeights); float4 dstPixel = lerp(luminance,srcPixel,Saturation); //retain the incoming alpha dstPixel.a = srcPixel.a; return dstPixel; }
首先,看看代码有多么简短!再加上它是在大规模并行硬件上执行的,这意味着我们可以轻松地在屏幕级别执行这些着色器,而几乎不会有明显的性能损失。
下面是逐行说明:
- 着色器声明——接受一个TEXCOORD(另一个HLSL语义)并返回一个float4 : COLOR。
- 声明一个float3,表示转换为灰度的系数。这些系数被广泛认为是标准的灰度系数,NTSC和JPEG都使用它们来进行灰度转换。
- 使用像素的u,v坐标从传入的纹理采样器中提取一个源像素。如果您不知道u,v坐标是什么,您需要查找一些关于纹理坐标的3D数学知识,但基本上它们相当于纹理中像素的X,Y坐标。
- 通过使用源像素和亮度系数的点积来计算亮度。
- 通过“饱和度”浮点数指定的量,计算灰色值(亮度)和源像素数据(srcPixel)之间的线性插值。(哇,一口气完成!)
- 注释!
- 因为我们不想对alpha通道进行灰度处理,所以我们只获取传入像素的alpha值,并将其未经修改地放入目标像素的alpha通道。
- 返回新像素。
很简单!
现在我们在FX Composer中有了着色器,我们可以编辑代码并重新编译,立即看到编译结果。在devstudio中,这一点更难做到,互动性也差得多,尤其是当您的着色器位于一个大型程序中,或者一个需要较长时间才能运行的程序中时。
以上就是FX Composer中的着色器代码。接下来是devstudio。
Devstudio中的着色器编码——C#支持代码
加载着色器解决方案文件后,打开位于“Shaders”子文件夹中的FXComposer项目文件。请注意,像素着色器的名称与源文件名相同,并附加了“PS”。这是为了让devstudio中的命令行将此识别为代码的入口点。
接下来,查看DesaturateEffect.cs文件。这是加载我们像素着色器的包装文件,并允许C#代码与之交互并向着色器传递参数。这部分在Beta之后最有可能发生变化,因为用于通信的一些寄存器是固定的,而且我们目前只能传递一个采样器,这意味着我们还不能做任何多纹理效果:)
我不会在这里深入介绍,因为Greg的博客上有很多关于C#支持代码的信息。不过,我将概述两个主要方面——用于将Shader寄存器与C#属性连接的依赖属性。
public static readonly DependencyProperty SaturationProperty = DependencyProperty.Register("Saturation", typeof(double), typeof(DesaturateEffect), new UIPropertyMetadata(1.0, PixelShaderConstantCallback(0)));
重要的部分是PixelShaderConstantCallback(0),它直接映射到着色器文件中的代码,将Saturation属性链接到寄存器C0。
float Saturation : register(c0);
相同的依赖属性模式也用于传递到寄存器s0的采样器(隐式画笔)。
这些依赖属性回调是允许C#将值传递到着色器代码的缺失环节。通过阅读Greg的博客,这种机制在Beta之后可能会发生变化,所以请留意!
构建代码
要构建解决方案,我们必须先编译像素着色器。
使用我们之前设置的自定义工具,转到解决方案资源管理器,并选择(左键单击).fx文件Desaturate.fx。这告诉外部工具它将操作哪个文件。
接下来,转到“工具”菜单,然后单击我们之前设置的工具,在选定的文件上运行FXC.exe。
如果您没有看到输出窗口,请显示它(Ctrl+Alt+O),您应该会看到类似以下内容:
Microsoft (R) D3D10 Shader Compiler 9.19.949.2185 Copyright (C) Microsoft Corporation 2002-2007. All rights reserved. compilation succeeded; see F:\ActiveProjects\ShaderEffects\DesaturateEffect\Desaturate.ps
FXC.exe已编译我们的.fx源代码,并将生成的字节码放入项目目录。这在C#项目文件中被设置为资源,因此它会被编译到程序集中。
接下来,构建整个解决方案,它现在应该会编译着色器库和测试应用程序。
完成后,我们就可以开始了。运行测试应用程序(它应该设置为启动应用程序),如果一切顺利,您应该会看到下面的窗口,当效果去饱和窗口时,其颜色会逐渐变淡和变亮。
请注意,现在您应该能够在像素着色器位图效果运行时将窗口拉伸到全屏,而性能几乎没有下降。
像素着色器太棒了!
如果您有任何评论、更正或建议,本文以及更多内容可在我的博客上找到:
尽情享用!
Rob