Зачем нужны семантики в HLSL?

В GLSL есть varying, позволяющий передавать данные между вершинным и фрагментным шейдерами. В HLSL, насколько я понял, вместо этого используются семантики.

Можно ли как-то передавать данные в HLSL по-нормальному? Есть ли в HLSL аналог varying?


Ответы (1 шт):

Автор решения: user7860670

Стоит начать с того, что в GLSL результаты работы вершинного шейдера передаются в пиксельный во-первых неявно, ведь точка входа в шейдер объявляется возвращающей void; а во-вторых передаются они сразу несколькими окольными способами: посредством именованных блоков (named block), таких как gl_Position; посредством глобальных переменных со спецификаторами varying и/или in out. Причем конкретный доступный набор и применимость этих средств зависит от используемой версии OpenGL, профиля и задействованных расширений. В последний версиях varying был объявлен устаревшим и доступным только в профиле совместимости.

В DirectX же результаты работы вершинного шейдера передаются в пиксельный явно - это значение, возвращаемое точкой входа в шейдер. Никакие аналоги для named block, varying, out в DirectX не нужны.

А семантика позволяет указывать способ использования того или иного поля среди данных, передаваемых между шейдерами. За использование данных самим графическим конвейером отвечает набор предопределенных семантик, таких как SV_POSITION, а за использование данных пользователем в своих шейдерах отвечают произвольные пользовательские семантики. Коллекция типов и семантик (но не имен!) полей структуры, возвращаемой или принимаемых шейдером определяет его сигнатуру, шейдеры будут работать вместе только когда выходная сигнатура вершинного шейдера будет подходить ко входной сигнатуре пиксельного шейдера.

В OpenGL имеется только набор предопределенных семантик. За отсутствием семантики, как отдельной синтаксической сущности, семантические свойства включены в названия именованных блоков. Например значение, записанное в блок gl_Position будет использовано графическим конвейером аналогично полю с семантикой SV_POSITION, т.е. интерпретировано, как координаты вершины, по которым будет выполняться отсечение, culling и т.п.

Пара примеров (непроверенных) шейдеров для наглядности:

// GLSL
// Вершинный шейдер
/*
где-то в компиляторе объявлен встроенный блок возможных выходных значений
out gl_PerVertex
{
    vec4 gl_Position;
    float gl_ClipDistance[];
    ...
}
*/
#version 100

uniform mat4 u_view_mat;

attribute vec2 a_pos;

varying vec2 v_my_value; // пользовательское выходное значение

void main()
{
    gl_Position = u_view_mat * vec4(a_pos, 0.0, 1.0); // одно возвращаемое поле
    v_my_value = vec2(12.0, 34.0); // второе возвращаемое значение
}
// Пиксельный шейдер
// Тоже использует встроенный блок - gl_FragColor
#version 100

varying vec2 v_my_value;

void main()
{
    gl_FragColor = vec4(v_my_value.x / 78.0, v_my_value.y / 135.0, 0.0, 1.0);
}
// DirectX
// Вершинный шейдер
struct input_t
{
   float2 pos: POSITION;
};

struct output_t
{
    float4 pos: SV_POSITION;
    float2 my_value: RED_GREEN_WEIGHTS;
};

cbuffer state
{
    float4x4 view_mat;
};

output_t main(input_t input)
{
    output_t output;
    output.pos = view_mat * float4(input.pos, 0.0, 1.0);
    output.my_value = float2(12.0, 34.0);
    return output;
}
// Пиксельный шейдер.
struct input_t
{
    float2 my_value: RED_GREEN_WEIGHTS;
};

struct output_t
{
    float4 сolor: SV_TARGET;
};

output_t main(input_t input)
{
    output_t output;
    output.color = float4(input.my_value.x / 78.0, input.my_value.y / 135.0, 0.0, 1.0);
    return output;
}

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

float4 main(float2 my_value: RED_GREEN_WEIGHTS): SV_TARGET
{
    return float4(my_value.x / 78.0, my_value.y / 135.0, 0.0, 1.0);
}
→ Ссылка