V minulé lekci jsem často odkazoval na dnešní díl. Dnes si tedy všechny uvedené body podrobněji probereme. Výsledkem dnešní aplikace bude příklad, který pracuje s tzv. index bufferem. Z toho plyne, že v další části článku si něco povíme o index bufferu.
Minule jsme pomocí této struktury vytvořili objekt zařízení (IDirect3DDevice8). V této struktuře jsou uloženy vlastnosti budoucího zařízení. Je třeba, aby tyto parametry byly zadány správně, jinak dojde při volání metody CreateDevice() k chybě. Dnes si tedy povíme jaké parametry je třeba zadat a jakým způsobem zjistíme, co je správné a co ne. Tato problematika je velice důležitá, pokud má vaše aplikace pracovat na více systémech. Každý se totiž chová trochu jinak a pokud natvrdo nastavíte nějaké hodnoty, nemusí se to všem líbit. Přesně tuto chybu jsme udělali v příkladu z minulé lekce. Například formát zadního bufferu jsme nastavili na nějakou konstantní hodnotu, která se zrovna líbí mému systému, ale je více než pravděpodobné, že jinde aplikace nepůjde spustit, protože cílová grafická karta daný formát vůbec nepodporuje. Proto je nejprve velice důležité zjistit, jaký formát karta podporuje a podle nastavení aplikace nastavit buď formát, který 16-bitový nebo 32-bitový.
V dnešní lekci ještě nebudeme vytvářet konkrétní aplikaci na zjištění a nastavení D3D prostředí, protože musíme probrat ještě další důležité věci než začneme opět větší projekt, který se bude skládat z více knihoven. Jednou z části tohoto projektu bude aplikace Setup, pomocí které zjistíme a nastavíme vlastnosti hlavní aplikace. Povíme si tedy jen o funkcích, které jsou pro zjišťování parametrů hardwaru potřeba, abyste mohli přemýšlet nad vlastním systémem nastavení aplikace.
1. Nejprve je třeba zjistit kolik grafických karet váš systém obsahuje. K tomu slouží metoda rozhraní IDirect3D8 GetAdapterCount(), která přímo vrací počet adaptéru v systému. V nastavení aplikace byste totiž měli uživateli dát na výběr, pokud má v PC více grafických karet.
2. Dále bychom se asi rádi dozvěděli o konkrétním adaptéru nějaké další informace například jméno. K tomu použijte metodu GetAdapterIdentifier(). Zde už jsou parametry trochu složitější. První parametr je tzv. ordinální číslo adaptéru. Je to běžný integer, kterým je jednoznačně určen požadovaný adaptér. Toto číslo je v rozmezí 0 - (GetAdapterCount()-1). Druhý parametr budeme vždy nastavovat na hodnotu 0. A konečně do třetího parametru jsou uloženy informace o požadovaném adaptéru ve struktuře D3DADAPTER_IDENTIFIER8. V této struktuře je několik zajímavých atributů:
Description - i tato informace slouží ke zobrazení a platí o ní stejné pravidlo jako u atributu Driver. Tento řetězec může například být: "RADEON 8500 SERIES".
VendorID, DeviceID, SubSysID, Revision - Tyto hodnoty označují konkrétní chipset grafické karty. Pokud výrobce není známý, obsahují hodnotu 0.
3. V dalším kroku již počítáme s tím, že víme jaký adaptér chce uživatel použít. Takže jakékoliv další informace se vztahují pouze k vybranému adaptéru. Například jistě budete chtít vědět jaké rozlišení daný adaptér může použít. K tomu potřebujete metody GetAdapterModeCount() a EnumAdapterModes(). První metodou zjistíte, kolik zvolený adaptér (je určen ordinálním číslem) je schopen použít rozlišení. Rozlišením rozumíme strukturu D3DDISPLAYMODE, která obsahuje tyto atributy:
Width, Height - počet pixelů v horizontálním a vertikálním směru (640x480....)
RefreshRate - Obnovovací frekvence.
Format - Formát režimu. S tím je to trochu složitější, protože každý systém může používat jiné hodnoty, které ovšem ve výsledku reprezentují buď 16-ti nebo 32-bitový režim.
Z toho ovšem plyne, že za dvě různá rozlišení se považují i položky, které se liší pouze obnovovací frekvencí (na to je potřeba si dát pozor). Podle potřeby vyfiltrujete požadovaná rozlišení, které pak nabídnete uživateli k výběru.
Formáty:
Jméno formátu - konstanta | Bitů na pixel | Komentář |
D3DFMT_A8R8G8B8 | 32 | ARGB formát z alphou (kanál pro průhlednost) |
D3DFMT_X8R8G8B8 | 32 | RGB bez alphou, 8-bitů pro každou barvu RGB |
D3DFMT_R5G6B5 | 16 | RGB formát |
D3DFMT_X1R5G5B5 | 16 | RGB s 5-ti bity na barvu |
D3DFMT_A1R5G5B5 | 16 | RGB formát s 5-ti bity na barvu + 1 bit na alphu |
D3DFMT_A4R4G4B4 | 16 | 4 bity pro každou barvu s 4 bitovou alphou |
D3DFMT_A8R3G3B2 | 16 | RGB formát s 8-mi bitovou alphou |
D3DFMT_X4R4G4B4 | 16 | 4 bity pro každou barvu bez alphy |
D3DFMT_A2B10G10R10 | 32 | 10 bitů pro každou barvu + 2 bity pro alphu |
Toto samozřejmě nejsou všechny formáty, můžete použít i 8-mi bitové, které se ovšem používají spíše pro textury. Formáty, které jsem zde uvedl jsou vhodné pro zadní buffery. Ovšem většinou to bývá tak, že grafická karta podporuje jen jeden 32-ti bitový a jeden 16-ti bitový formát. Formáty s alphou jsou velice důležité u textur, pokud pracujete s průhledností.
Poznámka: V kanálu alpha je uložena informace o průhlednosti pixelu. Později si ukážeme, jak tuto možnost využijeme při texturování složitějších objektů.
Nakonec této části si ještě podrobněji probereme atributy struktury D3DPRESENT_PARAMETERS:
D3DSWAPEFFECT_DISCARD | Tento způsob budeme asi nejčastěji používat. Ovladač vybere nejefektivnější způsob prohazování, navíc můžete použít antialising a více zadních bufferů. |
D3DSWAPEFFECT_FLIP | Tento způsob funguje stejně jako v DirectDraw. Při každém volání metody Present() se zadní buffer zobrazí a bývalý přední se ocitne v pozici zadního. Můžete použít i více zadních bufferů. |
D3DSWAPEFFECT_COPY | Zde se kopíruje obsah zadního bufferu do předního nebo přímo do okna ve window režimu. Zde je důležité, že nemůžete použít více jak jeden zadní buffer a že ke zkopírování dochází okamžitě, tudíž zde nefunguje žádná vertikální synchronizace s obnovovací frekvencí monitoru. |
D3DSWAPEFFECT_COPY_VSYNC | Poslední způsob je stejný jako D3DSWAPEFFECT_COPY, ale je vhodné ho použít pro window aplikace, protože kopírování zadního bufferu je synchronizováno s obnovovací frekvencí monitoru. |
D3DPRESENT_INTERVAL_DEFAULT | Pro oknové aplikace musíte použít tuto hodnotu. Ve fullscreen je to stejné jako kdybyste nastavili D3DPRESENT_INTERVAL_ONE. |
D3DPRESENT_INTERVAL_IMMEDIATE | K prohazování dochází jak nejrychleji to jde tj. nečeká se na návrat paprsku monitoru. Pokud například chcete testovat cílový harware, je vhodné použít tuto hodnotu a nechat grafickou kartu "trhat" monitor:) |
D3DPRESENT_INTERVAL_ONE | Prohazování je synchronizováno s obnovovací frekvencí monitoru. |
D3DPRESENT_INTERVAL_TWO | Prohazování se provádí každý druhý cyklus. |
D3DPRESENT_INTERVAL_THREE | Prohazování se provádí každý třetí cyklus. |
D3DPRESENT_INTERVAL_FOUR | Prohazování se provádí každý čtvrtý cyklus. |
Minule jsme si v rychlosti uvedli, jakým způsobem mohou být reprezentována data ve vertex bufferu. Dnes se k tomu ještě vrátíme a ukážeme si to trochu podrobněji na obrázcích. Jedná se o první parametr všech funkcí typu DrawPrimitives(). Samozřejmě abyste mohli použit daný typ, musíte s tím počítat už když vytváříte a plníte zdrojový vertex buffer. Například těžko můžete vykreslovat trojúhelníky z bufferu, který neobsahuje ani tři vrcholy.
1. Point List - seznam bodů - D3DPT_POINTLIST
V tomto nejjednodušším případě je ve vertex bufferu uložen seznam bodů. Představte si, že máte ve vertex bufferu uloženy tyto body:
CUSTOMVERTEX Vertices[] =
{
{-5.0, -5.0, 0.0},
{ 0.0, 5.0, 0.0},
{ 5.0, -5.0, 0.0},
{10.0, 5.0, 0.0},
{15.0, -5.0, 0.0},
{20.0, 5.0, 0.0}
};
Pak je mohu vykreslit pomocí příkazu:
d3dDevice->DrawPrimitive(
D3DPT_POINTLIST, 0, 6 );
// d3dDevice je platný ukazatel na objekt
zařízení
Výsledkem bude toto:
2. Line list - seznam čar - D3DPT_LINELIST
V druhém případě jsou ve vertex bufferu uloženy dvojice bodů, které se pak při renderování spojují čarami. Opět si představte stejný seznam bodů jako v předchozím případě. Pokud je tedy budete vykreslovat pomocí příkazu:
d3dDevice->DrawPrimitive( D3DPT_LINELIST, 0, 3 );
Vždy se spojí dva sousední body.
3. Line strip - pás z čar - D3DPT_LINESTRIP
Dále budeme vykreslovat opět čáry, nyní ovšem spojíme každé dva sousední body. Mějme opět stejné pole, pak pomocí příkazu:
d3dDevice->DrawPrimitive( D3DPT_LINESTRIP, 0, 5 );
vykreslíme toto:
Jako primitivum se zde počítá spojnice dvou bodů a těch je dohromady 5.
4. Triangle list - seznam trojúhelníků - D3DPT_TRIANGLELIST
Nyní se konečně přesuneme k opravdovým (uzavřeným) polygonům. Jako první je zde seznam trojúhelníků. Budeme pracovat se stejným vertex bufferem, takže pomocí příkazu:
d3dDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 2 );
Vykreslíme dva oddělené trojúhelníky:
Pracuje to tak, že se vezmou tři vrcholy a vytvoří se z nich trojúhelník. Poté se vezmou další tři vrcholy a operace se opakuje.
5. Triangle strip - pás trojúhelníků - D3DPT_TRIANGLESTRIP
Tento pracuje podobně jako předchozí, jen se při dalším vykreslování nepřesouvá na tři nové vrcholy, ale přesune se jen o jeden vrchol a vytvoří trojúhelník z předchozích dvou:
d3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 4);
Vytvoříme toto:
Jsou to 4 trojúhelníky, takže počet primitiv je 4.
6. Triangle fan - vějíř - D3DPT_TRIANGLEFAN
Nakonec tu máme poslední způsob, kde se trojúhelníky vytvářejí vůči prvnímu bodu ve vertex bufferu. Zde nadefinujeme jinou posloupnost bodů:
CUSTOMVERTEX Vertices[] =
{
{ 0.0, 0.0, 0.0},
{-5.0, 5.0, 0.0},
{-3.0, 7.0, 0.0},
{ 0.0, 10.0, 0.0},
{ 3.0, 7.0, 0.0},
{ 5.0, 5.0, 0.0},
};
Pomocí tohoto příkazu:
d3dDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 4);
Vykreslíme toto:
Trojúhelník se vždy vytváří z prvního bodu posloupnosti a dvou následujících bodů. Další trojúhelník se vytvoří z prvního, třetího a čtvrtého vrcholu atd.
20.3. Index buffer
Princip vytvoření a naplnění index bufferu je stejný jako v případě vertex bufferu, jen neinicializujeme vertexy, ale indexy, které jsou typu WORD.
Vytvořme proměnnou typu:
IDirect3DIndexBuffer8* g_lpD3DIB = NULL;
Dále trochu upravíme pole vertexů, aby z něj šel nakreslit čtverec, který má 4 vrcholy:
TLVERTEX arVertices[] = {
{200.0f, 200.0f, 0.5f, 1.0f, 0xFFFF0000},
{600.0f, 200.0f, 0.5f, 1.0f, 0xFF0000FF},
{200.0f, 600.0f, 0.5f, 1.0f, 0xFFFFFF00}
{600.0f, 600.0f, 0.5f, 1.0f, 0xFF00FF00}};
A teď stejným způsobem vytvoříme pole indexů:
WORD arIndices[] = { 0, 1, 2, 1, 3, 2};
Nyní vytvoříme samotný index buffer pro 6 indexů a naplníme ho daty z výše uvedeného pole:
g_lpD3DDevice->CreateIndexBuffer(6 * sizeof(WORD), D3DUSAGE_WRITEONLY , D3DFMT_INDEX16 , D3DPOOL_DEFAULT, &g_lpD3DIB);
WORD *pdwIndex;
if(SUCCEEDED(g_lpD3DIB->Lock(0, 0, (BYTE**) &pdwIndex, 0))) {
for(int i = 0; i < 6; i++) { // zde uz pouzijeme cyklus
pdwIndex[i] = arIndices[i];
}
g_lpD3DVB->Unlock();
}
Proč je indexů právě 6? Máme dva trojúhelníky a každý má 3 vrcholy. Program vezme první tři indexy, podívá se do vertex bufferu a udělá trojúhelník z vrcholu 0, 1 a 2. Poté vezme další 3 indexy a udělá totéž z vrcholů 1, 3 a 2. Pořadí indexů musí být po směru hodinových ručiček, jinak by byl trojúhelník obrácený a nebyl by vidět. Dále musíme nastavit vstupní index buffer pomocí metody SetIndices():
g_lpD3DDevice->SetIndices(g_lpD3DIB, 0);
První parametr je ukazatel na index buffer a druhý je tzv. bázový index. Toto číslo je automaticky přičteno ke každému indexu.
Nakonec tedy vykreslíme čtverec pomocí metody DrawIndexedPrimitive():
g_lpD3DDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 4, 0, 2);
Tato metoda již má více parametrů:
Zbytek programu zůstává stejný.
Po spuštění se vykreslí barevný čtverec.
V dnešní lekci jsme tedy probrali vše, co jsem minule slíbil. Na příště si necháme první větší projekt, kde postupně začneme slepovat více knihoven dohromady.
Těším se příště nashledanou.