Написание шейдеров HLSL в Direct3D 9
- Vertex-Shader базовые
- Pixel-Shader базовые
- входные данные шейдера и переменные шейдера
- функции записи
- управление потоками
- Связанные темы
При выполнении программируемый шейдер вершин заменяет обработку вершин, выполненную конвейером графики Microsoft Direct3D. При использовании вершинного шейдера сведения о состоянии, касающиеся операций преобразования и освещения, игнорируются конвейером фиксированной функции. При отключении шейдера вершин и возврате обработки фиксированной функции применяются все текущие параметры состояния.
Тесселляция примитивов высокого порядка должна выполняться до выполнения вершинного шейдера. Реализации, выполняющие тесселяцию поверхности после обработки шейдера, должны сделать это так, чтобы это было неочевидно для приложения и кода шейдера.
Как минимум, вершинный шейдер должен выводить позицию вершин в однородном пространстве клипа. При необходимости шейдер вершин может выводить координаты текстуры, цвет вершины, освещение вершин, туманные факторы и т. д.
Обработка пикселей выполняется шейдерами пикселей на отдельных пикселях. Шейдеры пикселей работают вместе с вершинными шейдерами; выходные данные шейдера вершин предоставляют входные данные для шейдера пикселей. Другие операции пикселя (туманное смешение, операции набора элементов и смешивание целевых объектов отрисовки) происходят после выполнения шейдера.
Шейдер пикселей полностью заменяет функциональность смешивания пикселей, указанную мультитекстурным блендером, включая операции, предварительно определенные состояниями стадии текстуры. Операции выборки текстур и фильтрации, контролируемые стандартными состояниями стадии текстуры для минификации, увеличения размера, фильтрации mip и режимов адресации оболочки, можно инициализировать в шейдерах. Приложение свободно изменять эти состояния, не требуя повторного создания в данный момент привязанного шейдера. Настройка состояния может стать еще проще, если ваши шейдеры разработаны в виде эффекта.
Для версий шейдера пикселей ps_1_1 - ps_2_0, диффузные и спекулярные цвета насыщенны (зажаты) в диапазоне от 0 до 1 перед использованием шейдером.
Предполагается, что входные значения цвета для пиксельного шейдера являются перспективно корректными, но это не гарантируется (для всех аппаратных средств). Цвета, выбираемые из координат текстуры, итерационно обрабатываются с перспективной коррекцией и ограничиваются диапазоном от 0 до 1 в процессе итерации.
Для версий шейдера пикселей ps_1_1 - ps_1_4 результатом, создаваемым шейдером пикселей, является содержимое регистра r0. Что бы он ни содержал к моменту завершения обработки шейдером, отправляется на этап тумана и в блендер рендер-целей.
Для версий шейдера пикселей ps_2_0 и выше выходной цвет выводится через oC0 - oC4.
- Объявление переменных шейдера
- унифицированные входные данные шейдера
- Изменяющиеся входные данные шейдера и семантики
- Самплеры и объекты текстур
Самое простое объявление переменной включает тип и имя переменной, например это объявление с плавающей запятой:
float fVar;
Вы можете инициализировать переменную в той же инструкции.
float fVar = 3.1f;
Массив переменных можно объявить,
int iVar[3];
или объявлен и инициализирован в той же инструкции.
int iVar[3] = {1,2,3};
Ниже приведены несколько объявлений, демонстрирующих многие характеристики высокоуровневых переменных языка шейдеров (HLSL):
float4 color;
uniform float4 position : POSITION;
const float4 lightDirection = {0,0,1};
Объявления данных могут использовать любой допустимый тип, включая:
- Типы данных (DirectX HLSL)
- векторный тип (DirectX HLSL)
- Тип матрицы (DirectX HLSL)
- тип шейдера (DirectX HLSL)
- тип сэмплера (DirectX HLSL)
- тип User-Defined (DirectX HLSL)
Шейдер может иметь переменные верхнего уровня, аргументы и функции.
// top-level variable
float globalShaderVariable;
// top-level function
void function(
in float4 position: POSITION0 // top-level argument
)
{
float localShaderVariable; // local variable
function2(...)
}
void function2()
{
...
}
Переменные верхнего уровня объявляются вне всех функций. Аргументы верхнего уровня — это параметры функции верхнего уровня. Функция верхнего уровня — это любая функция, вызываемая приложением (в отличие от функции, вызываемой другой функцией).
Вершинные и пиксельные шейдеры принимают два типа входных данных: варьируемые и униформные. Различные входные данные — это данные, уникальные для каждого выполнения шейдера. Для шейдера вершин различные данные (например, положение, нормальное и т. д.) исходят из потоков вершин. Однородные данные (например, цвет материала, преобразование мира и т. д.) являются константами для нескольких выполнений шейдера. Для тех, кто знаком с ассемблерными моделями шейдеров, однородные данные задаются регистрами констант, а переменные данные — регистрами v и t.
Однородные данные можно указать двумя методами. Наиболее распространенным методом является объявление глобальных переменных и их использование в шейдере. Любое использование глобальных переменных в шейдере приведет к добавлению этой переменной в список универсальных переменных, необходимых для этого шейдера. Второй метод — пометить входной параметр функции шейдера верхнего уровня как единообразную. Эта маркировка указывает, что указанная переменная должна быть добавлена в список универсальных переменных.
Однородные переменные, используемые шейдером, передаются приложению через постоянную таблицу. Постоянная таблица — это имя таблицы символов, которая определяет, как универсальные переменные, используемые шейдером, помещаются в регистры констант. Параметры универсальной функции отображаются в таблице констант, предустановленной знаком доллара ($), в отличие от глобальных переменных. Знак доллара необходим, чтобы избежать конфликтов имен между локальными универсальными входными данными и глобальными переменными того же имени.
Таблица констант содержит расположения постоянных регистров всех универсальных переменных, используемых шейдером. Таблица также содержит сведения о типе и значение по умолчанию, если указано.
Различные входные параметры (функции шейдера верхнего уровня) должны быть помечены семантическим или универсальным ключевым словом, указывающим, что значение является константой для выполнения шейдера. Если входные данные шейдера верхнего уровня не отмечены семантической или универсальной ключевой словой, то шейдер не будет компилироваться.
Семантика ввода — это имя, используемое для связывания заданных входных данных с выходными данными предыдущей части графического конвейера. Например, входная семантическая POSITION0 используется шейдерами вершин, чтобы указать, где следует связывать данные положения из буфера вершин.
Пиксельные и вершинные шейдеры имеют разные наборы входных семантик из-за различных частей графического конвейера, который передается в каждую единицу шейдера. Семантика входных данных вершинного шейдера описывает информацию о вершинах (например, положение, нормаль, координаты текстуры, цвет, тангент, бинормаль и т. д.), которая загружается из буфера вершин в форму, доступную для обработки вершинным шейдером. Семантика входных данных напрямую сопоставляется с использованием объявления вершин и индексом использования.
Семантика входных данных шейдера пикселей описывает сведения, предоставляемые на пиксель единицей растеризации. Данные создаются путем интерполяции между выходными данными шейдера вершин для каждой вершины текущего примитива. Базовая семантика ввода шейдера пикселей связывает выходные цвета и координаты текстуры с входными параметрами.
Семантику ввода можно назначить входным данным шейдера двумя методами:
- Добавление двоеточия и семантического имени в объявление параметра.
- Определение входной структуры с семантикой ввода, назначенной каждому элементу структуры.
Шейдеры вершин и пиксельные шейдеры предоставляют выходные данные на последующий этап графического конвейера. Семантика выходных данных используется для указания того, как данные, созданные шейдером, должны быть связаны с входными данными следующего этапа. Например, выходные семантики для шейдера вершин используются для связывания выходных данных интерполяторов в растризаторе для создания входных данных для шейдера пикселей. Выходные данные шейдера пикселей — это значения, предоставляемые единице альфа-смешивания для каждого из целевых объектов отрисовки или значения глубины, записанные в буфер глубины.
Семантика выходных данных шейдера вершин используется для связывания шейдера как с шейдером пикселей, так и с этапом растеризатора. Шейдер вершин, используемый растеризатором и не открываемый шейдеру пикселей, должен как минимум создавать данные позиции. Вершинные шейдеры, создающие координаты текстуры и цветные данные, предоставляют данные шейдеру пикселей после завершения интерполяции.
Семантика выходных данных шейдера пикселей привязывает выходные цвета шейдера пикселей с правильным целевым объектом отрисовки. Цвет на выходе шейдера пикселей связан с этапом альфа-смешивания, который определяет, как изменяются цели отрисовки. Выходные данные глубины шейдера пикселей можно использовать для изменения целевых значений глубины в текущем расположении растра. Выходные данные глубины и несколько целевых объектов отрисовки поддерживаются только в некоторых моделях шейдеров.
Синтаксис для семантики вывода идентичен синтаксису для указания семантики ввода. Семантика может быть указана непосредственно на параметрах, объявленных как "out", или может назначаться в процессе определения структуры, которая затем возвращается в качестве "out" параметра или в качестве возвращаемого значения функции.
Семантика определяет, откуда приходят данные. Семантика — это необязательные идентификаторы, определяющие входные и выходные данные шейдера. Семантика отображается в одном из трех мест:
- После элемента структуры.
- После аргумента в списке входных аргументов функции.
- После списка входных аргументов функции.
В этом примере используется структура для предоставления одного или нескольких входных данных шейдера вершин и другой структуры для предоставления одного или нескольких выходных данных шейдера вершин. Каждый из элементов структуры использует семантику.
vector vClr;
struct VS_INPUT
{
float4 vPosition : POSITION;
float3 vNormal : NORMAL;
float4 vBlendWeights : BLENDWEIGHT;
};
struct VS_OUTPUT
{
float4 vPosition : POSITION;
float4 vDiffuse : COLOR;
};
float4x4 mWld1;
float4x4 mWld2;
float4x4 mWld3;
float4x4 mWld4;
float Len;
float4 vLight;
float4x4 mTot;
VS_OUTPUT VS_Skinning_Example(const VS_INPUT v, uniform float len=100)
{
VS_OUTPUT out;
// Skin position (to world space)
float3 vPosition =
mul(v.vPosition, (float4x3) mWld1) * v.vBlendWeights.x +
mul(v.vPosition, (float4x3) mWld2) * v.vBlendWeights.y +
mul(v.vPosition, (float4x3) mWld3) * v.vBlendWeights.z +
mul(v.vPosition, (float4x3) mWld4) * v.vBlendWeights.w;
// Skin normal (to world space)
float3 vNormal =
mul(v.vNormal, (float3x3) mWld1) * v.vBlendWeights.x +
mul(v.vNormal, (float3x3) mWld2) * v.vBlendWeights.y +
mul(v.vNormal, (float3x3) mWld3) * v.vBlendWeights.z +
mul(v.vNormal, (float3x3) mWld4) * v.vBlendWeights.w;
// Output stuff
out.vPosition = mul(float4(vPosition + vNormal * Len, 1), mTot);
out.vDiffuse = dot(vLight,vNormal);
return out;
}
Входная структура определяет данные из буфера вершин, которые будут предоставлять входные данные шейдера. Этот шейдер сопоставляет данные из элементов позиции, нормали и весов смешивания буфера вершин в регистры вершинного шейдера. Тип входных данных не обязательно должен точно совпадать с типом данных в объявлении вершин. Если данные вершин не совпадают точно, данные вершин автоматически преобразуются в тип данных HLSL при записи в регистры шейдера. Например, если нормальные данные определяются приложением как тип UINT, они будут преобразованы в float3 при чтении шейдером.
Если данные в потоке вершин содержат меньше компонентов, чем соответствующий тип данных шейдера, отсутствующие компоненты будут инициализированы в 0 (за исключением w, который инициализирован до 1).
Семантика входных данных аналогична значениям в D3DDECLUSAGE.
Структура выходных данных определяет выходные параметры шейдера вершин: позицию и цвет. Эти выходные данные будут использоваться конвейером для растризации треугольников (в примитивной обработке). Выходные данные, помеченные как данные позиции, указывают позицию вершины в однородном пространстве. Как минимум, шейдер вершин должен генерировать данные позиции. Положение пространства экрана вычисляется после завершения шейдера вершин, разделив координату (x, y, z) по w. В пространстве экрана -1 и 1 являются минимальными и максимальными значениями x и y границ окна просмотра, а z используется для тестирования z-буфера.
Семантика выходных данных также аналогична значениям в D3DDECLUSAGE. Как правило, выходная структура для шейдера вершин также может использоваться в качестве входной структуры для шейдера пикселей, если шейдер пикселей не считывается из любой переменной, помеченной положением, размером точки или семантикой тумана. Эти семантики связаны с по-вершинными скалярными значениями, которые не используются шейдером пикселей. Если эти значения необходимы для шейдера пикселей, их можно скопировать в другую выходную переменную, использующую семантику шейдера пикселей.
Глобальные переменные автоматически назначаются компилятором в регистры. Глобальные переменные также называются универсальными параметрами, так как содержимое переменной одинаково для всех пикселей, обрабатываемых каждый раз при вызове шейдера. Регистры содержатся в таблице констант, которую можно считывать с помощью интерфейса ID3DXConstantTable.
Семантика входных данных для шейдеров пикселей сопоставляется с определенными аппаратными регистрами для передачи данных между шейдерами вершин и шейдерами пикселей. Каждый тип регистра имеет определенные свойства. Так как в настоящее время существует только две семантики для координат цветов и текстур, большинство данных обычно помечаются как координата текстуры, даже если это не так.
Обратите внимание, что выходная структура вершинного шейдера использовала входные данные положения, которые не используются шейдером пикселей. HLSL разрешает допустимые выходные данные шейдера вершин, которые не являются допустимыми входными данными для шейдера пикселей, если они не используются в шейдере пикселей.
Входные аргументы также могут быть массивами. Семантика автоматически увеличивается компилятором для каждого элемента массива. Например, рассмотрим следующее явное объявление:
struct VS_OUTPUT
{
float4 Position : POSITION;
float3 Diffuse : COLOR0;
float3 Specular : COLOR1;
float3 HalfVector : TEXCOORD3;
float3 Fresnel : TEXCOORD2;
float3 Reflection : TEXCOORD0;
float3 NoiseCoord : TEXCOORD1;
};
float4 Sparkle(VS_OUTPUT In) : COLOR
Явное объявление, указанное выше, эквивалентно следующему объявлению, которое будет автоматически увеличивать семантику компилятором:
float4 Sparkle(float4 Position : POSITION,
float3 Col[2] : COLOR0,
float3 Tex[4] : TEXCOORD0) : COLOR0
{
// shader statements
...
Как и входная семантика, семантика вывода определяет использование данных для выходных данных шейдера пикселей. Многие шейдеры пикселей записывают только один выходной цвет. Шейдеры пикселей также могут записывать значение глубины в один или несколько целевых объектов отрисовки одновременно (до четырех). Как и шейдеры вершин, шейдеры пикселей используют структуру для возврата нескольких выходных данных. Этот шейдер записывает 0 в компоненты цвета, а также в компонент глубины.
struct PS_OUTPUT
{
float4 Color[4] : COLOR0;
float Depth : DEPTH;
};
PS_OUTPUT main(void)
{
PS_OUTPUT out;
// Shader statements
...
// Write up to four pixel shader output colors
out.Color[0] = ...
out.Color[1] = ...
out.Color[2] = ...
out.Color[3] = ...
// Write pixel depth
out.Depth = ...
return out;
}
Выходные цвета шейдера пикселей должны иметь тип float4. При написании нескольких цветов все выходные цвета должны использоваться последовательно. Другими словами, COLOR1 не может быть выводом, если COLOR0 уже написан. Выходные данные глубины шейдера пикселей должны иметь тип float1.
Объект sampler содержит состояние sampler. Состояние sampler указывает текстуру для выборки и управляет фильтрацией, которая выполняется во время выборки. Для выборки текстуры необходимо выполнить три действия:
- Текстура
- Сэмплер (с состоянием сэмплера)
- Инструкция по выборке
Семплеры можно инициализировать с помощью текстур и состояния семплера, как показано ниже.
sampler s = sampler_state
{
texture = NULL;
mipfilter = LINEAR;
};
Ниже приведен пример кода для примера текстуры 2D:
texture tex0;
sampler2D s_2D;
float2 sample_2D(float2 tex : TEXCOORD0) : COLOR
{
return tex2D(s_2D, tex);
}
Текстура объявлена с переменной текстуры tex0.
В этом примере объявляется переменная примера с именем s_2D. Семплер содержит внутри себя состояние семплера в фигурных скобках. Это включает в себя текстуру, которая будет использоваться для выборки и, опционально, состояние фильтра (т.е. режимы обёртки, режимы фильтрации и т. д.). Если состояние сэмплера опущено, то применяется состояние сэмплера по умолчанию, указывающее на линейную фильтрацию и режим обертывания для координат текстуры. Функция sampler принимает двухкомпонентную текстурную координату с плавающей запятой и возвращает двухкомпонентный цвет. Он представлен типом возвращаемого значения float2 и представляет данные в красных и зеленых компонентах.
Определены четыре типа сэмплеров (см. ключевых слов) и вызовы текстур выполняются встроенными функциями: tex1D(s, t) (DirectX HLSL), tex2D(s, t) (DirectX HLSL), tex3D(s, t) (DirectX HLSL), texCUBE(s, t) (DirectX HLSL). Ниже приведен пример трехмерной выборки:
texture tex0;
sampler3D s_3D;
float3 sample_3D(float3 tex : TEXCOORD0) : COLOR
{
return tex3D(s_3D, tex);
}
В этом объявлении сэмплера используется состояние сэмплера по умолчанию для параметров фильтра и режима адресации.
Вот соответствующий пример выборки из куба:
texture tex0;
samplerCUBE s_CUBE;
float3 sample_CUBE(float3 tex : TEXCOORD0) : COLOR
{
return texCUBE(s_CUBE, tex);
}
И, наконец, вот пример выборки 1D:
texture tex0;
sampler1D s_1D;
float sample_1D(float tex : TEXCOORD0) : COLOR
{
return tex1D(s_1D, tex);
}
Так как среда выполнения не поддерживает трехмерные текстуры, компилятор будет использовать трехмерную текстуру с знанием, что координата y не имеет значения. Так как tex1D(s, t) (DirectX HLSL) реализован в виде выборки текстуры 2D, компилятор может эффективно выбрать y-компонент. В некоторых редких сценариях компилятор не может выбрать эффективный компонент y, в этом случае он выдает предупреждение.
texture tex0;
sampler s_1D_float;
float4 main(float texCoords : TEXCOORD) : COLOR
{
return tex1D(s_1D_float, texCoords);
}
Этот конкретный пример неэффективн, так как компилятор должен переместить входную координату в другой регистр (поскольку 1D-поиск реализуется как 2D-поиск, а координата текстуры объявлена как float1). Если код перезаписывается с помощью входных данных float2 вместо float1, компилятор может использовать входную координату текстуры, так как он знает, что y инициализирован в чем-то.
texture tex0;
sampler s_1D_float2;
float4 main(float2 texCoords : TEXCOORD) : COLOR
{
return tex1D(s_1D_float2, texCoords);
}
Все поиски текстур могут быть дополнены "bias" или "proj" (т. е. tex2Dbias (DirectX HLSL), texCUBEproj (DirectX HLSL)). При суффиксе "proj" координата текстуры делится на w-компонент. С "предвзятостью", уровень MIP перемещается компонентом w-component. Таким образом, все запросы текстур с суффиксом всегда принимают на вход float4. tex1D(s, t) (DirectX HLSL) и tex2D(s, t) (DirectX HLSL) игнорируют соответственно компоненты yz- и z.
Пробники также могут использоваться в массиве, хотя в настоящее время серверные компоненты не поддерживают динамический доступ к массиву пробников. Поэтому это допустимо, так как его можно разрешить во время компиляции:
tex2D(s[0],tex)
Однако этот пример недопустим.
tex2D(s[a],tex)
Динамический доступ к сэмплерам в основном полезен для написания программ с использованием циклов с литералами. Следующий код иллюстрирует доступ к массиву sampler:
sampler sm[4];
float4 main(float4 tex[4] : TEXCOORD) : COLOR
{
float4 retColor = 1;
for(int i = 0; i < 4;i++)
{
retColor *= tex2D(sm[i],tex[i]);
}
return retColor;
}
Примечание
С помощью среды выполнения отладки Microsoft Direct3D можно поймать несоответствия между количеством компонентов в текстуре и образцом.
Функции разбивают большие задачи на небольшие. Небольшие задачи проще отладить и повторно использовать их после того, как это было проверено. Функции можно использовать для скрытия сведений о других функциях, что упрощает выполнение программы, состоящей из функций.
Функции HLSL похожи на функции C несколькими способами: они содержат определение и тело функции, и они объявляют возвращаемые типы и списки аргументов. Как и функции C, проверка HLSL выполняет проверку типов аргументов и возвращаемого значения во время компиляции шейдера.
В отличие от функций на C, входные функции HLSL используют семантику для привязки аргументов функций к входным и выходным данным шейдера (функции HLSL, вызываемые внутренне, игнорируют семантику). Это упрощает привязку буферных данных к шейдеру и привязке выходных данных шейдера к входным данным шейдера.
Функция содержит объявление и тело, и объявление должно предшествовать телу.
float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
return mul(inPos, WorldViewProj );
};
Объявление функции включает всё, что находится перед фигурными скобками:
float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
Объявление функции содержит следующее:
- Тип возвращаемого значения
- Имя функции
- Список аргументов (необязательно)
- Семантика вывода (необязательно)
- Заметка (необязательно)
Возвращаемый тип может быть любым из базовых типов данных HLSL, таких как float4:
float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
...
}
Возвращаемый тип может быть структурой, которая уже определена:
struct VS_OUTPUT
{
float4 vPosition : POSITION;
float4 vDiffuse : COLOR;
};
VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
...
}
Если функция не возвращает значение, void можно использовать в качестве возвращаемого типа.
void VertexShader_Tutorial_1(float4 inPos : POSITION )
{
...
}
В объявлении функции возвращаемый тип всегда появляется первым.
float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
Список аргументов объявляет входные аргументы функции. Он также может объявлять значения, которые будут возвращены. Некоторые аргументы являются входными и выходными аргументами. Ниже приведен пример шейдера, который принимает четыре входных аргумента.
float4 Light(float3 LightDir : TEXCOORD1,
uniform float4 LightColor,
float2 texcrd : TEXCOORD0,
uniform sampler samp) : COLOR
{
float3 Normal = tex2D(samp,texcrd);
return dot((Normal*2 - 1), LightDir)*LightColor;
}
Эта функция возвращает окончательный цвет, то есть сочетание образца текстуры и светлого цвета. Функция принимает четыре входных данных. Два входных данных имеют семантику: LightDir имеет TEXCOORD1 семантику, а texcrd имеет TEXCOORD0 семантику. Семантика означает, что данные для этих переменных будут поступать из буфера вершин. Несмотря на то, что переменная LightDir имеет TEXCOORD1 семантику, параметр, вероятно, не является координатой текстуры. Семантический тип TEXCOORDn часто используется для определения семантики для типа, который не является предопределённым (для направления света нет входных данных вершинного шейдера).
Остальные два входных элемента LightColor и samp помечены ключевым словом "однородный" с обозначением и. Это однородные константы, которые не изменятся между вызовами рисования. Значения этих параметров приходят из глобальных переменных шейдера.
Аргументы можно обозначить как входные с помощью ключевого слова in, а выходные — с помощью ключевого слова out. Аргументы не могут передаваться по ссылке; однако аргумент может быть как входным, так и выходным, если он объявлен с ключевым словом inout. Аргументы, передаваемые функции и помеченные ключевым словом inout, считаются копиями оригинала до тех пор, пока функция не завершит выполнение, после чего они копируются обратно. Ниже приведен пример использования inout:
void Increment_ByVal(inout float A, inout float B)
{
A++; B++;
}
Эта функция увеличивает значения в A и B и возвращает их.
Текст функции — это весь код после объявления функции.
float4 VertexShader_Tutorial_1(float4 inPos : POSITION ) : POSITION
{
return mul(inPos, WorldViewProj );
};
Тело состоит из инструкций, окруженных фигурными скобками. Текст функции реализует все функциональные возможности с помощью переменных, литералов, выражений и инструкций.
Тело шейдера выполняет два действия: оно производит умножение матриц и возвращает результат float4. Умножение матрицы осуществляется с помощью функции mul (DirectX HLSL), которая выполняет умножение 4х4 матрицы. mul (DirectX HLSL) называется внутренней функцией, потому что она уже встроена в библиотеку функций HLSL. Встроенные функции подробно рассматриваются в следующем разделе.
Перемножение матриц объединяет входной вектор Pos и составную матрицу WorldViewProj. Результатом являются данные о положении, преобразованные в экранное пространство. Это минимальная возможная обработка шейдера вершин. Если бы мы использовали конвейер фиксированной функции вместо шейдера вершин, данные вершин можно было бы нарисовать после этого преобразования.
Последняя инструкция в теле функции — это оператор return. Как и в языке C, этот оператор возвращает управление из функции в оператор, который вызвал функцию.
Типы возвращаемых функций могут быть любым из простых типов данных, определенных в HLSL, включая bool, int half, float и double. Возвращаемые типы могут быть одним из сложных типов данных, таких как векторы и матрицы. Типы HLSL, ссылающиеся на объекты, не могут использоваться в качестве возвращаемых типов. К ним относятся пиксельный шейдер, шейдер вершин, текстура и сэмплер.
Приведен пример функции, которая использует структуру для возвращаемого значения.
float4x4 WorldViewProj : WORLDVIEWPROJ;
struct VS_OUTPUT
{
float4 Pos : POSITION;
};
VS_OUTPUT VS_HLL_Example(float4 inPos : POSITION )
{
VS_OUTPUT Out;
Out.Pos = mul(inPos, WorldViewProj );
return Out;
};
Тип возвращаемого значения float4 был заменен структурой VS_OUTPUT, которая теперь содержит один элемент float4.
Оператор return сигнализирует конец функции. Это простейшая инструкция возврата. Он возвращает элемент управления из функции в вызывающую программу. Он не возвращает значения.
void main()
{
return ;
}
Оператор return может возвращать одно или несколько значений. В этом примере возвращается литеральное значение:
float main( float input : COLOR0) : COLOR0
{
return 0;
}
В этом примере возвращается скалярный результат выражения:
return light.enabled;
В этом примере возвращается значение float4, созданное из локальной переменной и литерала:
return float4(color.rgb, 1) ;
В этом примере возвращается значение float4, созданное из результата, возвращаемого внутренней функцией, и несколько литеральных значений:
float4 func(float2 a: POSITION): COLOR
{
return float4(sin(length(a) * 100.0) * 0.5 + 0.5, sin(a.y * 50.0), 0, 1);
}
В этом примере возвращается структура, содержащая один или несколько элементов:
float4x4 WorldViewProj;
struct VS_OUTPUT
{
float4 Pos : POSITION;
};
VS_OUTPUT VertexShader_Tutorial_1(float4 inPos : POSITION )
{
VS_OUTPUT out;
out.Pos = mul(inPos, WorldViewProj );
return out;
};
Большинство текущих аппаратных средств шейдера вершин и пикселей предназначены для запуска линии шейдера по строкам, выполняя каждую инструкцию один раз. HLSL поддерживает управление потоками, включающее статические ветви, предикатные инструкции, статические циклы, динамическое ветвление и динамический цикл.
Ранее использование инструкции if приводило к коду шейдера на языке ассемблера, реализующему как часть кода для if, так и else часть потока выполнения кода. Ниже приведен пример кода HLSL, скомпилированного для vs_1_1:
if (Value > 0)
oPos = Value1;
else
oPos = Value2;
Ниже приведен результирующий код сборки:
// Calculate linear interpolation value in r0.w
mov r1.w, c2.x
slt r0.w, c3.x, r1.w
// Linear interpolation between value1 and value2
mov r7, -c1
add r2, r7, c0
mad oPos, r0.w, r2, c1
Некоторые аппаратные средства позволяют выполнять статические или динамические циклы, но большинство из них требуют линейного выполнения. В моделях, не поддерживающих циклы, все циклы должны быть развернуты. Примером является образец DepthOfField , использующий развёрнутые циклы даже для шейдеров ps_1_1.
Теперь HLSL включает поддержку для каждого из этих типов управления потоками:
- статическое ветвление
- Предикатные инструкции
- статическое циклирование
- динамическое ветвление
- динамическое циклирование
Статическое ветвление позволяет включать или отключать блоки кода шейдера на основе логической константы шейдера. Это удобный метод включения или отключения путей кода на основе типа объекта, который в данный момент обрабатывается. Между вызовами рисования можно решить, какие функции необходимо поддерживать с текущим шейдером, а затем задать логические флаги, необходимые для получения этого поведения. Любые утверждения, отключенные логической константой, пропускаются во время выполнения шейдера.
Наиболее знакомая поддержка ветвления — это динамическое ветвление. При динамическом ветвлении условие сравнения находится в переменной, что означает, что сравнение выполняется для каждой вершины или каждого пикселя во время исполнения (в отличие от случая, когда сравнение происходит во время компиляции или между двумя вызовами рисования). Снижение производительности — это стоимость ветвления, плюс стоимость инструкций, выполненных на стороне выбранной ветви. Динамическая ветвь реализована в модели шейдера 3 или выше. Оптимизация шейдеров, работающих с этими моделями, аналогична оптимизации кода, выполняемого на ЦП.
-
Руководство по программированию для HLSL