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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (9投票s)

2010年11月27日

CPOL

4分钟阅读

viewsIcon

32713

downloadIcon

500

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

引言

目前,我正在开发一个图像编辑器,该编辑器将使用像素着色器效果。为了节省手动硬编码每个效果控件界面的大量时间,我构建了一个简单的基于反射的效果控制器系统。本文将介绍其背后的技术。这是我首次在此发布文章。

代码

反射控件是通过一些自定义属性实现的。反射代码会查找这些属性,并根据属性中包含的信息来构建用户界面。

属性的UML图如下所示

Attributes.png

ShaderVersion属性指定了着色器的版本。这是必需的,因为在WPF 4.0中,像素着色器级别2.0和3.0都受支持,因此您需要检查渲染硬件是否支持该级别。

IValueController接口为参数控制指定了一个通用接口。DoubleCovnverterPoint2DValueConverter都实现了该接口。它们用于指定效果参数的最小值、最大值和默认值。此外,它们还包含一个将在用户界面上显示的描述。主要的反射代码位于一个名为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类中具有DoubleValueControllerPoint2DValueController属性的属性。如果找到一个,它将为该属性创建一个控件,并将Shader的属性绑定到Control的值。我使用双向绑定,因为这样,如果您从代码中更改其中一个受控值,它将在GUI中得到反映。

需要牢记的一点是,您只能在依赖属性上使用绑定。因此,如果您创建自定义控件,请始终将它们的属性实现为依赖属性。

Using the Code

控件可以这样使用

BloomEffect eff = new BloomEffect();
EffectTarget.Effect = eff;
EffectOptions.ControledEffect = eff;

构建您自己的值控制器

对于其他像素着色器属性类型(float3float4,它们在.NET中映射为Point3DColor类),您需要构建自己的控制器,然后您就可以基于此代码控制各种类型的着色器。

一个好的入门示例可能是Point2DControl

XAML
<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> 
C# 代码
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专家。

应用程序

完成的应用程序有三个效果,看起来像这样

screenshot.jpg

它有三种效果:两种颜色效果和一种扭曲效果。通道混合器是我自己创建的,而其他效果则直接取自Shazzam工具库。源代码也包含了这些效果的源代码。为了最大化兼容性,我只使用了像素着色器级别2.0的效果。程序中使用的图像是我家乡附近拍摄的。

关注点

© . All rights reserved.