WPF使用PixelShader需编译ps_2_0/ps_3_0的.ps文件、继承ShaderEffect并声明匹配的DependencyProperty,VisualBrush坐标需手动归一化,性能受限于CPU模拟与96指令上限。

WPF中使用PixelShader需要哪些前置条件
必须有编译好的.ps文件(HLSL像素着色器),且目标模型为ps_2_0或ps_3_0(WPF不支持ps_4_0+)。WPF的ShaderEffect类只支持纯像素着色器,不支持顶点着色器或计算着色器。项目需引用PresentationCore和System.Windows.Media.Effects命名空间。
常见错误:InvalidOperationException: “The pixel shader bytecode is invalid.”——多数因HLSL编译未指定/target ps_2_0,或使用了WPF不支持的语义(如SV_Position)。
- 用
fxc.exe编译时加参数:fxc /T ps_2_0 /E main /Fo effect.ps effect.hlsl -
.ps文件需设为Resource生成操作(不是Content) - 着色器入口函数名必须是
main(WPF硬编码识别)
如何继承ShaderEffect并暴露着色器参数
不能直接实例化ShaderEffect,必须子类化,并在构造函数中加载PixelShader。每个可绑定的参数(如float、float2、Brush)需声明为DependencyProperty,并在UpdateShaderValue中触发更新。
关键点:WPF只识别DependencyProperty字段名与HLSL中register绑定名一致(大小写敏感),且类型必须匹配(例如float4 → Color,float2 → Point)。
- HLSL中写
float4 color : register(C0);,C#中就要定义public static readonly DependencyProperty ColorProperty = ... - 对
Brush类型参数,WPF会自动将其转为纹理采样器,HLSL中对应sampler2D+float2坐标 - 避免在
PropertyChangedCallback里调用UpdateShaderValue以外的逻辑——它只负责同步值,不触发重绘
VisualBrush作为输入纹理时的坐标陷阱
当把VisualBrush传给着色器(如Input或自定义Texture属性),其UV坐标范围不是[0,1],而是以源Visual实际像素尺寸为基准。若控件缩放或RenderTransform介入,坐标会偏移或拉伸。
典型现象:着色器本应做中心模糊,结果模糊区域随窗口拉伸跑偏。根本原因是WPF未自动归一化VisualBrush的RelativeTransform。
- 稳妥做法:在HLSL中手动归一化,用
texelSize(传入的float2)反推原始尺寸 - 或改用
ImageBrush+RenderTargetBitmap预渲染,确保输入是稳定尺寸位图 - 切勿依赖
VisualBrush.Viewbox来“矫正”——它不影响着色器采样坐标
性能瓶颈常出现在哪里
WPF着色器运行在CPU模拟环境(除非启用EnableGPUAcceleration且驱动支持),ps_2_0指令数上限仅96条。一个for循环展开后极易超限,if嵌套过深也会被编译器拒绝。
更隐蔽的问题是:每次绑定属性变更都会触发整个视觉树重绘,哪怕只是改了个Opacity。如果ShaderEffect套在ListBoxItem上,滚动即卡顿。
- 用
PerfView抓Composition.Draw耗时,确认是否真由着色器引起 - 避免在
ControlTemplate中无条件应用Effect;用Trigger按需启用 - 复杂效果拆解:先用
DropShadowEffect或BlurEffect替代部分逻辑,它们是硬件加速的
真正难调的不是语法,是HLSL语义与WPF渲染管线之间的隐式契约——比如Input默认走premultiplied alpha,但你自己传的Brush未必是。










