Создание шейдеров HLSL в Direct3D 9

Основы Vertex-Shader

При работе программируемый шейдер вершин заменяет обработку вершин, выполняемую графическим конвейером Microsoft Direct3D. При использовании вершинного шейдера сведения о состоянии операций преобразования и освещения игнорируются конвейером фиксированной функции. Если шейдер вершин отключен и возвращается обработка фиксированной функции, применяются все текущие параметры состояния.

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

Как минимум, вершинный шейдер должен выводить положение вершин в однородном пространстве клипа. При необходимости вершинный шейдер может выводить координаты текстуры, цвет вершин, освещение вершин, факторы тумана и т. д.

Основы Pixel-Shader

Обработка пикселей выполняется пиксельными шейдерами для отдельных пикселей. Пиксельные шейдеры работают совместно с вершинными шейдерами; Выходные данные вершинного шейдера предоставляют входные данные для пиксельного шейдера. Другие пиксельные операции (туманное наложение, операции набора элементов и наложение целевых объектов отрисовки) выполняются после выполнения шейдера.

Стадии текстуры и состояния выборки

Пиксельный шейдер полностью заменяет функцию смешивания пикселей, заданную в блендере с несколькими текстурами, включая операции, ранее определенные состояниями стадии текстуры. Операции выборки и фильтрации текстур, управляемые стандартными состояниями стадии текстуры для минификации, увеличения, фильтрации 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};

Объявления данных могут использовать любой допустимый тип, включая:

Шейдер может иметь переменные верхнего уровня, аргументы и функции.

// 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;
}

Входная структура определяет данные из буфера вершин, которые будут предоставлять входные данные шейдера. Этот шейдер сопоставляет данные из позиций, нормальных элементов и элементов blendweight буфера вершин в регистры шейдера вершин. Входной тип данных не должен точно соответствовать типу данных объявления вершины. Если они не совпадают точно, данные вершин будут автоматически преобразованы в тип данных 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 s = sampler_state 
{ 
  texture = NULL; 
  mipfilter = LINEAR; 
};

Ниже приведен пример кода для примера двухd-текстуры:

texture tex0;
sampler2D s_2D;

float2 sample_2D(float2 tex : TEXCOORD0) : COLOR
{
  return tex2D(s_2D, tex);
}

Текстура объявляется с помощью переменной текстуры tex0.

В этом примере объявляется переменная выборки с именем s_2D. Средство выборки содержит состояние выборки внутри фигурных скобок. Сюда входит текстура, которая будет выборка, и при необходимости состояние фильтра (то есть режимы переноса, режимы фильтрации и т. д.). Если состояние выборки опущено, применяется состояние выборки по умолчанию, определяющее линейную фильтрацию и режим переноса для координат текстуры. Функция выборки принимает двухкомпонентную координату текстуры с плавающей запятой и возвращает двухкомпонентный цвет. Он представлен возвращаемым типом 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);
}

Так как среда выполнения не поддерживает 1D-текстуры, компилятор будет использовать двухмерную текстуру, зная, что координата Y не имеет значения. Так как tex1D(s, t) (DirectX HLSL) реализуется как двухмерный поиск текстуры, компилятор может выбрать компонент Y эффективным образом. В некоторых редких сценариях компилятор не может выбрать эффективный компонент y, и в этом случае он выдает предупреждение.

texture tex0;
sampler s_1D_float;

float4 main(float texCoords : TEXCOORD) : COLOR
{
    return tex1D(s_1D_float, texCoords);
}

Этот конкретный пример неэффективен, так как компилятор должен переместить входную координату в другой регистр (так как 1D-поиск реализуется в виде двухмерного поиска, а координата текстуры объявляется как 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-компонентом. Таким образом, все подстановки текстур с суффиксом всегда принимают входные данные 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 помечены единым ключевое слово. Это однородные константы, которые не изменяются между вызовами draw. Значения этих параметров поступают из глобальных переменных шейдера.

Аргументы можно пометить как входные данные с ключевое слово и выходные аргументы с ключевое слово 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), которая выполняет умножение матрицы 4x4. mul (DirectX HLSL) называется встроенной функцией, так как она уже встроена в библиотеку функций HLSL. Встроенные функции будут рассмотрены более подробно в следующем разделе.

Матричное умножение объединяет входной вектор Pos и составной матрицы WorldViewProj. В результате данные о положении преобразуются в пространство экрана. Это минимальная обработка вершинного шейдера, который мы можем сделать. Если бы мы использовали конвейер фиксированной функции вместо вершинного шейдера, данные вершин можно было бы отрисовать после выполнения этого преобразования.

Последний оператор в теле функции является оператором return. Как и В, этот оператор возвращает управление из функции оператору, который вызвал функцию.

Типы возвращаемых функций могут быть любым из простых типов данных, определенных в HLSL, включая bool, int half, float и double. Возвращаемые типы могут быть одним из сложных типов данных, таких как векторы и матрицы. Типы HLSL, ссылающиеся на объекты, нельзя использовать в качестве возвращаемых типов. К ним относятся pixelshader, vertexshader, texture и sampler.

Ниже приведен пример функции, которая использует структуру для возвращаемого типа.

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 сигнализирует о конце функции. Это самый простой оператор 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, так и другую сторону потока кода. Ниже приведен пример кода в 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 теперь включает поддержку для каждого из следующих типов управления потоком:

  • статическое ветвление
  • предикатные инструкции
  • статические циклы
  • динамическое ветвление
  • динамическое циклирование

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

Наиболее привычной поддержкой ветвления является динамическое ветвление. При динамическом ветвления условие сравнения находится в переменной. Это означает, что сравнение выполняется для каждой вершины или каждого пикселя во время выполнения (в отличие от сравнения, выполняемого во время компиляции или между двумя вызовами draw). Снижение производительности — это стоимость ветви плюс стоимость инструкций на стороне взятой ветви. Динамическое ветвление реализуется в модели шейдера 3 или более поздней версии. Оптимизация шейдеров, работающих с этими моделями, аналогична оптимизации кода, выполняемого на ЦП.

Руководство по программированию для HLSL