Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Термины / HLSL

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. Типы данных

ТипЗначение
booltrue или false
int32-bit signed integer
uint32-bit unsigned integer
half16-bit floating point value
float32-bit floating point value
double64-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_DepthDEPTH
SV_PositionPOSITION
SV_TargetCOLOR

Пример использования 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?

Страницы: 1 2 Следующая »

11 июня 2009

#DirectX, #шейдеры


Обновление: 16 февраля 2011

2001—2018 © GameDev.ru — Разработка игр