Использование шейдеров в WPF и Silverlight

Часть 2. Создание и использование шейдеров

Последнее обновление: 29.05.2012

Хотя мы можем создавать и свои шейдеры, некоторые распространенные эффекты уже реализованы и являются частью фреймворка. К ним в частности, в инфраструктуре WPF и Silverlight уже реализованы эффект размытия (класс BlurEffect) и эффект тени (класс DropShadowEffect). Поэтому, если требуется создать эффект размытия, то, возможно, имеет смысл воспользоваться встроенным эффектом.

Итак, создадим новое приложение Silverlight. Для этого откроем Visual Web Developer 2010 Express и выберем в меню New Project (Создать проект). В списке шаблонов проекта выберем шаблон Silverlight Application

При создании нового приложения уберем галочку с поля, чтобы все настройки были следующие:

Добавим в приложение элемент Image, к изображению на котором мы будем применять эффект, и добавим кнопку, после нажатия которой и будет применяться эффект:

<UserControl x:Class="SilverlightShaders.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="250" d:DesignWidth="300">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="4*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Image x:Name="Image1" Grid.Row="0" Source="bronz.jpg" />
        <Button x:Name="Button1" Grid.Row="1" Content="Applay Effect" Width="120" Height="30" Click="Button1_Click" /> 
    </Grid>
</UserControl>

В обработчике нажатия кнопки Button1_Click для начала установим для изображения эффект размытия:

        private void Button1_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Media.Effects.BlurEffect blurEffect = new System.Windows.Media.Effects.BlurEffect();
            blurEffect.Radius = 10;
            Image1.Effect = blurEffect;
        }

Запустим приложения на выполнение.

Если до нажатия на кнопку у нас было такое изображение

То после нажатия оно будет следующим

Эффект сработал. Но это вообщем-то была прелюдия. Использовать встроенные эффекты не так интересно, поэтому перейдем к самому созданию эффекта.

Чтобы создать свой эффект нам надо вначале создать текстовый файл и внести в него код шейдера на языке HLSL. Затем этот файл компилируется при помощи компилятора fxc.exe в бинарную форму и добавляется в качестве ресурса в проект WPF или Silverlight. Но даже после этого нам еще надо будет написать класс-обертку, чтобы мы могли использовать наш эффект в приложении, также как BlurEffect.

Для начала реализуем самый простой эффект - эффект инверсии цветов. Добавим в проект простой текстовый файл и назовем его invertcolor.txt. Затем добавим в него следующий код:

sampler2D InputTexture;
float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 color = tex2D(InputTexture, uv); 
	float4 invertedcolor = float4(color.a-color.rgb,color.a);
    return invertedcolor; 
}

Что данный код делает? Во-первых, переменная InputTexture, являющаяся объектом sampler2D, у нас будет представлять текстуру или наше изображение в элементе Image1.

Далее у нас идет функция main, которая возвращает значение типа float4. float4 представляет массив значений с плавающей точкой. Например, мы можем определить объект float4 и инициализировать его значение с помощью следующей строки: float4 color = float4(1, 0, 0, 1);. В нашем случае значение float4 будет нести цвет пикселя в виде float4(red, green, blue, alpha)

TEXCOORD и COLOR - это семантики шейдера. TEXCOORD представляет координаты текстуры, а COLOR - цвет.

Затем функция tex2D считывает двухмерную текстуру, и мы производим интертирование цвета.

Код написан, и мы можем скомпилировать шейдер. Для этого зайдем в меню Пуск и среди программ выберем Microsoft DirectX SDK (June 2010)-> Microsoft DirectX Command Prompt. В открывшейся консоли введем следующую строку:

fxc /T ps_2_0 /E main /Fo output.ps invertcolor.txt

После этого компилятор fxc.exe скопилирует нам файл output.ps:

Небольшое примечание. Поскольку по умолчанию в Visual Studio используется кодировка UTF, и если вы набираете hlsl-код в Visual Studio, то потом может произойти ошибка компиляции. Чтобы такого не происходило, надо кодировку файла изменить на UTF without BOM.

Теперь добавим в проект файл output.ps и убедимся, что для него для свойства Bild Action установлено значение Resource:

Создание класса-обертки

Теперь уже перейдем к шарпу. Для использования нашего шейдера, нужно создать класс, унаследованный от базового класса ShaderEffect. Класс-обертка (назовем его InvertEffect) будет иметь следующий код:

    public class InvertEffect : ShaderEffect
    {
        private PixelShader pixelShader = new PixelShader();

        public InvertEffect()
        {
            pixelShader.UriSource = new Uri("/SilverlightShaders;component/output.ps", UriKind.Relative);
            this.PixelShader = pixelShader;
            this.UpdateShaderValue(InputProperty);
        }

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

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

В нашем классе объявлено одно свойство зависимостей InputProperty. Оно и будет представлять те данные, которые потом будут подаваться на вход в пиксельный шейдер из растеризатора. И теперь мы готовы к применению нового эффекта. Изменим код обработчика нажатия кнопки следующим образом:

        private void Button1_Click(object sender, RoutedEventArgs e)
        {
            InvertEffect invertEffect = new InvertEffect();
            Image1.Effect = invertEffect;
        }

Запустим приложение, нажмем на кнопку, и наше изображение инвертирует все цвета:

И как и любой другой эффект, мы можем использовать наш эффект в xaml-коде:

<UserControl x:Class="SilverlightShaders.MainPage"
    xmlns:local="clr-namespace:SilverlightShaders"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="250" d:DesignWidth="300">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="4*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Image x:Name="Image1" Grid.Row="0" Source="bronz.jpg">
            <Image.Effect>
                <local:InvertEffect />
            </Image.Effect>
        </Image>
        <Button x:Name="Button1" Grid.Row="1" Content="Applay Effect" Width="120" Height="30" Click="Button1_Click" /> 
    </Grid>
</UserControl>

Мы использовали проект Silverlight, но то же самое относится и к WPF.

Очевидно, что чтобы создавать эффекты на основе шейдеров HLSL, надо знать сам язык HLSL или ориентироваться. Весь справочный материал по этому языку можно найти как всегда на msdn по адресу https://msdn.microsoft.com/en-us/library/windows/desktop/bb509561(v=vs.85).aspx

Теперь создадим еще несколько эффектов. Сделаем эффект оттенков серого. Он будет похож на наш предыдущий эффект:

sampler2D input;
float4 main(float2 uv : TEXCOORD) : COLOR 
{
    float4 color = tex2D(input, uv);
	float gray = dot(color.rgb, float3(0.2126, 0.7152, 0.0722));
    return float4(gray, gray, gray, color.a); 
}

Здесь надо отметить функцию dot, которая возвращает скалярное произведение двух векторов типа float3. Мы также создаем класс эффекта и устанавливаем новый эффект для объекта Image1:

И еще один эффект - создание сепии:

sampler2D Input;
float4 main(float2 uv : TEXCOORD) : COLOR 
{
	float4 TintColor = float4(0.9,0.7,0.3,1);
    float4 color = tex2D(Input, uv);
    float gray = dot(color.rgb, float3(0.2126, 0.7152, 0.0722)); 
    float4 grayColor = float4(gray, gray, gray, color.a);
	return grayColor * TintColor;
}

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850