Kurz DirectX (42.)

Dříve než se vrhneme na slíbený vertex shader, přejdeme s naším projektem na DirectX 9.0(c). V druhé části si povíme něco o programovatelné pipeline, zatím budeme řešit pouze cestu vertexu čili pixel pipeline si necháme na některou budoucí lekci.

42.1. DirectX 9.0

Protože se budeme v příštích lekcích zabývat pokročilejšími technikami jako je například vertex shader, je správný čas na přechod na současnou verzi DirectX. Přechod není nikterak složitý, vesměs stačí přepsat osmičku na devítku. Jako příklad uvedu rozhraní IDirect3D8, zkrátka jej přejmenujeme na IDirect3D9. Přesto je v nové verzi několik odlišností a o těch si povíme v následující části. Zvláštností je, že systém DirectInput, zůstal ve verzi 8.0, čili do knihovny Input, budeme sahat minimálně, hlavně kvůli tomu, že Input používá také Direct3D, které prodělalo největší změny.

K tomu, abyste mohli učinit přechod na verzi 9.0, potřebujete pochopitelně nejnovější DirectX SDK, které lze stáhnout ze stránek Microsoftu a balíček má něco přes 200MB.

1) Hlavičkové soubory a knihovny

Do teď jsme používali hlavičkové soubory d3d8.h a d3dx8.h pro verzi 8. Verze 9 bude mít nečekaně dva hlavičkové soubory d3d9.h a d3dx9.h. Dále je třeba změnit konstantu DIRECT3D_VERSION na 0x0900. Totéž co jsme provedli s hlavičkovým soubory, provedeme i s knihovnami. Zde se jedná o knihovnu d3d8.lib a d3dx8.lib. Obě přejmenujeme na d3d9.lib a d3dx9.lib.

2) Přejmenování rozhraní a dalších typů

V dalším kroku postupně projdeme celý zdrojový kód a přejmenujeme většinu rozhraní a jiných typů na verzi 9. Uvedu jen některé příklady:

IDirect3D8 -> IDirect3D9
IDirect3DDevice8 -> IDirect3DDevice9
D3DMATERIAL8 -> D3DMATERIAL9
IDirect3DTexture8 -> IDirect3DTexture8
IDirect3DVertexBuffer8 -> IDirect3DVertexBuffer9
IDirect3DIndexBuffer8 -> IDirect3DIndexBuffer9
D3DLIGHT8 -> D3DLIGHT9

3) Další změny

Na závěr ještě provedeme pár úprav, protože některé metody se od minulé verze lehce změnily. Jak uvidíte v následující tabulce, většině metodám jeden parametr přibyl.

Metoda
Změna
SetStreamSource()
Tato metoda má navíc jeden parametr a tím je posun v bytech ve vstupním proudu vertexů. Abyste mohli tuto novou funkčnost využít, je třeba zkontrolovat příznak D3DDEVCAPS2_STREAMOFFSET.
Lock()
Tato metoda prodělala změny jak v rámci rozhraní IDirect3DVertexBuffer8 tak IDirect3DIndexBuffer8. Dříve byl pevně daný typ třetího parametru na BYTE**, nyní je použit typ void**.
SetIndices()
Druhý parametr, bázový index, byl přesunut přímo do metody DrawIndexedPrimitive().
DrawIndexedPrimitive()
Jak bylo zmíněno v předchozím řádku, bázový index je nyní jako druhý parametr v této metodě.
PresentationInterval
Atribut struktury D3DPRESENT_PARAMETERS byl přejmenován z Fullscreen_ PresentationInterval na PresentationInterval.
D3DXLoadMeshFromX()
Přibyl jeden parametr LPD3DXBUFFER, do kterého se uloží seznam efektů.
SetSamplerState()
Pro některé nastavení, které se nastavovaly metodou SetRenderState(), byla vytvořena nová metoda SetSamplerState(). My ji používáme pro nastavení filtrování textur.
SetFVF()
Formát vertexů pro fixní pipeline se nyní nastavují pomocí metody SetFVF(). Dříve to byla metoda SetVertexShader(), ale ta nyní slouží opravdu jen na vertex shader.
CheckDeviceMultiSampleType()
Této metodě přibyl jeden parametr, který je ale rezervovaný zřejmě pro budoucí použití a musí být NULL.
EnumAdapterModes() GetAdapterModeCount()
Těmto dvěma metodám přibyl parametr typu D3DFORMAT, který funguje jako filtr.

Změn je v nové verzi samozřejmě mnohem více, ale protože náš projekt je zatím velmi jednoduchý, týkají se změny jen uvedeného výčtu v tabulce. V dalších lekcích se budeme zabývat vertex shaderem a zde již budeme stavět na současné verzi DirectX.

Zde najdete upravenou verzi projektu D3DEngine2, od této lekce budeme výhradně používat DirectX 9.0.

42.2. Programovatelná pipeline

Dosud jsme se zabývali tzv. fixní vertexovou pipeline, která se vyznačuje tím, že pomocí metody SetTransform() jsme nastavili světovou, pohledovou a projekční matici. Transformace složená z těchto matic se pak aplikovala na všechny vertexy poslané na grafickou kartu metodou DrawPrimitive() či DrawIndexedPrimitive(). Kromě transformací, se v této části také provádí tzv. per-vertex osvětlení čili mění se barva vertexu podle okolního osvětlení. To se samozřejmě projeví i na barvě výsledného trojúhelníku. Proto se tato část zpracování někdy také nazývá T&L jednotka, čili Transformation&Lighting jednotka (hardwarové T&L je pak vertex shader).

Nejprve si připomeneme, jak vypadá pipeline Direct3D:

D3D pipeline
Obrázek sejmuty z MSDN dokumentace, samozřejmě trochu upravený

Ve schématu jsou názorně vyznačeny rozhraní mezi jednotlivými bloky a u každého je uvedeno, co se předává k dalšímu zpracování. Nyní se podíváme podrobněji na jednotlivé bloky. V dnešní lekci nás ovšem bude zajímat především blok Programmable pipeline (ve skutečnosti je to vertex pipeline, čili místo, kde se pracuje pouze s vertexy).

Blok
Význam a funkce
Vertex data Modelová data ve formě seznamu vertexů ve vertex bufferu (IDirect3DVertexBuffer9).
Primitive data
Tento blok doplňuje blok předchozí o indexy, čili ukazatele do vertex bufferu pomocí nichž jsou složeny objekty (body, čáry, trojúhelníky a obecně polygony). Ty se sestavují v další jednotce.
Tesselation
V této jednotce se převádí tzv. higher-order primitiva (například beziérovy křivky či B-Splines) na trojúhelníky nebo čáry. Výsledkem už jsou jen vertex buffery.
Fixed function pipeline
Tento blok představuje to, co jsem popsal v úvodu druhé části. Pomocí metody SetTransform() se nastaví příslušné transformační matice a poté se tyto transformace aplikují na všechny vertexy. To jsme využívali až do této lekce. Dnes si povíme o alternativě v podobě vertex shaderu.
Programmable pipeline
Tento blok obsahuje právě zmíněný Vertex shader, což není nic jiného než malá výpočetní jednotka (ALU), která vykonává krátký program, psaný ve speciálním assembleru, na každém vertexu zvlášť (ve skutečnosti je na grafickém procesoru těchto jednotek několik a pracují paralelně). Ve vertex shaderu můžeme měnit všechny atributy daného vertexu (pokud tyto atributy pochopitelně má). Například normálu vertexu použijeme pro výpočet barvy vertexu (vektor světla je předán přes tzv. konstantní registry). Zásadní je, že z vertex shaderu vystupuje tolik vertexů jako do něj na začátku vstoupilo, tudíž trojúhelníky nemůžeme mazat ani přidávat. Tento blok budeme probírat v příštích lekcích opravdu podrobně, takže se nyní nebojte, že o něco přicházíte.
Ještě zajímavost nakonec. Vertex shader (zřejmě díky tomu, že je na začátku zpracování) lze emulovat softwarově, i když Direct3D zařízení jede přes HAL, čili s vertex shaderem můžeme pracovat i na kartě, která si o hw vertex shaderu může jen zdát. To je rozdíl oproti pixel shaderu, ten emulovat nelze.

Rasterizace
V dalším kroku podrobíme transformované vertexy ořezání neboli clippingu (odstranění hran, které jsou mimo viewport). Back face culling je proces, kdy se odstraňují trojúhelníky, které mají odvrácené normály od pozorovatele. Tyto trojúhelníky se totiž považují za neviditelné. Nastavením SetRenderState() s parametrem D3DRS_CULL můžeme ovlivnit, které trojúhelníky se odstraňují případně lze funkci zcela vypnout. Při rasterizaci se konečně vytváří objekt na pixelové úrovni, zde končí život vertexů - dále se pracuje pouze s pixely. Trojúhelník se vyplní barvou, která vyplyne z interpolované barvy vertexů.
Pixel shader
Pixel shader je podobná jednotka, jako vertex shader, tedy opět procesor, který má ovšem jiné instrukce a hlavně program "je spuštěn" na jednotlivé pixely dříve rasterizovaného objektu. Na této úrovni se také texturuje. V předchozích verzích Direct3D byla tato jednotka paralelně s tzv. multitexturing unit, která prováděla vícenásobné nanášení textur (a samozřejmě nanášení textur vůbec). To dnes již hravě zvládne pixel shader, my jsme ale doteď používali zmíněnou multitexturovací jednotku (a také ještě nějakou dobu budeme).
Uložení pixelů do frame bufferu
Na závěr se provede několik testů, které zde nebudeme podrobně rozebírat. Důležitý je test v z-bufferu (hloubkový test), kde se testuje, zda-li je právě kreslený pixel za nebo před pixelem na stejném místě ve frame bufferu a pokud se pixel kreslí, je třeba upravit i z-buffer. Dále se aplikuje mlha a také míchání barev, čili spojení cílové a zdrojové barvy pixelu podle zadaného faktoru (alpha, barva a další).

Je důležité si uvědomit, že výše uvedený popis je velice stručný. Pro úplné pochopení, jak pracuje celá pipeline by bylo třeba napsat mnoho dalších řádků. Pro programátora, který ví, co potřebuje a zhruba chápe, co která jednotka dělá není potřeba znát detailně principy (třeba jak se rasterizuje úsečka), ale je dobré vědět, co se děje s našimi vertexy.

V dnešní době je celý řetězec implementovaný na grafické kartě. Čili programátor pouze "naláduje" vertexy a indexy a z výstupu mu padají otexturované trojúhelníky. Pomocí ovladače grafické karty se pouze určují parametry jednotlivých bloků (matice, osvětlení, textury, program ve vertex shaderu, blending a desítky dalších nastavení).

42.3. Závěr

A je tu opět konec. Doufám, že po dnešní lekci jste si ujasnili, jakým způsobem se zpracovávají trojúhelníky než se dostanou na monitor. Dnešní lekci jsem se snažil vést poněkud z nadhledu, snad se mi to alespoň trochu povedlo a většina z Vás pochopila princip D3D pipeline. Nutno říci, že například OpenGL nebo jakákoliv (třeba i 2D) pipeline, je velice podobná (třeba vyvedena pouze v softwaru).

A co bude v příští lekci? Především začnu popisovat vertex shader a podíváme se, jaké efekty s ním lze udělat (samozřejmě už teď se můžete podívat na příklady z DirectX SDK, kde jsou mnohé ukázky).

Jiří Formánek