Programátorské hledisko
Na rozdíl od strukturovaného OpenGL je Direct3D objektově orientované. Jaký je mezi tím rozdíl? V případě OpenGL se na začátku programu vytvoří okno s jeho podporou a pak je možné odkudkoli volat jakékoli OpenGL specifické funkce. V případě Direct3D se musí vytvořit objekt Direct3D (IDirect3D9), s jeho pomocí zařízení Direct3D (IDirect3DDevice9) a přes volání jejich metod programátor pracuje. Objektově orientované programování sice preferuji také, ale v případě DirectX mi připadá hodně těžkopádné – alespoň v porovnání s OpenGL.
Jedním z nejdůležitějších charakteristik jakéhokoli API je pro programátora délka zdrojového kódu, který musí napsat, aby program něco udělal. John Carmack, autor her Quake, uvádí [3], že kód programu plnící stejnou funkci je v Direct3D i čtyřikrát delší než v OpenGL. Můžeme si to bez problémů ověřit. Z učebnice Direct3D [1], str. 180, 181 uvedu jednoduchou renderovací funkci, která na černém pozadí vykresluje jeden obarvený trojúhelník – bez textur, světel či jakýchkoli maticových operací (translace, rotace). Následně vše přepíši do OpenGL.
///////////////////////////////////////////
// Direct3D 9.0
///////////////////////////////////////////
struct VLASTNIVERTEX
{
FLOAT x, y, z, rhw;
DWORD barva;
}
void Vykresleni()
{
VLASTNIVERTEX vrchTrojuhl[] =
{
{400.0, 180.0, 0.0, 1.0, D3DCOLOR_XRGB(255,0,0)},
{500.0, 380.0, 0.0, 1.0, D3DCOLOR_XRGB(0,255,0)},
{300.0, 380.0, 0.0, 1.0, D3DCOLOR_XRGB(0,0,255)},
};
IDirect3DVertexBuffer9* pVertexBuffer = NULL;
HRESULT hVysledek = g_pZarizeniDirect3D->CreateVertexBuffer(
3*sizeof(VLASTNIVERTEX), 0, D3DFVF_XYZRHW | D3DFVF_DIFFUSE,
D3DPOOL_DEFAULT, &pVertexBuffer, NULL);
if (FAILED(hVysledek))
{
DXTRACE_ERR("Chyba při vytv. vertex bufferu.", hVysledek);
}
VOID* pVrcholy;
hVysledek = pVertexBuffer->Lock(0, 0, (viod**)&pVrcholy, 0);
if (FAILED(hVysledek))
{
DXTRACE_ERR("Chyba při zamyk. vertex bufferu.", hVysledek);
}
memcpy(pVrcholy, vrchTrojuhl, sizeof(vrchTrojuhl));
pVertexBuffer->Unlock();
g_pZarizeniDirect3D->Clear(0, NULL, D3DCLEAR_TARGET,
D3DCOLOR_XRGB(0,0,0), 1.0, 0);
g_pZarizeniDirect3D->SetStreamSource(0, pVertexBuffer, 0,
sizeof(VLASTNI_VERTEX));
g_pZarizeniDirect3D->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE);
g_pZarizeniDirect3D->BeginScene();
g_pZarizeniDirect3D->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
g_pZarizeniDirect3D->EndScene();
g_pZarizeniDirect3D->Present(NULL, NULL, NULL, NULL);
if (pVertexbuffer)
pVertexBuffer->Release();
}
To samé v OpenGL…
///////////////////////////////////////////
// OpenGL
///////////////////////////////////////////
void Vykresleni()
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glBegin(GL_TRIANGLES);
glColor3ub(255,0,0); glVertex2i(400,480);
glColor3ub(0,255,0); glVertex2i(500,380);
glColor3ub(0,0,255); glVertex2i(300,380);
glEnd();
glFlush();
SDL_GL_SwapBuffers();
}
Pozn.: Při inicializaci se nesmí definovat perspektiva, ale pravoúhlá projekce (glOrtho()) o rozměrech 800×600. Funkce glClearColor() se obvykle dává také do inicializace, ve vykreslení je jen kvůli tomu, aby kód plně odpovídal Direct3D. Pokud provozujete OpenGL ve Win32 API, určitě používáte místo SDL_GL_SwapBuffers() funkci SwapBuffers(), SDL má tu výhodu, že lze kód bez větších problémů přenášet mezi Linuxem, Windows a dalšími operačními systémy.
Screenshot OpenGL verze programu
Vrátím se ještě k té délce kódu – v případě, že byste je chtěli porovnat číselně, pak D3D verze měla celkem 47 řádků, 108 slov a 1367 písmen, OpenGL pouze 14 řádků, 22 slov a 335 písmen. V poměru to činí u řádků 47 / 14 = 3.35, slov 108 / 22 = 4.9 a znaků 1367 / 335 = 4.08 …čtyřnásobná délka kódu tedy plus mínus platí. Zčásti je to i tím, že v D3D sekci byly použity dlouhé identifikátory, ale za to já nemůžu, jak jsem napsal výše, jedná se o doslovný přepis z učebnice D3D. Dokonce jsem identifikátor vrcholyTrojuhelniku musel zkrátit pouze na vrchTrojuhl, aby se kód vešel na řádek.
[woq@localhost tmp]$ wc *txt 47 108 1367 dx.txt 14 22 335 gl.txt
Uživatele programu zdrojové kódy absolutně nezajímají a hlavně jim nerozumí, takže nepředpokládám, že jste předchozí výpisy zkoumali podrobně. Jenom tak jimi v rychlosti prolétněte a zkuste říci, který z nich je přehlednější. Dále si všimněte v D3D sekci kódu řádku if(FAILED(hVysledek)), který se provede při chybě ve vytváření/zamykání vertex bufferu. V OpenGL se toto dělat nemusí/nelze, a to ani při použití vertex arrays, které více odpovídají D3D stylu programování.
Nic podobného, jako je samostatné volání funkcí glVertex*(), Direct3D neumí. Vertex arrays jsou sice výhodnější při renderování velkých množství souvisejících vertexů, které jsou vždy na konstantní pozici (3D světy, výškové mapy, 3D modely ap.), protože odpadají ztráty na výkonu při mnoha volání funkcí, nicméně u pohyblivých nesouvisejících trojúhelníků, kde často ani nebývá dopředu znám jejich počet (typicky částicové systémy), bývá použití vertex arrays těžkopádné a spíše méně vhodné, navíc se zbytečně alokuje systémová paměť. V OpenGL si může programátor zvolit, co považuje za výhodnější. Pokud je chytrý a používá extensiony, může všechna data dokonce uložit v paměti grafické karty jako tzv. VBO, tím se eliminuje vliv „délky drátů“ při posílání dat na grafickou kartu. To ale pravděpodobně jde i v D3D.
Literatura
- [0] Vlastní zkušenosti s 3D grafikou
- [1] Clayton Walnum: Programujeme grafiku v Microsoft Direct3D (9.0), Computer Press 2004, první vydání, 358 stran
- [2] Chip 01/2004, Hardwarový fotorealismus: Možnosti moderních 3D grafických akcelerátorů, str. 96–100)
- [3] Daniel Čech: OpenGL – Referát na praktikum z informatiky (PDF) – Opravdu kvalitní text
- [4] Bernard Lidický: Několik poznámek k tvorbě počítačových her (PDF) – Věnuje se především tématům, jaké hry programovat a v čem (přenositelné knihovny Allegro a SDL)
- [5] Linuxové hry (47): Unreal Tournament 2004 – Hlavní impuls pro vznik tohoto článku