Использование pixel shaders в Windows Presentation Foundation – Часть 1

Введение

Шейдер – это программа для одной из ступеней графического конвейера, используемая в трёхмерной графике для определения окончательных параметров объекта или изображения. Она может включать в себя произвольной сложности описание поглощения и рассеяния света, наложения текстуры, отражение и преломление, затенение, смещение поверхности и эффекты пост-обработки.

Шейдеры выполняются параллельно для каждого элемента данных. Пиксельные шейдеры, например, выполняются для каждого пикселя в битовой карте (растровом изображении), и поэтому используются для реализации эффектов на основе индивидуальных пикселей. Каждому пикселю может быть поставлен в соответствие некоторый набор атрибутов, таких как цвет, глубина, текстурные координаты.

 

Поддержка пиксельных шейдеров для Windows Presentation Foundation (WPF) впервые появилась в версии 3.5 SP1. В последней версии WPF 4.0 реализована поддержка третьей версии пиксельных шейдеров.

Microsof’s DirectX поддерживает шейдеры, поддержка пиксельных шейдеров 3.0 доступна с версии 9.0c.

Если попробовать загрузить пиксельные шейдеры 3 на компьютере, который не имеет их аппаратную поддержку, то они просто будут проигнорированы. Если на компьютере установлено более одной видеокарты, то поведение определяется по наиболее слабой видеокарте. Пиксельные шейдеры 2.0 могут работать при программном рендеринге, однако пиксельные шейдеры 3.0 работают только при аппаратном рендеринге.

 

Программирование пиксельных шейдеров

Пиксельные шейдеры были введены в виде так называемых ShaderEffect. Шейдерные эффекты можно применять к любому элементу управления для создания всяческих визуальных эффектов.

Класс ShaderEffect предоставляет настраиваемый эффект растрового изображения с использованием объекта PixelShader. Класс PixelShader используется для доступа к предварительно скомпилированному байткоду HLSL. Можно создать объект PixelShader из ссылки URI типа «pack» или из объекта Stream. Задайте свойство UriSource или вызовите метод SetStreamSource, чтобы загрузить байткод построителя текстуры. Чтобы создать настраиваемый эффект, назначьте объект PixelShader для свойства PixelShader объекта ShaderEffect.

 

Есть несколько способов написания и компиляции шейдерных программ. В наши дни самый распространенный способ, применяемый в Direct3D и мире Windows, — язык High Level Shading Language (HLSL). Компилятор шейдеров Direct3D под названием fxc.exe транслирует HLSL-код в байтовый, который и выполняется исполняющей средой.

HLSL — это язык в стиле C, содержащий некоторые специфические типы данных и встроенные функции, но указателей в нем нет.

HLSL определяет скалярные и различные векторные/матричные типы данных для целочисленных операций и операций с плавающей точкой. Встроенные функции поддерживают скалярные и векторные типы данных. Выражения, управляющие потоком выполнения, такие как if, switch, for и while, тоже поддерживаются. Конечно же, допустимы фигурные скобки, отделяющие блоки кода, и большинство операторов C. Компилятор использует семантику, чтобы определять предназначение параметра и предоставлять для него корректные данные. Параметр обычно передается в шейдерную программу с указанием ключевого слова register. К элементам векторных типов можно обращаться с использованием различных псевдонимов, например x, y, z, w; r, g, b, a; u, v; и др. Также можно комбинировать все перечисленное выше и писать элегантные и короткие выражения, используя обращения по псевдонимам (swizzling):

 

// Создаем трехмерный вектор значений с плавающей точкой a и b с разным синтаксисом

 

 

float3 a = float3(1, 2, 3);

 

 

float3 b = {5, 6, 7};

 

 

// Вычисляем произведение векторов a и b, используя обращение по псевдонимам

 

 

float3 crossProduct = a.yzx * b.zxy - a.zxy * b.yzx;

 

 

Приведем несколько примеров написания пиксельных шейдеров.

 

 

Базовый код пиксельного шейдера:

 

 

sampler2D input : register(s0);

 

 

float4 main(float2 uv : TEXCOORD) : COLOR

 

 

{

 

 

    float4 Color;

 

 

    Color = tex2D(input, uv.xy);

 

 

    return Color;

 

 

}

 

Входным регистром является собственно битовая карта/текстура, которая содержит пиксели и обрабатывается пиксельным шейдером. Функция main в этом пиксельном шейдере служит точкой входа и выполняется для каждого пикселя входной битовой карты. Координаты текущего обрабатываемого пикселя передаются как параметр uv типа float2. Эти координаты нормализуются до диапазона [0, 1]. Цвет пикселя в данных координатах извлекается как значение типа float4 с помощью встроенной функции tex2D. Ожидается, что этот пиксельный шейдер вернет значение float4 COLOR.

Начальный код пиксельного шейдера возвращает исходный цвет каждого пикселя, и мы используем это в качестве отправной точки для преобразования в шкалу оттенков серого.

 

Преобразование в серые оттенки:

 

sampler2D input : register(s0);

 

 

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, которая умножает значения RGB (red, green и blue) на вектор констант типа float3. Результат представляет яркость пикселя. Скалярное произведение перемножает элементы вектора color на элементы вектора констант и добавляет три результата умножения, образуя скалярное значение типа float. Этот пиксельный шейдер возвращает новый цвет, состоящий из значения оттенка серого для RGB и исходного значения альфа-канала (прозрачности) выбранного пикселя.

 

Растягивание / сжимание изображения:

sampler2D input : register(s0);
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 Color;

//растягивание половины изображения

uv.x = uv.x * 0.5;

//сжимание изображения в половину

//uv.x = uv.x / 0.5;
Color = tex2D( input , uv.xy);
return Color;
}

 

Изменение яркости и контрастности:

 

sampler2D input : register(s0); float brightness : register(c0); float contrast : register(c1);

 

 

float4 main(float2 uv : TEXCOORD) : COLOR {     float4 color = tex2D(input, uv);     float4 result = color;     result = color + brightness;     result = result * (1.0+contrast)/1.0;     return result; }

 

Мозаичный шейдер. Следующий шейдерный эффект постобработки будет немного посложнее. Он начинается с пикселизации изображения (его разбиения на блоки пикселей), а затем полученные прямоугольники округляются до тех пор, пока не будет получен результат, аналогичный мозаике.


Исходное тестовое изображение

Пикселизация ввода

 

// Количество пиксельных блоков

 

 

float BlockCount : register(C0);

 

 

sampler2D input : register(S0);

 

 

// Статически вычисляемые переменные для оптимизации

 

 

static float BlockSize = 1.0f / BlockCount;

 

 

float4 main(float2 uv : TEXCOORD) : COLOR

 

 

{

 

 

    // Вычисляем центр блока

 

 

    float2 blockPos = floor(uv * BlockCount);

 

 

    float2 blockCenter = blockPos * BlockSize + BlockSize * 0.5;

 

 

    // Получаем цвет в вычисленных координатах

 

 

    return tex2D(input, blockCenter);

 

 

}

 

Параметр BlockCount типа float определяет количество блоков («крупных пикселей»), на которое делится полученное изображение. Размер блока (BlockSize) является обратным значением BlockCount и вычисляется как static float для экономии процессорного времени. Координаты текущего пикселя (uv) используются для определения блока, к которому он относится. Это определение зависит от BlockCount и является результатом встроенной функции floor. Чтобы получить цвет выходного пикселя, определяются координаты центра каждого блока, и извлекается цвет пикселя в этом центре.


Вывод стадии пикселизации

Округление блоков пикселей

 

float BlockCount : register(C0);

 

 

// Округление блока пикселей

 

 

float Max : register(C2);

 

 

sampler2D input : register(S0);

 

 

// Статически вычисляемые переменные для оптимизации

 

 

static float BlockSize = 1.0f / BlockCount;

 

 

float4 main(float2 uv : TEXCOORD) : COLOR

 

 

{

 

 

    // Вычисляем центр блока

 

 

    float2 blockPos = floor(uv * BlockCount);

 

 

    float2 blockCenter = blockPos * BlockSize + BlockSize * 0.5;

 

 

    // Округляем блок, проверяя расстояние

 

 

    // пикселя в данных координатах до центра

 

 

    float dist = length(uv - blockCenter) * BlockCount;

 

 

    if(dist > Max)

 

 

    {

 

 

          return 0;

 

 

    }

 

 

    // Получаем цвет в вычисленных координатах

 

 

    return tex2D(input, blockCenter);

 

 

}

 

Параметр Max определяет максимальное расстояние пикселя до центра своего блока, а значит, и степень округления блока пикселей. Если длина вектора между текущими координатами пикселя (uv) и центром его блока превышает значение параметра Max, возвращается прозрачный пиксель (0). Для вычисления скалярной длины вектора расстояния применяется встроенная функция length. На рисунках показаны результаты округления с разными значениями параметра Max.


Вывод стадии округления (Max = 0.45)


Вывод стадии округления (Max = 0.60)

Выпекаем пончики

 

// Количество блоков пикселей

 

 

float BlockCount : register(C0);

 

 

// Округление блока пикселей

 

 

float Min : register(C1);

 

 

// Округление блока пикселей

 

 

float Max : register(C2);

 

 

sampler2D input : register(S0);

 

 

// Статически вычисляемые переменные для оптимизации

 

 

static float BlockSize = 1.0f / BlockCount;

 

 

float4 main(float2 uv : TEXCOORD) : COLOR

 

 

{

 

 

// Вычисляем центр блока

 

 

float2 blockPos = floor(uv * BlockCount);

 

 

float2 blockCenter = blockPos * BlockSize + BlockSize * 0.5;

 

 

// Округляем блок, проверяя расстояние

 

 

// пикселя в данных координатах до центра

 

 

float dist = length(uv - blockCenter) * BlockCount;

 

 

if(dist < Min || dist > Max)

 

 

{

 

 

       return 0;

 

 

}

 

 

// Получаем цвет в вычисленных координатах

 

 

return tex2D(input, blockCenter);

 

 

}

 

Чтобы получить симпатичные кольца (пончики), осталось добавить проверку на минимальное расстояние. Это довольно легко и делается с помощью дополнительного параметра Min.


Вывод стадии округления (Min = 0.20, Max = 0.45)

Эффект волны:


 

sampler2D input : register(s0); float2 size : register(c0); //size of the screen, in pixelfloat3 location:register(c1);//relative location of p2, controlled by WPF animation#define Pi 3.1415926

 

 

float4 main(float2 uv : TEXCOORD) : COLOR {  float3 p1={0.5,0.5,2}; float3 p2=p1+location; float3 current={lerp(0.5,uv[0],size.x/size.y),uv[1],0}; float delta=(distance(p1,current)-distance(p2,current))/0.005; float4 result0= tex2D(input, uv.xy);     float interfere=((1-cos(2*delta))/2+1)/2; result0*=interfere; result0.a=1; return result0;}

 

 

 

В следующей части я расскажу о использовании шейдеров в WPF, и мы рассмотрим уже конкретный пример создания шейдера в WPF.

 

Запись опубликована в рубрике Uncategorized. Добавьте в закладки постоянную ссылку.

3 Responses to Использование pixel shaders в Windows Presentation Foundation – Часть 1

  1. Уведомление: информационные материалы | Alexander Knyazev: блог

  2. Уведомление: Технические материалы по продуктам и решениям Microsoft на русском языке – март 2011. | Dmitry Bulavko:MySpaces

  3. Уведомление: Технические материалы по продуктам и решениям Microsoft на русском языке – март 2011 « Mcp Club Minsk's Blog

Оставьте комментарий