Зачем нужны семантики в HLSL?
В GLSL есть varying, позволяющий передавать данные между вершинным и фрагментным шейдерами. В HLSL, насколько я понял, вместо этого используются семантики.
Можно ли как-то передавать данные в HLSL по-нормальному? Есть ли в HLSL аналог varying?
Ответы (1 шт):
Стоит начать с того, что в 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);
}