HLSL
HLSL (High Level Shader Language) — высокоуровневый Си-подобный язык для написания шейдеров, разработанный Microsoft и являющийся частью DirectX.
Аналоги: GLSL (OpenGL), Cg (OpenGL, Direct3D).
HLSL пришел на смену ассемблерному шейдерному языку и позиционируется как более удобный язык для разработки шейдеров. Доступен для Direct3D начиная с версии 9.0 (возможно комбинирование кода на HLSL и asm). Начиная с Direct3D10 ассемблерные вставки запрещены.
Пример кода на HLSL
В качестве простого примера рассмотрим шейдеры, производящие расчет диффузного освещения (попиксельно). От вершинного шейдера поступает нормаль, трансформированная в мировое пространство и текстурные координаты. Oт приложения поступают матрицы трансформаций, текстура и направление источника света.
//================================================== // Вершинный шейдер //================================================== // константы для вершинного шейдера //--------------------------------- float4x4 mWorldViewProj; float4x4 mWorld; // входные данные для вершинного шейдера //-------------------------------------- struct VS_IN { float3 pos : POSITION; float2 uv0 : TEXCOORD0; float3 normal : NORMAL; }; // результат работы вершинного шейдера, подается на вход пиксельного //------------------------------------------------------------------ struct VS_OUT { float4 pos : POSITION; float2 uv0 : TEXCOORD0; // текстурные координаты float3 normal : TEXCOORD1; // нормаль в мировом пространстве }; // непосредственно сам шейдер //--------------------------- void main(in VS_IN In, out VS_OUT Out ) { Out.pos = mul( float4( In.pos,1), mWorldViewProj ); Out.uv0 = In.uv0; Out.normal = mul( In.normal, mWorld ); } //================================================== // Пиксельный шейдер //================================================== // сэмплеры //--------- sampler2D diffuse : register ( s0); // константы //---------- float3 Ln; // направление источника света // пиксельный шейдер //------------------ void main( in PS_Input In, out float4 oColor : COLOR ) { float diff = dot( In.normal, Ln ); float3 color = tex2D( diffuse, In.uv0 ); oColor = float4( diff * color, 1.f ); }
Для сравнения код приведенного выше пиксельного шейдера на ассемблере:
ps_2_0 def c1, 1, 0, 0, 0 dcl t0.xy dcl t1.xyz dcl_2d s0 texld r0, t0, s0 dp3 r0.w, t1, c0 mul r0.xyz, r0, r0.w mov r0.w, c1.x mov oC0, r0
Краткий обзор
1. Типы данных
Тип | Значение |
bool | true или false |
int | 32-bit signed integer |
uint | 32-bit unsigned integer |
half | 16-bit floating point value |
float | 32-bit floating point value |
double | 64-bit floating point value |
2. Векторы
Используя конструкцию вида vector<тип, размерность> можно создавать вектора любого из поддерживаемых типов данных.
Более распространенная форма описания выглядит как конкатенация типа и размерности, например int3 означает трехмерный вектор из целочисленных величин.
Аналогичным образом можно объявить и многомерные структуры, например матрица 4х4 может быть объявлена как float4x4 SomeMatrix;
3. Операторы
Набор операторов аналогичен операторам c++. Не используются оператор ->, а так же невозможна перегрузка операторов.
4. Структуры и классы
В HLSL имеется поддержка структур. Пример структуры (см. так же код шейдеров выше):
struct VS_IN
{
float3 pos : POSITION;
float2 uv0 : TEXCOORD0;
float3 normal : NORMAL;
};
В HLSL для D3D11 введена поддержка классов и интерфейсов. Примеры:
// интерфейс материала //--------------------- interface iBaseMaterial { float3 GetAmbientColor(float2 vTexcoord); float3 GetDiffuseColor( float2 vTexcoord); int GetSpecularPower( ); }; // реализация материала //--------------------- class cBaseMaterial : iBaseMaterial { float3 m_vColor; int m_iSpecPower; float3 GetAmbientColor( float2 vTexcoord) { return m_vColor; } float3 GetDiffuseColor( float2 vTexcoord) { return m_vColor; } int GetSpecularPower( ) { return m_iSpecPower; } };
Использование классов и интерфейсов в совокупности с новой возможностью динамической линковки шейдеров позволяет строить мощные системы освещения.
На момент написания статьи [11.06.09] доступна лишь preview версия D3D11 и beta SDK, которая не содержит подробной документации, но содержит примеры использования новых возможностей с комментариями.
Особенности языка
1. Семантики
В HLSL повсеместно используются семантики - средства языка, позволяющие назначить входному или выходному параметру шейдера информацию о том, как его следует трактовать. Таким образом, например, устанавливается соответствие между результатом работы вершинного шейдера и входом пиксельного.
В примере выше для поля float3 pos структуры VS_OUT указана семантика POSITION, что означает, что вершинный шейдер должен в данное поле записать положение вершины после проецирования. Список поддерживаемых семантик:
Вершинный шейдер. Входные данные.
Семантика | Описание | Тип данных |
BINORMAL[n] | Бинормаль | float4 |
BLENDINDICES[n] | Индекс весовой матрицы | uint |
BLENDWEIGHT[n] | Веса для смешивания | float |
COLOR[n] | Цвет (диффузное и бликовое освещение) | float4 |
NORMAL[n] | Нормаль | float4 |
POSITION[n] | Позиция вершины в пространстве модели | float4 |
POSITIONT | Трансформированное положение вершины | float4 |
PSIZE[n] | Размер точки | float |
TANGENT[n] | Касательная | float4 |
TEXCOORD[n] | Текстурные координаты | float4 |
Вершинный шейдер. Выходные данные.
Семантика | Описание | Тип данных |
COLOR[n] | Цвет (диффузное и бликовое освещение) | float4 |
FOG | Коэффициент затуманивания | float |
POSITION[n] | Позиция вершины в пост-проекционном пространстве.
Каждый вершинный шейдер должен возвращать данные, отмеченные данной семантикой | float4 |
PSIZE | Размер точки | float |
TESSFACTOR[n] | Уровень тесселяции | float |
TEXCOORD[n] | Текстурные координаты | float4 |
Пиксельный шейдер. Входные данные.
Семантика | Описание | Тип данных |
COLOR[n] | Цвет (диффузное и бликовое освещение) | float4 |
TEXCOORD[n] | Текстурные координаты | float4 |
VFACE | Ориентация примитива по отношению к камере: отрицательное значение говорит о том,
что примитив ориентирован к камере обратной стороной | float |
VPOS | Координаты точки (экранное пространство координат) | float2 |
Пиксельный шейдер. Выходные данные.
Семантика | Описание | Тип данных |
COLOR[n] | Цвет | float4 |
DEPTH[n] | Глубина | float |
Семантики для системных значений (System-value semantics)
Данный набор семантик применим только в D3D10 и более поздних версиях.
Таблица аналогов некоторых семантик в D3D9 приведена ниже.
Семантика | Описание | Стадия граф. конвейера | Тип данных |
SV_ClipDistance[n] | Дистанция отсечения1 | Растеризатор | float |
SV_CullDistance[n] | Дистанция отсечения2 | Растеризатор | float |
SV_Coverage | Маска output coverage | Растеризатор | bool |
SV_Depth | Данные буффера глубины | Растеризатор | float |
SV_IsFrontFace | Видимый примитив | Растеризатор | bool |
SV_Position | Позиция вершины в пост-проекционном пространстве | Растеризатор | float4 |
SV_RenderTargetArrayIndex | Индекс для массива Render target'ов | Растеризатор | uint |
SV_SampleIndex | Индекс сэмплера | Пиксельный шейдер | uint |
SV_Target[n], (n=[0,7]) | Массив Render target'ов | Растеризатор | float |
SV_ViewportArrayIndex | Индекс для массива view port'ов | Растеризатор | uint |
SV_InstanceID | Идентификатор копии | Вершинный шейдер | uint |
SV_PrimitiveID | Идентификатор примитива | Геометрический и пиксельный шейдеры | uint |
SV_VertexID | Идентификатор вершины | Вершинный шейдер | uint |
1 - отсечение с помощью near/far clip plane
2 - back/front face culling
Аналоги некоторым семантикам D3D10 в D3D
Семантика Direct3D 10 | Семантика Direct3D 9 |
SV_Depth | DEPTH |
SV_Position | POSITION |
SV_Target | COLOR |
Пример использования system value семантик
Рассмотрим в качестве примера код, иллюстирующий использование сематки SV_PrimitiveID для определения сторон куба. В пиксельном шейдере производится оценка значения SV_PrimitiveID и выбирается цвет:
// Выберем 3 стороны куба и закрасим их разными цветами. // Остальные оставим белыми. static const float4 white = float4(1,1,1,1); static const float4 red = float4( 1,0,0,1); static const float4 green = float4( 0,1,0,1); static const float4 blue = float4( 0,0,1,1); float4 main( in uint face : SV_PrimitiveID ) : SV_TARGET { float4 c = white; if( face >= 0 && face <= 3 ) c = red; if( face >= 4) && face <= 7) ) c = green; if( face >= 8) && face <= 11) ) c = blue; return c; }
2. Размещение констант по регистрам вручную
Помимо прочего в HLSL присутствует возможность привязки констант к регистрам. Для этого используется ключевое слово register. С помощью него можно вручную размещать константы по регистрам. Точно так же можно размещать сэмплеры по слотам. ( строка sampler2D diffuse : register (s0); в примере ). Такая возможность может быть полезной при оптимизации шейдеров. Если не указывать регистр явно, а поручить эту работу компилятору, то данные будут размещены в регистрах по порядку. Например в коде, приведенном выше матрица mWorldViewProj займет регистры c0..c4, матрица mWordl - регистры c5..c8. Аналогично ситуация и с сэмплерами.
3. Swizzle operator
Одной из интересных особенностей языка является обращение к компонентам встроенных типов. Например, пусть у нас имеется вектор объявленный так:
static const float3 params = float3(1,2,3);
Обратившись к нему как params.xyz, получим весктор со значением {1,2,3}. Обращение params.zyx даст результат {3,2,1}, params.zzz - {3,3,3}. Необязательно получать все 3 компоненты вектора, вариант params.xy сработает, а результатом будет вектор {1,2}.
4. Flow control
Для настройки динамического контроля исполнения кода (циклы, ветвления) есть специальный набор команд, называемых атрибутами. Их использование позволяет существенно оптимизировать код шейдера. Наиболее распространенные атрибуты:
Атрибуты для for, while
Атрибут | Описание |
unroll(x) | Цикл разворачивается до прекращения выполнения. Возможно указать максимальное число итераций |
loop | Исполнять код динамически, не разворачивать цикл. |
Атрибуты для if
Атрибут | Описание |
branch | Динамическое ветвление - исполняется только одна ветка в зависимости от условий |
flatten | Исполняются обе ветки, выбирается результат в зависимости от условия |
Атрибуты для switch
Атрибут | Описание |
flatten | Интерпретировать switch как набор операторов if, каждый с атрибутом flatten |
branch | Интерпретировать switch как набор операторов if, каждый с атрибутом branch |
forcecase | Выполнять оператор switch аппаратно |
Пример:
Воспользовавшись атрибутом branch можно превратить конструкцию switch в набор операторов if, причем для ее выполнения будет применено динамическое ветвление (исполняться будет только одна ветка оператора if в зависимости от условия). Таким образом код
[branch] switch(a) { case 0: return 0; case 1: return 1; case 2: return 3; default: return 6; }
эквивалентен следующему:
[branch] if(a == 2 ) return 3; else if( a == 1 ) return 1; else if( a == 0 ) return 0; else return 6;
Что такое HLSL?
11 июня 2009 (Обновление: 16 фев 2011)
Комментарии [4]