Опубликован: 28.04.2009 | Уровень: для всех | Доступ: платный
Лекция 5:

Вершинные шейдеры

< Лекция 4 || Лекция 5: 123456789101112
5.4. Передача параметров в эффект

Предположим, что нам необходимо написать эффект, моделирующий визуализацию поверхности сквозь цветное стекло, пропускающее лишь часть света. Прозрачность стекла будет задаваться тремя коэффициентами, лежащими в диапазоне [0..1] и указывающими прозрачность стекла для красного, зеленого и синего компонентов цвета объекта. Если коэффициент равен 1, то стекло пропускает данный компонент цвета без изменений, если 0 - вообще не пропускает, а при промежуточных значениях 0..1 ослабляет яркость цветового компонента по мере уменьшения коэффициента. Итоговый цвет объекта определяется с использованием следующей формулы:

c_r=k_r-o_r
c_g=k_g-o_g
c_b=k_b – o_b
( 5.4)

где

  • c_r, c_g, c_b - итоговый цвет;
  • o_r, o_g, o_b - исходный цвет объекта;
  • k_r, k_g, k_b - коэффициент прозрачности стекла для отдельных цветов.

Данные выражения очень легко реализуются в вершинном шейдере:

VertexOutput MainVS(VertexInput input) 
{
VertexOutput output;
output.pos = float4(input.pos, 1.0f);
// Определяем коэффициенты пропускания разных цветов
const float4 filter = float4(0.2, 0.8, 0.5, 1.0); 
// Вычисляем итоговый цвет вершин, используя векторную
 операцию умножения
output.color = input.color * filter;
return output; 
}

Данный вариант отлично работает в случае одного фиксированного фильтра. Но что делать, если в процессе работы приложения фильтр постоянно меняется? Теоретически, можно попробовать динамически генерировать код шейдера, задавая значение константы filter налету. Однако такой подход имеет ряд существенных недостатков: компиляция эффекта и загрузка его в GPU занимают заметное время, что неминуемо окажет отрицательное влияние на производительность приложения. Кроме того, такие динамически генерируемые эффекты очень трудоемко сопровождать и отлаживать.

Поэтому разработчики языка HLSL предусмотрели специальный механизм для быстрого внесения изменений в эффекты "налету ". Техника очень проста: если при объявлении глобальной переменной указать ключевое слово uniform, то эта переменная будет доступна и прикладной программе, использующей эффект. Например, следующий код объявляет глобальную переменную filter, значение которой будет задаваться приложением:

uniform float4 circleColor;

Параметру можно указать значение по умолчанию, которое будет ему присваиваться сразу после загрузки эффекта из файла. Например:

uniform float4 circleColor = float4(0.5, 1.0, 0.8, 1.0);

Впрочем, ключевое слово uniform предполагается по умолчанию, поэтому его обычно не указывают -любая глобальная переменная является uniform -переменной. Антиподом uniform является ключевое слово static, которое скрывает глобальную переменную от программы. Например:

// Переменная circleColor скрыта от прикладной программы static float4
circleColor=float4(0.5, 1.0, 0.8, 1.0);

Примечание

Ключевое слово static так же применяется для объявления статических локальных переменных функции. В этом случае, его использование полностью аналогично языку C# за исключением маленького нюанса: при выходе из шейдера содержимое статических переменных теряется.

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

Реализовать эффект цветного полупрозрачного стекла с использованием параметра не составит труда (листинг 5.6).

// Параметр с коэффициентами прозрачности стекла float4
filter;
struct VertexInput 
{
float3 pos : POSITION;
float4 color : COLOR; 
};
struct VertexOutput 
{
float4 pos : POSITION;
float4 color : COLOR; 
};
VertexOutput MainVS(VertexInput input) 
{
VertexOutput output;
output.pos = float4(input.pos, 1.0f);
output.color = input.color * filter;
return output; 
}
float4 MainPS(float4 color:COLOR):COLOR 
{
return color; 
}
technique FilterFill 
{
pass p0
{
VertexShader = compile vs_1_1 MainVS();
 PixelShader = compile ps_1_1 MainPS();
} 
}
Листинг 5.6.

Ниже приведен отчет NVIDIA FX Composer 2.0 с ассемблерным кодом вершинного шейдера эффекта, полученный посредством вкладки Shader Perfomance:

// Generated by Microsoft (R) D3DX9 Shader Compiler 9.12.589.0000
//
// Parameters:
//
// float4 filter;
//
//
// Registers:
//
// Name	Reg Size
// 	 	 ----
// filter	c0	1
//
//
// Default values:
//
// filter
// c0 = { 0, 0, 0, 0 };
//
vs_1_1
def c1, 1, 0, 0, 0
dcl_position v0
dcl_color v1
mul oD0, v1, c0
mad oPos, v0.xyzx, c1.xxxy, c1.yyyx
// approximately 2 instruction slots used

В комментариях перед ассемблерным кодом эффекта указано, что эффект содержит один параметр float4 filter и что компилятор отвел для хранения данного параметра константный регистр c0. При этом, так как мы не указали значение по умолчанию для данного параметра, он будет автоматически инициализироваться вектором (0, 0, 0, 0).

Таким образом, изменение значения параметра filter будет сводиться к модификации значения константного регистра c0, не затрагивая собственно код вершинного шейдера.

Примечание

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

5.4.1. Работа с параметрами эффектов в XNA Framework

В XNA Framework параметры эффекта хранятся в коллекции Parameters класса Effect:

public EffectParameterCollection Parameters { get; }

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

public EffectParameter this[string name] { get; }

Примечание

Если эффект не содержит параметр с указанным именем, возвращается значение null.

Собственно параметр эффекта инкапсулируется классом EffectParameter, позволяющим читать и изменять значение эффекта посредством разнообразных типизированных методов SetValue и GetValueXXX. Ниже приведены определения некоторых методов GetValueXXX.

// Возвращает значение скалярного параметра HLSL типа float
public float GetValueSingle();
// Возвращает значение массива параметров типа float: 
например, float[10]. Параметр
// count указывает число элементов в массиве
public float[] GetValueSingleArray(int count);
// Возвращает значение параметра, являющегося 
двухмерным вектором (float2)
public Vector2 GetValueVector2();
// Возвращает значение параметра, являющегося 
массивом двухмерных векторов (float2[])
public Vector2[] GetValueVector2Array(int count);
// Возвращает значение параметра, являющегося 
трехмерным вектором (float3)
public Vector3 GetValueVector3();
// Возвращает значение параметра, являющегося 
массивом трехмерных векторов (float3[])
public Vector3[] GetValueVector3Array(int count);
// Возвращает значение параметра, являющегося 
четырехмерным вектором (float4)
public Vector4 GetValueVector4();
// Возвращает значение параметра, являющегося 
массивом четырехмерным вектором (float4[])
public Vector4[] GetValueVector4Array(int count);

Такое обилие методов обусловлено тем, что с точки зрения XNA Framework параметры HLSL являются просто константными регистрами GPU, содержимое которых можно трактовать по-разному в зависимости от ситуации. Например, значение цвета rgba можно трактовать как четырехмерный вектор, два двухмерных вектора или массив из четырех скалярных элементов.

Примечание

При некорректном обращении к параметру эффекта (например, при попытке записать трехмерный вектор в параметр HLSL, являющийся четырехмерным вектором) генерируется исключение System.InvalidCastException.

Методы SetValueXXX приводить не имеет смысла, так как каждому методу GetValueXXX соответствует свой метод SetValue с аналогичным набором параметров. Например, парой для метода float GetValueSingle() является метод public void SetValue(float value).

В качестве примера использования параметров XNA Framework, в листинге 5.7 приведен код приложения, визуализирующего прямоугольник, видимый через цветное стекло, с возможностью изменения пользователем цвета стекла (рисунок 5.18). Визуализация осуществляется с использованием эффекта, созданного в предыдущем разделе.

 Визуализация примитива через полупрозрачное стекло

увеличить изображение
Рис. 5.18. Визуализация примитива через полупрозрачное стекло
public partial class MainForm : Form
{
// Эффект, созданный в разделе 5.4 (листинг 5.6)
const string effectFileName = "Data\\FilterFill.fx";
Effect effect = null; 
// Объект, инкапсулирующий параметр filter эффекта 
(цвет стекла) EffectParameter
 filterParam;
private void MainFormLoad(object sender, EventArgs e) 
{
// Загружаем и компилируем эффект в промежуточный код
CompiledEffect compiledEffect;
compiledEffect = Effect.CompileEffectFromFile
(effectFileName, null, null, 
CompilerOptions.None, TargetPlatform.Windows);
// Создаем объект эффекта
effect = new Effect(device, compiledEffect.GetEffectCode(),
 CompilerOptions.NotCloneable, null);
// Получаем объект EffectParameter, соответствующий параметру filter
filterParam = effect.Parameters["filter"]; 
// Если параметр filter не существует, генерируем исключение
Debug.Assert(filterParam != null, effectFileName + " :
 не найден параметр filter"); 
}
// Обработчик нажатия панели, открывающий на экране 
диалоговое окно с выбором цвета стекла
private void filterPanel_Click(object sender, EventArgs e)
{
if (colorDialog.ShowDialog() == DialogResult.OK) 
{ 
// Изменяем цвет панели в соответствии с выбранным цветом
 filterPanel.BackColor = colorDialog.Color; xnaPanel.Invalidate(); 
} 
}
private void xnaPanel_Paint(object sender, PaintEventArgs e)
{
...
// Изменяем значение цвет стекла. Так как в Windows 
Form значения компонентов цвета находится
// в диапазоне 0..255, а в XNA Framework в диапазоне 
0..1, нам приходится делить значения
// компонентов на 255
filterParam.SetValue(new Vector4((float)filterPanel.BackColor.R /
 255.0f,
(float)filterPanel.BackColor.G / 255.0f, (float)filterPanel.
BackColor.B / 255.0f, 1.0f));
// Визуализируем прямоугольник с использование эффекта 
effect.Begin();
foreach (EffectPass pass in effect.CurrentTechnique.Passes) 
{
pass.Begin();
device.DrawUserPrimitives(PrimitiveType.TriangleStrip, vertices, 
0, vertices.Length - 2);
pass.End(); 
} effect.End();
device.Present();
 ... 
}
Листинг 5.7.

В принципе XNA Framework позволяет работать с параметрами с использованием следующего лаконичного синтаксиса:

effect.Parameters["filter"].SetValue(newColor);

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

Поэтому обработчик события Load один раз выполняется поиск параметра filter, после чего вся работа с ним осуществляется уже посредством экземпляра класса EffectParameter. Во избежание проблем при модификации приложения после получения объекта EffectParameter вызывается метод Debug.Assert с проверкой ссылки на равенство null – гораздо удобнее получить исключение при загрузке приложения рядом с "проблемным методом ", чем где-то глубоко в дебрях приложения спустя несколько минут работы.

< Лекция 4 || Лекция 5: 123456789101112
Андрей Леонов
Андрей Леонов

Reference = add reference, в висуал студия 2010 не могу найти в вкладке Solution Explorer, Microsoft.Xna.Framework. Его нету.

Пизда Хуй
Пизда Хуй
Россия, Зимбабве
Муминжон Юсупов
Муминжон Юсупов
Узбекистан, г.Ургенч