High Level Shader Language

Malá fotografie zobrazuje několik různých prvků scény vytvořených pomocí jazyka HLSL. Jednotlivé prvky jsou záměrně různě deformovány podle různých algoritmů.

High Level Shader Language (zkráceně HLSL) je vyšší programovací jazyk pro psaní shaderů vyvinutý společností Microsoft s podporou DirectX (verze 8 a vyšší) XNA, Xbox a Xbox 360. HLSL je velmi podobný jazyku Cg od společnosti NVIDIA, přičemž syntaxe obou jazyků vychází z jazyka C.

Popis

Jazyk vznikl v rámci procesu postupné transformace fixního vykreslovacího řetězce na řetězec programovatelný a stal se jednou z vyspělejších alternativ k tehdy používaným nižším programovacím jazykům pro psaní shaderů.

Shader Model

V rámci vývoje grafických karet a vykreslovacího řetězce se postupně měnily i vlastnosti a schopnosti jednotlivých shaderů. Aby bylo možné rozlišit jaké technologie z hlediska shaderů konkrétní hardware či software podporuje, jsou potupně zaváděny tzv. verze Shader Modelu (samostatně pro vertex shader i pixel shader). Bohužel tento údaj je zcela porovnatelný pouze v rámci produktů jedné společnosti, protože neexistuje žádná pevná specifikace, která by jasně definovala požadavky pro jednotlivé verze Shader modelů. Vývoj shaderů je řízen výrobci GPU — společnostmi nVidia a ATI (nyní AMD) — a společností Microsoft, která vyvíjí grafické rozhraní Direct3D. Společnost Microsoft uveřejnila svou vlastní specifikaci pro Shader model[1].

Shader Model 1.0

Fixní vykreslovací řetězec omezoval realizaci vlastního formátu pro uložení vrcholu. Toto omezení odstranilo zavedení Vertex Shaderu 1.0 a 1.1.

Jeho instrukční sada je velice omezená. Většina instrukcí, které známe z běžných procesorů zde schází. Naopak jsou zde speciální instrukce, které se hodí pro výpočet osvětlení apod. Délka programu vertex shaderu je zde omezena podle použité grafické karty nejčastěji na 128 instrukcí.

Struktura Pixel Shaderu verzí 1.1 až 1.3, se od sebe příliš neliší. Instrukční sada této verze se spíše podobá CISC procesoru, tzn. že obsahuje poměrně velké množství specializovaných instrukcí převážně na nabírání texelu z textury. Podívejme se na schematický obrázek shaderu:

Další verze Pixel Shader 1.4 už přinesla změny převážně v instrukční sadě a způsobu výběru texelu. Přesto je tato verze plně zpětně kompatibilní se starší řadou. Přibyly dva texturové a tři odkládací registry.

Shader Model 2.0

Ve vertex shaderu verze 2.0 se objevilo hned několik vylepšení. Bylo přidáno několik nových aritmetických instrukcí a délka programu byla prodloužena na 256 instrukcí. Umožňuje skoky, podmíněné příkazy nebo smyčky, takže program ve vertex shaderu se může libovolně větvit.

Kromě několika nových texturových instrukcí, Pixel Shader 2.0 přináší aritmetické instrukce podobné těm ve vertex shaderu. Maximální délka programu byla zvětšena na 32 texturovacích a 64 aritmetických instrukcí. Byly přidány instrukce pro vektorové výpočty (normalizace vektoru, násobení maticí, vektorový součin).

Shader Model 3.0

Vertex Shader 3.0 přinesla prodloužení programu na 512 instrukcí. Specifikace jednotky vertex shaderu byla rozšířena o čtení dat z textury. Počet pracovních registrů byl zvýšen na 32 a ke statickému řízení toku programu přibylo dynamické.

Jednotka Pixel Shaderu 3.0 se v programátorském modelu velice přiblížila jednotce vertex shaderu. Maximální počet instrukcí byl navýšen na 512, počet pracovních registrů vzrostl na 32 a počet registrů konstant na 224. Pixel shader verze 3.0 umí statické i dynamické řízení toku programu.

Shader Model 4.0

Ve čtvrté verzi Shader Modelu nabízejí všechny shadery stejný základní programátorský model. Každý ze tří shaderů (vertex, geometry, pixel) pak přidává funkčnost, která je specifická pro danou fázi grafického řetězce. Délka programu shaderu není omezena.

HLSL profily překladu

Profilem překladu lze specifikovat cílovou verzi vertex/pixel shaderu. Dostupné profily shrnuje následující tabulka:

ProfilVerze DirectXSpecifikace
vs_1_18.0Vertex Shader verze 1.1
vs_2_09.0Vertex Shader verze 2.0
vs_2_x9.0a, 9.0brozšířený Vertex shader verze 2.0
vs_2_a9.0aoptimalizováno pro model shaderu na NVIDIA GeForce FX
vs_3_09.0cVertex shader verze 3.0
vs_4_010.0Vertex shader verze 4.0
vs_4_110.1Vertex shader verze 4.1
vs_5_011.0Vertex shader verze 5.0
ps_1_18.0Pixel Shader verze 1.1
ps_1_28.1Pixel Shader verze 1.2
ps_1_38.1Pixel Shader verze 1.3
ps_1_48.1Pixel Shader verze 1.4
ps_2_09.0Pixel Shader verze 2.0
ps_2_0a9.0aoptimalizováno pro model shaderu na NVIDIA GeForce FX
ps_2_0b9.0boptimalizováno pro model shaderu na ATI Radeon X700, X800, X850
ps_2_x9.0rozšířený Pixel Shader verze 1.1
ps_3_09.0cPixel Shader verze 3.0
ps_4_010.0Pixel Shader verze 4.0
ps_4_110.0Pixel Shader verze 4.1
ps_5_010.0Pixel Shader verze 5.0

Operátory

HLSL poskytuje operátory známé z jazyka C, navíc je k dispozici speciální operátor swizzle[2].

Kvalifikátory

  • varying — proměnný datový typ. Hodnota se mění každým vykonání shaderu (typicky pozice, normála aj.)
  • uniform — globální proměnné v shaderu, které jsou inicializovány externě aplikací nebo běhovým prostředím. Hodnota zůstává konstantní pro každé vykonání shaderu. (typicky barva materiálu, transformační matice)
  • const — proměnnou nelze v rámci shaderu změnit
  • static — lokální proměnná, jejichž hodnota po skončení průběhu funkce zůstává zachována.

Datové typy

Skalární typy

  • bool — pravdivostní datový typ (true/false)
  • int — 32bit, celé číslo se znaménkem
  • uint — 32bit, celé číslo bez znaménka
  • half — 16bit, číslo s plovoucí desetinnou čárkou
  • float — 32bit, číslo s plovoucí desetinnou čárkou
  • double — 64bit, číslo s plovoucí desetinnou čárkou

Vektorové typy

Vektor je homogenní datový typ. HLSL podporuje až čtyř složkové vektory. Vektorový typ můžete definovat dvěma ekvivalentními způsoby. V následující ukázce je definován třísložkový vektor, kde každá složka je datového typu float:

  • float3 prikladVektor;
  • vector <float, 3> prikladVektor;

Vektor můžeme při deklaraci přímo inicializovat:

  • float3 fVector = { 0.2f, 0.3f, 0.4f };
  • vector <float, 3> fVector = { 0.2f, 0.3f, 0.4f };

K jednotlivým složkám vektoru smíme přistupovat pomocí definovaných složek x, y, z, w nebo r, g, b, a. Oba způsoby jsou ekvivalentní, ovšem nesmějí se vzájemně kombinovat.

float4 vektor;

vektor.x = 9; /* správně */
vektor.yz = 0; /* správně */

vektor.a = 5; /* správně */
vektor.rg = 0; /* správně */

vektor.ay = 0; /* nelze */

Matice

Postup při deklaraci i inicializaci matice je podobný jako u vektoru. Obecný formát deklarace matice je následující:

datový_typ počet_řádkůxpočet_sloupců identifikátor

Ukázka deklarace matice 4x4 typu float:

  • float4x4 prikladMatice;
  • vector <float, 3> prikladVektor;

Inicializace matice:

float2x2 fMatrix = { 0.0f, 0.1, 2.1f, 2.2f };

Přistoupit k jednotlivým složkám matice můžeme dvěma způsoby:

  • číslování od nuly
_m00, _m01, _m02, _m03
_m10, _m11, _m12, _m13
_m20, _m21, _m22, _m23
_m30, _m31, _m32, _m33
  • číslování od jedničky
_11, _12, _13, _14
_21, _22, _23, _24
_31, _32, _33, _34
_41, _42, _43, _44

V obou případech první číslo značí číslo řádku a druhé číslo sloupce.

Příklad[2]

float4x4 matice;

matice._11 = 1;
matice._m00_m11_m22_m33 = 1;

float2x2 fMatrix = { 1.0f, 1.1f, // row 1
                     2.0f, 2.1f }; // row 2 
                    
float temp;

temp = fMatrix[0][0]; // cteni slozky 0, 0
temp = fMatrix[0][1]; // cteni slozky 0, 1

float2 temp2;
temp2 = fMatrix[0];

Sampler, Texture a Shader Type

  • Sampler — handler na texturový objekt
  • Texture — strukturovaná kolekce dat pro ukládání texelů. Texel je nejmenší možnou jednotkou textury, z které lze číst nebo do ní zapisovat (lze asociovat s pixelem). Každý texel obsahuje tři nebo čtyři složky uspořádané podle daného formátu. Na rozdíl od bufferu lze texturu filtrovat pomocí sampleru čtením shaderovací jednotkou. Typ textury určuje jak bude textura filtrována. Textury mohou být: Texture1D, Texture1DArray, Texture2D, Texture2DArray, Texture3D, TextureCube[2]
  • Shader Type — objekt realizující shader

Buffer

Generické datové úložiště. Data v bufferu jsou přístupná jako pole prvků. Ukázka: Buffer<float4> g_Buffer;

Vstupní a výstupní proměnné

Parametry procházející skrz grafický vykreslovací řetězec je třeba označit jako vstup nebo výstup.

Syntaxe pro označení identifikátoru: Shader má definovány množiny vstupních a výstupních sémantických atributů (POSITION, BLENDWEIGHT, BLENDINDICES, NORMAL, PSIZE, TEXCOORD, TANGENT, BINORMAL, TESSFACTOR, POSITIONT, COLOR, FOG, DEPTH, SAMPLE). Pro vstupní proměnné se kterými programátor chce dále pracovat je třeba vytvořit speciální strukturu. Identifikátory jednotlivých složek této struktury jsou odděleny symbolem : od sémantického atributu. Obdobně se postupuje i při výběru parametrů, které chceme předat na výstup vertex shaderu.

struct VertexShaderInput
{
    float4 vPosition : POSITION;
    float3 vNormal : NORMAL;
    float4 vBlendWeights : BLENDWEIGHT;
};

struct VertexShaderOutput
{
    float4 vPosition : POSITION;
    float4 vDiffuse : COLOR;

};

Ve výše uvedené ukázce je atribut vPosition je označen jako POSITION, což znamená že ve výsledném programu bude proměnná namapována na výstupní registr.

Řídící struktury a funkce

HLSL nabízí klasické řídící struktury jejichž konstrukce je stejná jako v jazyce C, navíc jsou rozšířeny o tzv. atributy[2]. Jazyk podporuje tvorbu uživatelských funkcí a současně disponuje souborem funkcí vestavěných, které jsou zaměřeny zejména na grafické operace. Tyto funkce dělíme na matematické funkce a specializované funkce pracující s texturami.

if else

Příkaz if slouží k větvení programu. Vyhodnocením podmínkového výrazu rozhodne zdali se daný blok příkazů provede (podmínka je pravdivá) nebo ne (podmínka je nepravdivá). K příkazu if lze přidat příkaz else. Příkaz else určuje blok příkazů, které se provedou pouze pokud je podmínka nepravdivá. Formát konstrukce if, else je následující:

[Atribut] if ( výraz )
{
    blok příkazů 1;
}
else
{
    blok příkazů 2;
}

Zadání položky Atribut je volitelné a určuje způsob kompilace příkazu if. Může nabývat hodnot (flatten, branch):

Příklad

[branch] if(x)
{
    x = sqrt(x);
}

switch

Příkaz switch slouží k výběru jedné z několika větví programu, která se má provést v závislosti na nějaké celočíselné hodnotě. Jeho formát je následující:

[Atribut] switch( celočíselný výraz)
{
    case hodnota1 :
        { blok příkazů; }
        break;
    case hodnota2 :
        { blok příkazů;}
        break;
    ...
    case hodnotaN :
        { blok příkazů; }
        break;
    default :
        { blok příkazů; }
        break;
}

Zadání položky Atribut je volitelné a určuje způsob kompilace příkazu switch. Může nabývat těchto hodnot (flatten, branch, forcecase, call).

Příklad

[branch] switch( a )
{
    case 0:
        return 0; 
    case 1:
        return 1; 
    case 2:
        return 3; 
    default:
        return 6; 
}

for

Příkaz cyklu for umožňuje opakovat jeden, nebo více příkazů. Konstrukce cyklu for:

[Atribut] for ( inicializace; test podmínky; inkrementace )
{
    blok příkazů;
}

Zadání položky Atribut je volitelné a určuje způsob kompilace příkazu for. Může nabývat těchto hodnot (unroll, loop, fastopt, allow_uav_condition).

Příklad

float value = 0;
[loop]
for (uint i = 0; i < 4; i++)
{
    value++;
}

while

Cyklus while je cyklus s podmínkou na začátku. Napřed se testuje podmínka, je-li platná, pak se provede tělo cyklu a znovu se testuje podmínka. Není-li platná, program pokračuje za cyklem. Není-li tedy podmínka platná při prvním příchodu na cyklus, neprovede se cyklus ani jednou. Jeho formát vypadá takto:

[Atribut] while ( test podmínky )
{
    blok příkazů;
}

Zadání položky Atribut je volitelné a určuje způsob kompilace příkazu for. Může nabývat těchto hodnot (unroll, loop, fastopt).

Jazyk HLSL umožňuje použít i do while variantu cyklu.

break, continue

Příkazy pro řízení cyklu.

Příkaz discard

Příkaz discard ruší zpracování fragmentu nebo vrcholu. Alternativou k tomuto příkazu je funkce clip().

Seznam aritmetických funkcí

acos, asin, atan, ceil, cos, cosh, degrees, dot, lerp, log, log10, mul, pow, radians, rsqrt, sin, sincos, sinh, step, sqrt, tan, tanh.

Seznam geometrických funkcí

distance, length, normalize, reflect, refract

Seznam funkcí pro diferenciální výpočty

ddx, ddy

Funkce pro práci s texturami

Prvním argumentem při zavolání texturovací funkce je vždy objekt typu sampler. Použitá funkce musí vždy souhlasit s dimenzí textury.

Seznam funkcí pro práci s texturami:

FunkcePopis
tex1D, tex2D, tex3D, texCUBEVzorkuje texturu.
tex1Dbias, tex2Dbias, tex3Dbias, texCUBEbiasVzorkuje texturu s mipmap biasem. Můžeme tak ovlivnit, které úrovně mipmapy se budou vzorkovat. Jako bias se použije 4. souřadnice (a nebo w).
tex1Dproj, tex2Dproj, tex3Dproj, texCUBEprojTexturové souřadnice se před vzorkováním vydělí 4. složkou (a nebo w) tj. provede se projekce souřadnic.

Jednotlivé texturovací funkce mohou mít více přetížených alternativ.

Direktivy preprocesoru

Pro řízení předzpracování zdrojového kódu, je k dispozici sada preprocesorových direktiv známých z jazyka C.

Komentáře

Stejné jako v jazycích C, C++

/* krátká verze */
// celořádková verze

Proces překladu

Programy napsané v HLSL musejí být nejprve přeloženy do byte kódu překladačem, který je součástí knihovny D3DX (součást DirectX Graphics). Výsledný byte kód je binární reprezentací jazyka podobného jazyku symbolických instrukcí. Takto přeložený program už může být použit jako vstup pro metodu rozhraní Direct3D, která se postará o vytvoření strojové verze shaderu.

HLSL kód[3]:

float4 main(float4 t: TEXCOORD0) : SV_Target
{
    if (t.x > t.y)
        return t.xyzw;
    else
        return t.wzyx;
}

Odpovídající DX9 kód[3]:

add r0.x, -v0.x, v0.y
cmp oC0, r0.x, v0.wzyx, v0

Odpovídající DX10 kód (sémantika původního kódu zůstává více zachována)[3]:

float4 main(float4 t: TEXCOORD0) : SV_Target
{
    lt r0.x, v0.y, v0.x
    if_nz r0.x  // <--- POZOR! Opravdu bylo záměrem vytvořit větvení?
        mov o0.xyzw, v0.xyzw
        ret 
    else 
        mov o0.xyzw, v0.wzyx
        ret 
    endif 
}

Pro ukládání shaderovacích programů do souboru byl zaveden nový formát s příponou FX.

Ukázka vertex shaderu

Ukázka vertex shaderu pro Direct3D 9[4].

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;

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

    // Normála
    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;
    
    // Výstup
    out.vPosition    = mul(float4(vPosition + vNormal * Len, 1), mTot);
    out.vDiffuse  = dot(vLight,vNormal);

    return out;
}

Ukázka geometry shaderu

Ukázka vertex shaderu pro Direct3D 10[5].

void GSScene(triangleadj GSSceneIn input[6], inout TriangleStream<PSSceneIn> OutputStream)
{
    PSSceneIn output = (PSSceneIn)0;

    for (uint i=0; i<6; i+=2)
    {
        output.Pos = input[i].Pos;
        output.Norm = input[i].Norm;
        output.Tex = input[i].Tex;

        OutputStream.Append(output);
    }

    OutputStream.RestartStrip();
}

Ukázka pixel shaderu

Ukázka jednoduchého pixel shaderu[6].

float4 PS(float vPos : VPOS, float2 tex : TEXCOORD0) : COLOR
{
    ...
    return float4(1.0f, 0.3f, 0.7f, 1.0f);
}

Odkazy

Reference

Související články

Ostatní jazyky pro psaní shaderů

Externí odkazy

Média použitá na této stránce

HLSL-Examples-1.PNG
Autor: CaliCoastReplay, Licence: CC0
A small photo showing several different scene elements shaded using HLSL. The statue is distorted physically based on location. The inner door texture is distorted physically based on color intensity. The background square is transformed and rotated algorithmically and the water ripple reflecting the entire scene is also a shader.