使用反射控制像素着色器的参数






4.89/5 (9投票s)
使用反射控制像素着色器的参数。
引言
目前,我正在开发一个图像编辑器,该编辑器将使用像素着色器效果。为了节省手动硬编码每个效果控件界面的大量时间,我构建了一个简单的基于反射的效果控制器系统。本文将介绍其背后的技术。这是我首次在此发布文章。
代码
反射控件是通过一些自定义属性实现的。反射代码会查找这些属性,并根据属性中包含的信息来构建用户界面。
属性的UML图如下所示
ShaderVersion
属性指定了着色器的版本。这是必需的,因为在WPF 4.0中,像素着色器级别2.0和3.0都受支持,因此您需要检查渲染硬件是否支持该级别。
IValueController
接口为参数控制指定了一个通用接口。DoubleCovnverter
和Point2DValueConverter
都实现了该接口。它们用于指定效果参数的最小值、最大值和默认值。此外,它们还包含一个将在用户界面上显示的描述。主要的反射代码位于一个名为EffectOptionDialog
的单独用户控件中。其XAML非常简单
<UserControl x:Class="RefactorShaderControl.EffectOptoonDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="300"
d:DesignWidth="300">
<ScrollViewer>
<StackPanel x:Name="ControlRoot"/>
</ScrollViewer>
</UserControl>
它定义了一个封装在ScrollViewer
中的StackPanel
。着色器的控件将被添加到此StackPanel
中。该控件的代码如下所示
public partial class EffectOptoonDialog : UserControl
{
public EffectOptoonDialog()
{
InitializeComponent();
}
/// <summary>
/// Controlled effect defined as a deoendency property to support data binding
/// </summary>
public static readonly DependencyProperty ControledEffectProperty =
DependencyProperty.Register("ControledEffect",
typeof(ShaderEffect), typeof(EffectOptoonDialog));
/// <summary>
/// Gets or sets the Controlled Effect of this control
/// </summary>
public ShaderEffect ControledEffect
{
get { return (ShaderEffect)GetValue(ControledEffectProperty); }
set
{
SetValue(ControledEffectProperty, value);
Clear();
if (value != null) Build();
}
}
/// <summary>
/// Removes all controlls & collects garbage
/// </summary>
private void Clear()
{
ControlRoot.Children.Clear();
GC.Collect();
}
/// <summary>
/// Builds the user interface
/// </summary>
private void Build()
{
Type effType = ControledEffect.GetType();
bool hwsupport = true;
Attribute[] memberatribs;
Attribute[] effatribs = Attribute.GetCustomAttributes(effType);
// Seerch the class attributes for a ShaderVersion attribute.
// If the Level is not supported by the hardware a messagebox is shown
foreach (Attribute atrib in effatribs)
{
if (atrib is ShaderVersion)
{
ShaderVersion v = (ShaderVersion)atrib;
if (!RenderCapability.IsPixelShaderVersionSupported(v.Major, v.Minor))
{
MessageBox.Show("Pixel shader level " + v.ToString() +
" required by this effect is not supported by your hardware",
"Warning", MessageBoxButton.OK, MessageBoxImage.Error);
hwsupport = false;
}
}
}
// if no hw support found UI construction stopped
if (!hwsupport) return;
MemberInfo[] members = effType.GetMembers();
//label for description
Label descrpt;
//cycle trough all members
foreach (MemberInfo member in members)
{
//only check properties
if (member.MemberType == MemberTypes.Property)
{
memberatribs = Attribute.GetCustomAttributes(member);
//get attributes of the property
foreach (Attribute atr in memberatribs)
{
PropertyInfo pi = effType.GetProperty(member.Name);
//property is a double value
if (atr is DoubleValueContoller)
{
DoubleValueContoller ctrl = (DoubleValueContoller)atr;
//create label with description
descrpt = new Label();
descrpt.Content = ctrl.Description;
ControlRoot.Children.Add(descrpt);
//create a slider for it
//and set it's properties
Slider slider = new Slider();
slider.Minimum = ctrl.Minimum;
slider.Maximum = ctrl.Maximum;
slider.Value = ctrl.Curent;
slider.Margin = new Thickness(10, 0, 0, 0);
//bind the slider to the value
Binding doublebind = new Binding();
doublebind.Source = ControledEffect;
doublebind.Mode = BindingMode.TwoWay;
doublebind.Path = new PropertyPath(pi);
slider.SetBinding(Slider.ValueProperty, doublebind);
ControlRoot.Children.Add(slider);
}
//property is a 2D Point value (float2 in HLSL)
else if (atr is Point2DValueController)
{
Point2DValueController pctrl = (Point2DValueController)atr;
//create label with description
descrpt = new Label();
descrpt.Content = pctrl.Description;
ControlRoot.Children.Add(descrpt);
//create a for it
//and set it's properties
Point2DContol pointc = new Point2DContol();
pointc.Maximum = pctrl.Maximum;
pointc.Minimum = pctrl.Minimum;
pointc.Value = pctrl.Curent;
pointc.Margin = new Thickness(10, 0, 0, 0);
//bind the slider to the value
Binding pointbind = new Binding();
pointbind.Source = ControledEffect;
pointbind.Mode = BindingMode.TwoWay;
pointbind.Path = new PropertyPath(pi);
pointc.SetBinding(Point2DContol.ValueProperty, pointbind);
ControlRoot.Children.Add(pointc);
}
}
}
}
}
}
它的功能
此反射代码首先列出指定的Shader
类的属性。如果在此类属性中找到ShaderVersion
类,则会检查图形硬件是否支持该级别。如果不支持,则会停止用户界面的创建,因为没有继续的意义。
对于像素着色器级别2.0,有一个软件渲染的回退机制;然而,当您在慢速CPU上运行代码并使用大图像时,这会非常缓慢。对于级别3.0,不提供软件回退。
在此之后,代码会查找Shader
类中具有DoubleValueController
或Point2DValueController
属性的属性。如果找到一个,它将为该属性创建一个控件,并将Shader
的属性绑定到Control
的值。我使用双向绑定,因为这样,如果您从代码中更改其中一个受控值,它将在GUI中得到反映。
需要牢记的一点是,您只能在依赖属性上使用绑定。因此,如果您创建自定义控件,请始终将它们的属性实现为依赖属性。
Using the Code
控件可以这样使用
BloomEffect eff = new BloomEffect();
EffectTarget.Effect = eff;
EffectOptions.ControledEffect = eff;
构建您自己的值控制器
对于其他像素着色器属性类型(float3
和float4
,它们在.NET中映射为Point3D
和Color
类),您需要构建自己的控制器,然后您就可以基于此代码控制各种类型的着色器。
一个好的入门示例可能是Point2DControl
。
<UserControl x:Name="Point2D"
x:Class="RefactorShaderControl.Controls.Point2DContol"
xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
xmlns:mc=http://schemas.openxmlformats.org/markup-compatibility/2006
xmlns:d=http://schemas.microsoft.com/expression/blend/2008
mc:Ignorable="d" Height="50" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Content="X:" Grid.Column="0" Grid.Row="0"/>
<Label Content="Y:" Grid.Column="0" Grid.Row="1"/>
<Slider x:Name="XValue" ValueChanged="ValueChanged"
Grid.Row="0" Grid.Column="1"/>
<Slider x:Name="YValue" ValueChanged="ValueChanged"
Grid.Row="1" Grid.Column="1"/>
</Grid>
</UserControl>
public partial class Point2DContol : UserControl
{
bool exec;
public Point2DContol()
{
InitializeComponent();
Maximum = new Size(10, 10);
Minimum = new Size(0, 0);
Value = new Size(0, 0);
exec = true;
}
public static readonly DependencyProperty MinimumProperty =
DependencyProperty.Register("Minimum", typeof(Size), typeof(Point2DContol));
public static readonly DependencyProperty MaximumProperty =
DependencyProperty.Register("Maximum", typeof(Size), typeof(Point2DContol));
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(Size), typeof(Point2DContol));
/// <summary>
/// Control Minimum value
/// </summary>
public Size Minimum
{
get { return (Size)GetValue(MinimumProperty); }
set
{
SetValue(MinimumProperty, value);
XValue.Minimum = value.Width;
YValue.Minimum = value.Height;
}
}
/// <summary>
/// Control maximum value
/// </summary>
public Size Maximum
{
get { return (Size)GetValue(MaximumProperty); }
set
{
SetValue(MaximumProperty, value);
XValue.Maximum = value.Width;
YValue.Maximum = value.Height;
}
}
/// <summary>
/// Control value
/// </summary>
public Size Value
{
get { return (Size)GetValue(ValueProperty); }
set
{
SetValue(ValueProperty, value);
if (exec)
{
XValue.Value = value.Width;
YValue.Value = value.Height;
}
else exec = true;
}
}
private void ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
exec = false;
Value = new Size(XValue.Value, YValue.Value);
}
}
这是一个非常简单的控件;它封装了两个滑块控件,用于控制X轴和Y轴(宽度和高度)的值。从示例中可以看到,每个滑块的值都是从代码设置的,而不是绑定的。之所以这样做,是因为我无法以其他方式使其正常工作。在ValueChanged
事件和Value
设置代码中使用的exec
变量用于防止循环引用导致的堆栈溢出。我知道这不是最优雅的做法,但我还不是WPF专家。
应用程序
完成的应用程序有三个效果,看起来像这样
它有三种效果:两种颜色效果和一种扭曲效果。通道混合器是我自己创建的,而其他效果则直接取自Shazzam工具库。源代码也包含了这些效果的源代码。为了最大化兼容性,我只使用了像素着色器级别2.0的效果。程序中使用的图像是我家乡附近拍摄的。
关注点
- 要学习像素着色器,我推荐Ren© Schulte的Silverlight和WPF像素着色器入门。这篇优秀的文章可以在这里找到。
- Shazzam工具,一个用于创建WPF和Silverlight像素着色器的专业工具:http://shazzam-tool.com/。
- 如果您对C#中的反射还不熟悉,那么以下CodeProject文章是一个不错的起点:https://codeproject.org.cn/KB/cs/introreflection.aspx。
- MSDN页面,关于在C#中使用反射:http://msdn.microsoft.com/en-us/library/ms173183%28VS.80%29.aspx。
- MSDN HLSL指令完整参考:http://msdn.microsoft.com/en-us/library/ff471376%28v=VS.85%29.aspx。
- http://blogs.msdn.com/b/coding4fun/archive/2010/05/25/10014965.aspx.