V dnešní lekci již vytvoříme 3d prostor, do kterého vykreslíme rotující kostku. Povíme si něco o transformacích a o praktickém využití matic. V další části lekce si povíme něco málo o texturování a v příkladu na naší kostku naneseme texturu.
Doteď jsme používali tzv. transformované vrcholy. Tyto vrcholy vůbec neprochází transformační pipeline Direct3D. Například jsme měli vertex o souřadnicích (200.0f, 200.0f, 0.5f). Tyto hodnoty jsou přímo pozice na monitoru! Z-tová souřadnice je zde použita v rozmezí od 0.0 - 1.0, kde 1.0 je bod nejvzdálenější od pozorovatele (například si představte okna, které se částečně překrývají, vrchní okno má z = 0.0, zatímco spodní 1.0).
V dnešní lekci ovšem budeme pracovat výhradně s netransformovanými vrcholy! Tyto vrcholy mají souřadnice v tzv. modelovém prostoru, kde jednotlivé body jsou vztaženy k pomyslnému středu objektu (například krychle).
Mějme například kostku, která má 8 vrcholů:
VERTEX arCube[] = {
{ 2.0f, 2.0f, 2.0f, 0xFF00FF00},
{ 2.0f, -2.0f, 2.0f, 0xFFFF00FF},
{-2.0f, -2.0f, 2.0f, 0xFFFF0000},
{-2.0f, 2.0f, 2.0f, 0xFFFFFF00},
{ 2.0f, 2.0f, -2.0f, 0xFF00FFFF},
{ 2.0f, -2.0f, -2.0f, 0xFF0000FF},
{-2.0f, -2.0f, -2.0f, 0xFFFF0F0F},
{-2.0f, 2.0f, -2.0f, 0xFFFF0FFF}
};
Tato kostka je umístěna ve středu souřadného systému. Nyní je potřeba tyto body transformovat tak, aby šla kostka zobrazit na monitor.
Máme tři základní transformační matice, kterými je
transformován každý vrchol objektu:
1) Světová matice - tato matice transformuje vrcholy z modelového
prostoru do světového! V modelovém prostoru má každý objekt svůj střed
souřadného systému. Transformací do světových souřadnic získají všechny objekty
společný počátek.
2) Matice pohledu - pomocí této matice nastavíme pohled kamery v prostoru
(nyní už se pohybujeme ve světovém prostoru). Tento prostor se vytváří pomocí
dvou bodů: tzv. eye point (bod odkud pozorujeme, oko) a look at point
(bod, který pozorujeme, objekt).
3) Projekční matice - pomocí této matice vlastně nastavíme čočku kamery,
kterou jsme definovali pomocí matice pohledu. Pomocí této matice nastavíme
perspektivu (vzdálenější objekty jsou menší než objekty bližší), zorný úhel a
poměr stran pozorovaného prostoru (to je obecně nějaký čtyřstěn).
V našem příkladu budeme pomocí této matice otáčet krychlí kolem všech os současně. Ukážeme si, jak složíme více operací pomocí násobení matic. Pomocí této matice můžete aplikovat základní transformace na objekty. Můžete tuto matici nastavit pro každý objekt zvlášť.
Například pokud nastavíte tuto matici jednotkovou, nedojde k žádné změně vrcholů objektu, takže pokud máme naši kostku umístěnou ve středu modelového prostoru, bude i ve středu světového prostoru.
Nyní si ukážeme jak nastavit světovou matici:
D3DXMATRIX matWorldOld,
matWorldNewX, matWorldNewY, matWorldNewZ;
g_lpD3DDevice->GetTransform(D3DTS_WORLD, &matWorldOld);
D3DXMatrixRotationX(&matWorldNewX, -D3DX_PI/1000);
D3DXMatrixRotationY(&matWorldNewY, -D3DX_PI/2000);
D3DXMatrixRotationZ(&matWorldNewZ, D3DX_PI/500);
D3DXMatrixMultiply(&matWorldNewX, &matWorldNewX, &matWorldNewY);
D3DXMatrixMultiply(&matWorldNewX, &matWorldNewX, &matWorldNewZ);
D3DXMatrixMultiply(&matWorldNewX, &matWorldNewX, &matWorldOld);
g_lpD3DDevice->SetTransform(D3DTS_WORLD, &matWorldNewX);
Toto je kousek kódu našeho příkladu. Jednotlivé kroky nyní podrobněji popíšu. Nejprve získáme předchozí světovou matici. Tento krok bychom mohli vynechat, ale museli bychom naší matici definovat jako globální objekt. Poté vytvoříme tři matice, které provádějí rotaci kolem všech tří os pomocí funkce D3DXMatrixRotationX/Y/Z(). Tato funkce má dva parametry. První je ukazatel na výslednou matici a druhý je úhel otočení v radiánech! Zde používám definovanou konstantu D3DX_PI (3,14). Dále musíme všechny matice vynásobit (a tím spojit efekty všech matic do jedné) pomocí funkce D3DXMatrixMultiply(). Tato funkce má tři parametry. První je ukazatel na výslednou matici, druhý je ukazatel na první zdrojovou matici a třetí ukazuje na druhou zdrojovou matici. Mezivýsledek vždy uložíme do matice matWorldNewX. Nakonec zpětně nastavíme transformaci pomocí metody SetTransform(), kde pomocí prvního parametru nastavíme světovou matici a ve druhém je ukazatel na naší matici.
Tyto kroky budeme provádět v každém cyklu aplikace!
Tuto matici nastavíme pouze po inicializaci Direct3D, protože při rotaci kostky se budeme dívat na scénu stále ze stejného místa. Jak bylo uvedeno výše, tuto matici definujeme pomocí dvou bodů:
// Create view matrix
D3DXVECTOR3 vUpDir, vEyePt,
vLookAtPoint;
//
vUpDir = D3DXVECTOR3(0.0f, 0.0f,
1.0f);
vLookAtPoint = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
vEyePt = D3DXVECTOR3(10.0f, 10.0f, 0.0f);
//
D3DXMatrixLookAtLH(&matView, &vEyePt,
&vLookAtPoint, &vUpDir);
g_lpD3DDevice->SetTransform(D3DTS_VIEW, &matView);
LookAtPoint máme ve středu souřadnicového systému, takže se
díváme přímo do středu vesmíru. Díváme se v rovině xy pod úhlem 45 stupňů:
Dále je třeba definovat směr os pomocí vektoru vUpDir. I když se často definuje jako svislá osa osa Y, přijde mi logičtější takto definovat osu Z. Nakonec pomocí funkce D3DXMatrixLookAtLH() vytvoříme matici pohledu. Funkce má 4 parametry:
ukazatel na výslednou matici
ukazatel na EyePoint
ukazatel na LookAtPoint
ukazatel na vektor určující směr os
Nakonec opět nastavíme transformaci pomocí metody SetTransform(), nyní s parametrem D3DTS_VIEW.
Tato poslední matice definována čtyřmi parametry:
zorný úhel
poměr stran pozorovaného prostoru ve tvaru kvádru
vzdálenost bližší stěny (vše co je blíže se nevykresluje)
vzdálenost vzdálené stěny (všechno co je ve větší vzdálenosti se nevykresluje)
V našem příkladu vytvoříme projekční matici takto:
D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI/4,
4.0f/3.0f, 1.0f, 100.0f);
g_lpD3DDevice->SetTransform(D3DTS_PROJECTION, &matProj);
Pomocí D3DXMatrixPerspectiveFovLH() vytvoříme projekční matici. Tato funkce má 5 parametrů:
ukazatel na výslednou matici
zorný úhel v radiánech
poměr stran
vzdálenost bližší stěny (vše co je blíže se nevykresluje)
vzdálenost vzdálené stěny (všechno co je ve větší vzdálenosti se nevykresluje)
Nakonec opět nastavíme transformační matici pomocí metody SetTransform(), nyní s parametrem D3DTS_PROJECTION.
Zde vám doporučuji, abyste si vyzkoušeli jaký vliv mají jednotlivé parametry na "vzhled" scény. Například zjistíte, že pokud zadáte jiný poměr stran, stane se z kostky kvádr, protože rozlišení je v poměru 4/3.
Nejprve je důležité si uvědomit, že již pracujeme s transformovanými vrcholy (tyto vrcholy již nemají složku rhw):
struct VERTEX {
float x, y, z;
DWORD dwColor;
};
A dále je třeba nastavit typ shaderu:
#define VERTEXFORMAT D3DFVF_XYZ|D3DFVF_DIFFUSE
Tak jako každá krychle i naše bude mít 8 vrcholů:
VERTEX arCube[] = {
{ 2.0f, 2.0f, 2.0f, 0xFF00FF00},
{ 2.0f, -2.0f, 2.0f, 0xFFFF00FF},
{-2.0f, -2.0f, 2.0f, 0xFFFF0000},
{-2.0f, 2.0f, 2.0f, 0xFFFFFF00},
{ 2.0f, 2.0f, -2.0f, 0xFF00FFFF},
{ 2.0f, -2.0f, -2.0f, 0xFF0000FF},
{-2.0f, -2.0f, -2.0f, 0xFFFF0F0F},
{-2.0f, 2.0f, -2.0f, 0xFFFF0FFF}
};
Krychle je umístěna uprostřed a délka hrany je rovna 4. Takto vypadají indexy naší krychle
WORD
arCubeIndices[] = {
// top
0,3,1, 1,3,2,
// bottom
4,5,7, 5,6,7,
// right
0,4,7, 0,7,3,
// left
1,6,5, 1,2,6,
// back
0,1,5, 0,5,4,
// front
3,6,2, 3,7,6
};
Při definování indexů je třeba dát pozor na to, aby jednotlivé trojúhelníky byly definovány po směru hodinových ručiček, aby normálové vektory byly obráceny směrem k pozorovatelovi, jinak by trojúhelník nebyl vidět.
Osvětlení
Protože nedefinujeme normálové vektory jednotlivých vertexů, nemá systém D3D jak spočítat osvětlení. Tudíž je třeba, abychom světlo zcela vypnuli pomocí metody SetRenderState(). Tato metoda je velmi důležitá, protože s její pomocí nastavujeme většinu parametrů chování aplikace. Podíváte-li se do dokumentace zjistíte, že má spoustu možných parametrů. V našem případě chceme pouze vypnout osvětlení:
g_lpD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
Toto je velmi důležité, protože jinak bychom krychli viděli zcela černou (to je způsobeno tím, že nejsou definovány normálové vektory). Kdybychom je definovali, museli bychom navíc vytvořit a nastavit nějaké světlo.
V další části lekce si povíme něco o texturách a texturování. V knihovně D3DX je spousta funkcí jak vytvořit texturu ze souboru či paměti, takže práce s texturami je celkem jednoduchá. O trochu složitější je systém texturování. To se provádí pomocí tzv. texturových souřadnic. Tyto souřadnice jsou složkami každého vertexu u objektu, který chceme otexturovat:
struct VERTEX {
float x, y, z;
float tu, tv;
};
#define VERTEXFORMAT
D3DFVF_XYZ|D3DFVF_TEX1
Složky tu a tv jsou texturové souřadnice. Nyní si vysvětlíme, jak se s těmito souřadnicemi pracuje:
Takže pomocí těchto souřadnic určíme pozice odkud se čte zdrojová textura. Vše je vidět na obrázku. V prvním případě naneseme celou texturu, protože souřadnice mají maximální hodnotu 1.0. Ve druhém případě nanášíme pouze jednu čtvrtinu, protože maximální hodnota je pouze 0.5. Pokud byste zadali hodnotu větší jak 1.0, dosáhli byste opakování textury (pokud například zadáte 2.0, textura se zopakuje 2x).
Nyní se vrátíme k naší krychli. Když se nad problémem trochu zamyslíte, zjistíte, že nyní už to nebude tak jednoduché jako v případě první krychle (pro lepší představivost je dobré si kostku nakreslit na papír). Potíž je v tom, že každý vertex má jedny texturové souřadnice, jenže abychom mohli nanést texturu na kostku je nutné definovat pro každou stranu vlastní texturové souřadnice. Z toho plyne, že nyní si již nevystačíme s osmi vertexy! Budeme jich definovat rovnou 24 (pro každou stranu 4, takže 6 x 4)! Zde se bohužel ztrácí význam indexace.
Vertexy definujeme následovně:
VERTEX arCube[] = {
// top
{ 2.0f,
2.0f, 2.0f, 0.0f, 0.0f},
{ 2.0f, -2.0f, 2.0f, 0.0f, 1.0f},
{-2.0f, 2.0f, 2.0f, 1.0f, 0.0f},
{-2.0f, -2.0f, 2.0f, 1.0f, 1.0f},
// bottom
{ 2.0f,
2.0f, -2.0f, 0.0f, 0.0f},
{ 2.0f, -2.0f, -2.0f, 0.0f, 1.0f},
{-2.0f, 2.0f, -2.0f, 1.0f, 0.0f},
{-2.0f, -2.0f, -2.0f, 1.0f, 1.0f},
// left
{-2.0f, -2.0f,
2.0f, 0.0f, 0.0f},
{ 2.0f, -2.0f, 2.0f, 0.0f, 1.0f},
{-2.0f, -2.0f, -2.0f, 1.0f, 0.0f},
{ 2.0f, -2.0f, -2.0f, 1.0f, 1.0f},
// right
{-2.0f, 2.0f,
2.0f, 0.0f, 0.0f},
{ 2.0f, 2.0f, 2.0f, 0.0f, 1.0f},
{-2.0f, 2.0f, -2.0f, 1.0f, 0.0f},
{ 2.0f, 2.0f, -2.0f, 1.0f, 1.0f},
// front
{-2.0f, -2.0f,
2.0f, 0.0f, 0.0f},
{-2.0f, 2.0f, 2.0f, 0.0f, 1.0f},
{-2.0f, -2.0f, -2.0f, 1.0f, 0.0f},
{-2.0f, 2.0f, -2.0f, 1.0f, 1.0f},
// back
{2.0f, -2.0f,
2.0f, 0.0f, 0.0f},
{2.0f, 2.0f, 2.0f, 0.0f, 1.0f},
{2.0f, -2.0f, -2.0f, 1.0f, 0.0f},
{2.0f, 2.0f, -2.0f, 1.0f, 1.0f},
};
Nyní máme pro každou stranu nadefinované 4 vrcholy. Všimněte si, jakým způsobem nastavujeme texturové souřadnice. Protože stranu definujeme pomocí 4 vrcholů, je třeba každou stranu indexovat:
WORD arCubeIndices[] = {
// top
1,0,2, 1,2,3,
// bottom
4,7,6, 4,5,7,
// left
9,8,10, 9,10,11,
// right
13,14,12,
13,15,14,
// front
17,18,16,
17,19,18,
// back
21,22,23,
21,20,22
};
Nyní je samozřejmě třeba změnit velikost vertex bufferu na 24. Když se teď kostku pokusíte vykreslit, uvidíte pouze bílý obrys, protože jste nenastavili žádnou texturu!
Jak bylo zmíněno dříve, knihovna D3DX dobře podporuje správu textur. Máme zde hned několik funkcí:
Všechny tyto funkce pracují s objektem typu IDirect3DTexture8, ale častěji se setkáte s ukazatelem LPDIRECT3DTEXTURE8. Konkrétní využití těchto funkcí si postupně ukážeme v příštích lekcích. Dnes si vystačíme s první jmenovanou D3DXCreateTextureFromFile(). Nejprve tedy definujeme objekt textury:
LPDIRECT3DTEXTURE8 g_lpTexture = NULL;
V dalším kroku vytvoříme texturu z bitmapy:
// load texture from the file
D3DXCreateTextureFromFile(g_lpD3DDevice, "texture.bmp", &g_lpTexture);
Aby se daná textura aplikovala, musíme jí nastavit pomocí metody SetTexture().
// set texture
g_lpD3DDevice->SetTexture(0, g_lpTexture);
Toto volání provedeme těsně před vykreslením krychle. Pokud bychom chtěli mít na každé straně jinou texturu, museli bychom strany vykreslovat postupně (6 volání metody DrawIndexedPrimitive() a mezi každým voláním nastavovat jinou texturu). Všimněte si, že metoda má dva parametry. První parametr určuje tzv. texture stage. Tento parametr je využíván u míchání textur (texture blending), ale o tom si povíme někdy příště. Druhý parametr je ukazatel na naší texturu.
Tak toto byl lehký úvod do texturování
objektů v Direct3D. Texturování je ovšem obsáhlá kapitola, takže další
vlastnosti a nastavení si ukážeme v dalších lekcích.
Dnes jsme se konečně ponořili do pravého 3D světa Direct3D. Snad se vám dnešní lekce líbila.
Co budeme probírat příště? Snad již konečně začneme budovat náš malý 3D engine. Vytvoříme pár knihoven podobně jako tomu bylo u DirectDraw.
Těším se příště nashledanou.