Kurz DirectX (44.)

V dnešní lekci se opět vrátíme k našemu starému projektu D3DEngine2, do něhož implementujeme jednoduchou podporu vertex shaderu. Dále vytvoříme jednoduchou iluzi refrakce na vodní hladině právě pomocí vertex shaderu.

44.1. Verze vertex shaderu

Dnes tedy konečně implementujeme vertex shader do našeho enginu. Budeme pokračovat v projektu D3DEngine2, který jsme teď pár lekcí přeskočili. Dříve než se pustíme do třídy zapouzdřující náš vertex shader, povíme si o podpoře vertex shaderu ze strany Direct3D.

Samotný kód vertex shaderu můžete ukládat dvěma způsoby. Nyní se budeme bavit jen o kódu vertex shaderu 1.1 (1.4), tedy o kódu psaném v assembleru. V tomto případě můžete použít offline kompilaci, kdy si kód přeložíte při vývoji a s vaším programem pak dodáte pouze binární soubor s přeloženým vertex shaderem. My použijeme druhou možnost, kdy kód kompilujeme při spuštění aplikace čili načítáme soubor přímo s instrukcemi shaderu. Každý se tedy může podívat na náš zdrojový kód a dokonce program upravit. To by v profesionální aplikaci pochopitelně vadilo, ale pro náš učebnicový příklad je to naopak výhoda. Vertex shader můžete načítat ze souboru nebo z paměťového bloku tzn. že program může být uložen jako řetězec přímo v programu.

Poznámka: Jako externí překladač můžete použít překladač obsažený v DirectX SDK vsa.exe nebo překladač od firmy nVidia nvasm.exe.

První co bychom měli udělat v našem programu je zjištění dostupné verze vertex shaderu. Tuto informaci získáme z caps Direct3D zařízení takto:

3DCAPS9 d3dc;

if(S_OK == m_lpD3DDevice->GetDeviceCaps(&d3dc))
{
    TRACE("Vertex shader version: 0x%04x",
          (WORD)d3dc.VertexShaderVersion);
}

Tento fragment nám vypíše maximální dostupnou verzi vertex shaderu. Pokud chcete získat přímo majoritní resp. minoritní číslo verze, použijte makra D3DSHADER_VERSION_MAJOR() resp. D3DSHADER_VERSION_MINOR().

My přidáme metodu do třídy XDisplay:

BOOL XDisplay::CheckVertexShader(DWORD dwVSVersion)
{
   return (m_dwVSVersion < dwVSVersion) ? FALSE : TRUE;
}

Tato metoda vrátí TRUE, pokud naše grafická karta obsahuje vertex shader verze, kterou zadáme parametrem. Proměnnou m_dwVSVersion nastavíme po vytvoření zařízení následovně:

if( D3DSHADER_VERSION_MAJOR( d3dc.VertexShaderVersion ) == 1 &&
    D3DSHADER_VERSION_MINOR( d3dc.VertexShaderVersion ) == 1)
   m_dwVSVersion = VS_11;

if( D3DSHADER_VERSION_MAJOR( d3dc.VertexShaderVersion ) == 1 &&
    D3DSHADER_VERSION_MINOR( d3dc.VertexShaderVersion ) == 4)
   m_dwVSVersion = VS_14;

if( D3DSHADER_VERSION_MAJOR( d3dc.VertexShaderVersion ) == 2 &&
    D3DSHADER_VERSION_MINOR( d3dc.VertexShaderVersion ) == 0)
   m_dwVSVersion = VS_20;

if( D3DSHADER_VERSION_MAJOR( d3dc.VertexShaderVersion ) == 3 &&
    D3DSHADER_VERSION_MINOR( d3dc.VertexShaderVersion ) == 0)
   m_dwVSVersion = VS_30;

Konstanty VS_verze definujeme vzestupně takto:

#define VS_11 11
#define VS_14 14
#define VS_20 20
#define VS_30 30


Pochopitelně tyto konstanty použijeme i při volání metody CheckVertexShader() následovně:

TRACE("Vertex shader version 1.1: %d", CheckVertexShader(VS_11));
TRACE("Vertex shader version 1.4: %d", CheckVertexShader(VS_14));
TRACE("Vertex shader version 2.0: %d", CheckVertexShader(VS_20));
TRACE("Vertex shader version 3.0: %d", CheckVertexShader(VS_30));

Tyto čtyři makra vypíšou u každé verze VS buď 1 v případě, že daná verze je podporována nebo 0 v opačném případě. Verze vertex shaderu jsou zpětně kompatibilní, čili pokud vaše karta podporuje verzi 2.0, můžete použít kód pro verzi 1.1 (naopak to pochopitelně neplatí). Verzi vertex shaderu pak explicitně vyjádříme instrukcí vs.

44.2. Rozhraní IDirect3DVertexShader9

Předtím než vytvoříme samotný objekt vertex shaderu musíme načíst a zkompilovat náš program. Nyní budeme předpokládat, že program máme v souboru ve formě assembleru. Pro tyto účely knihovna D3DX nabízí funkci D3DXAssembleShaderFromFile(), která naplní buffer zkompilovaným kódem. Shader můžete kompilovat i z paměti pomocí funkce D3DXAssembleShader(), kde se místo jména souboru předává ukazatel do paměti.

Funkce D3DXAssembleShaderFromFile() je deklarována takto:

HRESULT WINAPI D3DXAssembleShaderFromFile(
    LPCTSTR pSrcFile ,
    CONST D3DXMACRO* pDefines ,
    LPD3DXINCLUDE pInclude ,
    DWORD Flags ,
    LPD3DXBUFFER* ppShader ,
    LPD3DXBUFFER * ppErrorMsgs
);

Parametry si rozebereme podrobněji:

pScrFile
    Jméno souboru, ve kterém máte uložený kód vertex shaderu.

pDefines
    Pole struktur D3DXMACRO, pomocí kterých můžete do vertex shaderu dostat vlastní makra. My v našem případě nic takového potřebovat nebudeme a proto necháme NULL.

pInclude
    Ukazatel na rozhrani ID3DXInclude, který se používá pro obsluhu #include příkazů uvnitř vertex shaderů. Tam totiž můžete načíst například konstanty z hlavičkového souboru. Ani tuto možnost nebudeme využívat, proto nechme parametr NULL.

Flags
    Příznaky, kterými můžete řídit chování překladače. Můžete například vypnout optimalizace, zapnout ladicí režim, přeskakovat kontrolu, zajistit, aby se nepoužívaly flow-control instrukce (if-else) atd. My necháme tento parametr rovný nule.

ppShader
    Ukazatel na D3DXBuffer, ve kterém bude po úspěšném překladu náš vertex shader, tedy jeho binární forma. Pokud dojde během překladu k chybě, bude tento parametr NULL.

ppErrorMsgs
    Pokud dojde během překladu k chybě, parametr ppShader bude NULL a naopak ppErrorMsgs bude obsahovat chyby, kterých jsme se dopustili v kódu. Proto je dobré tento parametr rovněž sledovat a jakmile nabývá jiné než nulové hodnoty, buffer vypsat.


Nyní můžeme vytvořit vlastní objekt vertex shaderu s rozhraním IDirect3DVertexShader9. K tomu potřebujeme pouze správně vytvořené zařízení, jehož rozhraní obsahuje metodu CreateVertexShader().

HRESULT CreateVertexShader(
    const DWORD * pFunction ,
    IDirect3DVertexShader9** ppShader
);

Prvním parametrem je ukazatel na binární podobu vertex shaderu. Tedy pokud jste použili výše uvedenou funkci ke kompilaci, zde předáte ukazatel bufferu ppShader. V případě externího překladače předáte přímo načtený shader do paměťového bufferu.
Druhý parametr je výstupní a pokud vše proběhne tak jak má, dostanete ukazatel na rozhraní IDirect3DVertexShader9!

44.3. Použití vertex shaderu

Nyní můžeme náš vertex shader použit při vykreslování trojúhelníků. Pochopitelně ho nebudeme chtít použít pro všechny trojúhelníky ve scéně, ale jen při vykreslování terénu. Musíme tedy nastavit vertex shader před voláním metod DrawIndexedPrimitives(). K tomu použijeme metodu zařízení SetVertexShader(), která má jediný parametr a tím je ukazatel na rozhraní IDirec3DVertexShader9. Po vykreslení musíme vertex shader vypnout stejným příkazem, ale s parametrem NULL.

To pochopitelně nebude stačit pro správnou funkci vertex shader, protože náš program bude používat také konstantní registry. Ty se nastavují ještě před tím, než nastavíme samotný vertex shader metodami SetVertexShaderConstantF(), SetVertexShaderConstantI() a SetVertexShaderConstantB(). Metody se liší jen v typu předávaných parametrů.

Metodou SetVertexShaderConstantF() předáváme do vertex shaderu čtveřice floatů. Jedním voláním můžeme nastavit více registrů najednou. SetVertexShaderConstantI() předáváme celočíselné hodnoty a konečně metodou SetVertexShaderConstantB() booleovské proměnné. Nejčastěji budeme využívat metodu SetVertexShaderConstantF():

HRESULT SetVertexShaderConstantF(
    UINT StartRegister ,
    CONST float * pConstantData ,
    UINT Vector4fCount
);

Parametry:

StartRegister
     Index konstantního registru, kterým začínáme nastavení.

pConstantData
     Ukazatel do pole, ze kterého se čtou jednotlivé hodnoty typu float. Pole tedy musí mít minimálně 4 položky (pro jeden registr). Pokud nastavujete dva registry, tak 8 položek atd. Například matici 4x4 musíme nastavit hned do 4 registrů (registry mají ve vertex shaderu 4 složky).

Vector4fCount
     Počet registrů, které nastavujete. Opět je zde důležité mít na paměti, že registr má 4 položky typu float a proto toto číslo udává počet čtveřic.

Ostatní dvě metody pracují stejně, jen s jinými typy druhého parametru. A to jsou všechny metody, které v tomto příkladu použijeme. Nyní dáme vše dohromady a vytvoříme vlastní třídu XVertexShader.

44.4. Třída XVertexShader

V poslední části použijeme vše nové co jsme se do teď dozvěděli a vytvoříme třídu, která nám trochu ulehčí práci s vertex shadery. Třída bude mít pochopitelně i své rozhraní IVertexShader.

Vrhneme se rovnou na deklaraci, neboť zde již není co vysvětlovat:

class XVertexShader : public IVertexShader
{
   IDirect3DDevice9 *m_pDevice;
   IDirect3DVertexShader9 *m_pVertexShader;

   DWORD m_dwRef;

public:
   virtual HRESULT LoadVertexShader(char* szFile, DWORD dwFlags);
   virtual IDirect3DVertexShader9* GetVertexShader();
   virtual void SetVertexShader();
   virtual void UnsetVertexShader();

   virtual void SetVSConstant(int start, float* data, int count);
   virtual void SetVSConstant(int start, int* data, int count);
   virtual void SetVSConstant(int start, bool* data, int count);
   virtual bool Loaded() {return (m_pVertexShader) ? true : false;}

   XVertexShader(void);
   ~XVertexShader(void);

   virtual HRESULT AddRef();
   virtual HRESULT Release();
};

Třída nám umožní načíst vertex shader ze souboru, zapnout a vypnout vertex shader při vykreslování. Pochopitelně také budeme moci nastavit shaderové konstanty. Pro zajímavost zde ještě uvedu kód metody LoadVertexShader(), která načte soubor, zkompiluje kód a vytvoří objekt IDirect3DVertexShader9. V případě chyby vypíše, proč nešel program zkompilovat:

HRESULT XVertexShader::LoadVertexShader(char* szFile, DWORD dwFlags)
{
   IDisplay *pDis;
   CreateDisplayObject(DISIID_IDisplay, (void**) &pDis);

   ID3DXBuffer* ppShader;
   ID3DXBuffer* ppErrors;

   TRACE("Creation vertex shader from file: %s", szFile);

   char szFilePath[MAX_PATH];
   cmnGetDataFilePath(szFilePath, "effects\\");
   strcat(szFilePath, szFile);

   D3DXAssembleShaderFromFile(szFilePath, NULL, NULL,
                              dwFlags, &ppShader, &ppErrors);

   if(ppErrors)
   {
      TRACE(" %s", (char*)ppErrors->GetBufferPointer());
      SAFE_RELEASE(ppErrors);
      SAFE_RELEASE(pDis);
      return S_FALSE;
   }
   if(ppShader)
   {
      if(D3D_OK == pDis->GetDevice()->CreateVertexShader(
                   (DWORD*)ppShader->GetBufferPointer(),                      &m_pVertexShader))
      {
         TRACE(" VertexShader has been created.");
      }
      m_pDevice = pDis->GetDevice();
      SAFE_RELEASE(ppShader);
      SAFE_RELEASE(pDis);
      return S_OK;
   }
   return S_FALSE;
}

V prvním kroku získáme celou cestu k souboru, který bude uložen v podadresáři effects celé aplikace. Dále zkusíme program zkompilovat použitím funkce D3DXAssembleShaderFromFile(). Pokud nastala chyba, tedy proměnná ppErrors není NULL, vypíšeme obsah chybového bufferu a činnost metody ukončíme. V opačném případě by měl být správně nastaven buffer ppShader, který předáme do metody CreateVertexShader().

44.5. Příklad - iluze refrakce

Na závěr lekce vytvoříme celkem jednoduchý příklad použití vertex shaderu. Vytvoříme iluzi refrakce na vodní hladině tím, že budeme jemně pohupovat vertexy, které jsou pod hladinou. Skutečná refrakce by se spíše hodila pro pixel shader.

Tento efekt bychom bez vertex shaderu dělali jen těžko. Nejspíše bychom museli rozdělit terén na vertexy pod hladinou a nad a pak každé vykreslovat jiným způsobem, ty pod hladinou modifikovat atd. Zkrátka by nás to stálo spoustu práce a výsledek by nebyl příliš efektivní, takže bychom navíc ztratili kus výkonu. S použitím vertex shaderu rozdělíme vertexy až při vykreslování a ty které jsou nad hladinou pouze transformujeme příslušnými maticemi, zatímco vertexům pod vodou budeme měnit z souřadnici.

Rozeberme si nyní program vertex shaderu:

vs_1_1

// Mapovani dat vertexu na vstupni registry VS
dcl_position v0
dcl_normal v1
dcl_color0 v2
dcl_texcoord v3

První instrukcí určíme pro jakou verzi VS je program psaný. Překladač pak pozná, jakým způsobem kód kompilovat. Následuje čtveřice instrukcí, kterými mapujeme vstupní data na vstupní registry (v0-v3). Náš vertex má v programu pozici, normálu, difusní barvu a texturové koordináty.

// presun polohy vertexu do pomocnyho registru, aby sel menit
mov r5, v0

Budeme potřebovat měnit polohu vertexu, takže si celý vektor zkopírujeme do registru, který lze později změnit (narozdíl od vstupního). Nyní provedeme podmíněný příkaz:

// pokud je vyska vertex mensi nez vyska hladiny, r0.x bude 1
slt r0.x, v0.z, c4.x

// omezeni zdola
add r0.y, c4.w, -c4.z // 5 - 4 = 1
sge r0.y, v0.z, r0.y // musi byt vetsi nez 1

// obe podminky musi platit zaroven
mul r0.x, r0.x, r0.y

Instrukce slt uloží do r0.x jedničku pokud bude z souřadnice vrcholu menší než c4. x, kde máme připravenou výšku hladiny jinak 0. Stejným způsobem ale musíme omezit modifikované vertexy i zdola. Nejprve spočítáme konstantu 1 tak, že odečteme c4.w a c4.z, zde máme připravena čísla 5 a 4 (pro jiné účely). Instrukcí sge uložíme do r0.y jedničku v případě, že v0.z je větší nez 1 jinak 0. Poslední instrukcí mul vynásobíme výsledky obou podmínek. Tím zaručíme, že v r0.x bude jednička pokud platí obě podmínky současně (a to platí v případě, že vrchol je pod hladinou a zároveň jeho výška je větší než 1), v opačném případě bude výsledek 0 a tento vertex modifikovat nebudeme.

V dalším kroku vybereme náhodně posun, o který se vertex posune. Tu náhodnost vybereme pomocí normálového vektoru:

// x-ova souradnice normalovyho vektoru
// muze byt zaporna, v to pripadne prictu jednicku

slt r0.y, v1.x, c4.y
add r0.y, r0.y, v1.x

// kladnou slozku normaly vezmu jako nahodny cislo (0-1)
// v1.x * 4 + 5 = (5 - 9)

mad r3.x, r0.y, c4.z, c4.w

// udelam z toho adresu
mov a0.x, r3.x

// posun v Z se bude aplikovat pouze na podvodni vertexy
mul r1.y, r0.x, c[a0.x].x

Bereme x-ovou souřadnici normály a ta může být záporná. Pokud tomu tak je, přičteme jedničku. Nyní jsme dostali náhodné číslo v rozsahu 0-1 (náhodné v tomto případě znamená, že je jiné pro každý vertex). Z tohoto čísla budeme počítat adresu konstantního registru, kde máme uloženy posuny vrcholů. Ty nastavujeme externě z našeho programu. Budeme mít celkem 4 float hodnoty, které budou kmitat okolo 0. Každá jinak rychle a v jiném rozsahu. Pokud každý vertex použije jinou tuto konstantu, bude výsledek maximálně náhodný.
Abychom dostali správnou adresu (konstanty budou uloženy na adrese 5 - 8), vynásobíme číslo 0-1 konstantou 4 a přičteme 5. To celé provede instrukce mad. Nakonec tuto hodnotu zkopírujeme do adresového registru a0.x (zde musíme použít mov), kde se hodnota zaokrouhlí na celé číslo. Závěrem do r1.y uložíme posun vertexu. V r0.x je 1 pro vertex, které se mají pohybovat, pro všechny ostatní je zde nula, takže i posun bude nulový.
Máme vybraný posun, zbýva modifikovat z souřadnici vrcholu:

// modifikace Z souradnice vertexu
add r5.z, v0.z, r1.y

Další funkcí vertex shaderu je výpočet barvy vertex podle osvětlení. My máme ve scéně jediné směrové světlo, kterým je ovlivněna barva vertexu. Do vertex shaderu předáváme směr tohoto světla jako vektor (registr c9), difusní resp. ambientní složky barvy světla jsou uložené v registrech c10 resp. c11.

// N dot L
// we need L vector towards the light, thus negate sign

dp3 r0, v1, -c9

// modulate against light color
mul r0, r0, c10
// diffuse
// modulate against material and ambient
mad oD0, r0, v2, c11

Tímto výpočtem modulujeme barvu vertex proti jednomu směrovému světlu. Nejprve se spočítá skalární součin normály a směru světla. Ten bude tím větší, čím se svírající úhel bude blížit 0 stupňům ( v tom případě bude 1). Touto hodnotou vynásobíme barvu osvětlení. Výsledná barva tedy bude tím tmavší, čím jsou oba vektory různé. Naopak pokud budou oba vektory rovnoběžné, do r0 se uloží plná barva světla. Poslední instrukcí uložíme výslednou barvu modulovanou difusní složkou a přičteme také ambientní složku. Původní barvu vertexu nalezneme v registru v2.

Na závěr musíme transformovat vertex, zapsat výstupní polohu a texturové souřadnice:

// Transformace modifikovaneho vertexu matici: world * view * projection
dp4 oPos.x, r5, c0
dp4 oPos.y, r5, c1
dp4 oPos.z, r5, c2
dp4 oPos.w, r5, c3

mov oT0, v3

V registrech c0-c3 máme připravenou matici složenou ze světové, projekční a pohledové matice. Matici jsme v programu ještě transponovali (tedy proházeli řádky a sloupce). Pokud si na papír nakreslíte co přesně dělá instrukce dp4 s jednotlivými složkami r5 a c0, zjistíte, že výsledná operace vypadá takto:

oPos.x = (r5.x * c0.x) + (r5.y * c0.y) +
         (r5.z * c0.z) + (r5.w * c0.w)
oPos.y = (r5.x * c1.x) + (r5.y * c1.y) +
         (r5.z * c1.z) + (r5.w * c1.w)
oPos.z = (r5.x * c2.x) + (r5.y * c2.y) +
         (r5.z * c2.z) + (r5.w * c2.w)
oPos.w = (r5.x * c3.x) + (r5.y * c3.y) +
         (r5.z * c3.z) + (r5.w * c3.w)

A to není nic jiného než transformace vektoru maticí. Vertex shader končí kopírováním texturových souřadnic na výstup. Obecně platí, že všechny výpočty, které jsou společné pro všechny vertexy (zde transformační matice), by se měly provádět vně vertex shaderu, tím omezíme výpočetní složitost programu a nepočítáme stále dokola totéž. Je vhodné si pořádně rozmyslet, jaké výpočty provádět vně a jaké uvnitř vertex shaderu. Rovněž nezapomeňte, že délka programu vertex shaderu je značně omezená.

Na závěr dnešního článku ještě upravíme třídu XTerrain tak, aby se vertexy terénu transformovaly přes náš vertex shader. Vytvoříme tedy proměnnou typu IVertexShader. Inicializace bude vypadat následovně:

// VERTEX SHADER
if(pDis->CheckVertexShader(VS_11))
{
    CreateDisplayObject(DISIID_IVertexShader,
                        (void**) &m_pvsTerrain);
    if(m_pvsTerrain)
        m_pvsTerrain->LoadVertexShader("terrain.vs", 0);
}
else
{
    TRACE("Vertex shader 1.1 is not supported by your system.
           Vertex shader for terrain will be disabled.");
}

Nejprve otestujeme dostupnou verzi. Dnes nám bude postačovat verze 1.1. Dále načteme vertex shader ze souboru "terrain.vs", kde máme uložený výše popsaný kód.

Nyní musíme vertex shader nastavit těsně před kreslením terénu v metodě Render():

if(m_pvsTerrain && m_pvsTerrain->Loaded())
{
    pDis->GetCamera()->GetWorldViewProjMatrix(&mat);
    D3DXMatrixTranspose(&mat2, &mat);
    m_pvsTerrain->SetVSConstant(0, (float*)&mat2, 4);

    float cons[4];
    cons[0] = WATER_SURFACE-1;
    cons[1] = 0;
    cons[2] = 4;
    cons[3] = 5;
    m_pvsTerrain->SetVSConstant(4, (float*)&cons, 1);

    float randn[16];
    randn[0] = m_zmod0;
    randn[4] = -m_zmod1;
    randn[8] = m_zmod2;
    randn[12] = -m_zmod3;
    m_pvsTerrain->SetVSConstant(5, (float*)&randn, 4);

    D3DXVECTOR4 lit[3];
    D3DXVec4Normalize(&lit[0], &D3DXVECTOR4(-2,-1,-5, 0.0));
    // light direction
    lit[1].x = 1.0f;lit[1].y = 1.0f;lit[1].z = 1.0f;
    // diffuse light
    lit[2].x = 0.3f;lit[2].y = 0.4f;lit[2].z = 0.3f;
    // ambient light
    m_pvsTerrain->SetVSConstant(9, (float*)&lit, 3);

    UpdateZMod(m_zmod0, m_zdir0, fFactor, 0.3f, -0.3f);
    UpdateZMod(m_zmod1, m_zdir1, fFactor, 0.6f, -0.2f);
    UpdateZMod(m_zmod2, m_zdir2, fFactor, 0.1f, -0.8f);
    UpdateZMod(m_zmod3, m_zdir3, fFactor, 0.5f, -0.4f);

    m_pvsTerrain->SetVertexShader();
}
else
{
    pDis->GetDevice()->SetFVF(VERTEXFORMAT);
}

///
/// NYNI SE BUDE VOLAT DrawIndexedPrimitive() PRO TEREN
///

if(m_pvsTerrain)
    m_pvsTerrain->UnsetVertexShader();

Pokud máme správně nahraný vertex shader, můžeme ho použít. V opačném případě nastavíme fixed pipeline a více neřešíme. Pro vertex shader musíme nastavit všechny konstanty, které náš program používá.
Registry 0 - 3 obsahují transponovanou transformační matici. Jedná se tedy o matici vzniklou složením světové, pohledové a projekční matice.
Registr 4 bude obsahovat několik potřebných konstant jako například výšku vodní hladiny. Všimněte si, že je snížena o jedničku, aby se nehýbaly vertexy, které jsou těsně pod hladinou.
V registrech 5 - 8 budou uloženy posuny vrcholů. Jak jsem již zmínil, tyto posuny jsou čtyři a až program shaderu vybere, který z těchto posunů se na daný vrchol aplikuje. Každý posun kmitá kolem nuly, ale s jiným rozkmitem a jinou rychlostí.
V poslední trojici konstantních registrů 9 - 11 budou informace o světlu: směrový vektor, difusní a ambientní barva. Na závěr této sekce musíme vertex shader nastavit metodou SetVertexShader(). Nesmíte zapomenou vertex shader po vykreslení vypnout. V našem případě k tomu slouží metoda UnsetVertexShader(). Tato metoda nastaví jako aktuální shader NULL.

Zdrojové kódy projektu D3DEngine2 s dnešními úpravami naleznete zde.

44.6. Závěr

Můžete si blahopřát, právě jste napsali první vertex shader. Doufám, že dnešní lekce byla srozumitelná. V případě nějakých nejasností mi můžete poslat email s konkrétním problémem a můžeme se k tomu vrátit.

V příští lekci si povíme něco o vyšším programovacím jazyku HLSL, který se dnes používá pro psaní vertex a pixel shaderů (2.0 a výš).

Jiří Formánek