játékfejlesztés.hu
FórumGarázsprojectekCikkekSegédletekJf.hu versenyekKapcsolatokEgyebek
Legaktívabb fórumozók:
Asylum:    5448
FZoli:    4892
Kuz:    4455
gaborlabor:    4449
kicsy:    4304
TPG:    3402
monostoria:    3284
DMG:    3172
HomeGnome:    2919
Matzi:    2520

Pretender:    2498
szeki:    2440
Seeting:    2306
Geri:    2188
Orphy:    1893
Joga:    1791
Bacce:    1783
MaNiAc:    1735
ddbwo:    1625
syam:    1491
(C++) DirectX programozás 14. - Direct3D 3. (3D-s programozástechnikai alapok) 2006.06.02 09:13


A háromdimenziós megjelenítés

Ez a cikk már tényleg a háromdimenziós megjelenítésről fog szólni, ugyanis megjelenítünk egy y tengely körül forgó négyzetet. Ez a kis feladat is sok tanulsággal fog szolgálni, úgyhogy lássuk is!

A forráskódból egyértelműen ki fog derülni, hogy mi az ami maradt a régi, és mit írunk át, de azért próbálok utalni ezen dolgokra. Kezdjük tehát az elején. Mivel mondtam, hogy egy négyzet kell, ezért meg kellene adnunk a térben egy négyzetet. Emlékezzünk vissza, hogy már említettem a használt koordinátarendszert. Tehát így az x tengely jobbra mutat, az y tengely felfele, a z pedig befele a képernyőbe. A négyzetünk így most legyen az xy síkban, és legyen a négyzet közepe az origó. Ekkor ugye minden vertex z koordinátája 0 lesz, az x és y koordinátákat pedig ki kell találnunk. Legyen a négyzet oldala két egységnyi, ekkor a négyzet koordinátái egységben:


Ekkor természetesen könnyű lesz forgatni, mert az y tengely lesz a négyzet forgástengelye, és mi pont a négyzet forgástengelye körül szeretnénk forgatni. Látni fogjuk, hogy a transzformációk komoly gondot fognak még okozni, de erről majd később.

Először inkább lássuk, hogy milyen vertex formátummal fogunk dolgozni:

#define CUSTOM_VERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)

Ez azt jelenti, hogy x,y,z modelltérbeli koordinátákat adunk meg, illetve a D3DFVF_DIFFUSE azt jelenti, hogy minden vertex kap egy színt is. Ez azt fogja jelenteni, hogy vertex színe a megadott lesz, a primitíven belül pedig a vertexek között folyamatos színátmenet lesz. Látható, hogy több tulajdonságot ’vagy’ kapcsolattal adhatunk meg.

Ekkor meg is adhatjuk a vertexünk adatainak struktúráját:

struct CUSTOMVERTEX {
      FLOAT x,y,z;
      DWORD color;
};

Az SDK dokumentációja egyértelműen megadja, hogy a szín egy DWORD típus. Ez 32 bites számot jelent, azaz a színkomponensek illetve egy alfa csatorna(ezt most nem használjuk). Ezzel meg is lennénk, adjuk meg a négyzet koordinátáit:

CUSTOMVERTEX vertices[]=
{
      { -1.0f, -1.0f, 0.0f, 0x0000ff00 },
      { -1.0f,  1.0f, 0.0f, 0x00ff00ff },
      {  1.0f, -1.0f, 0.0f, 0x00ffff00 },
      {  1.0f,  1.0f, 0.0f, 0x00ffaaff },
};

A szín természetesen ARGB formátumú, tehát például az első vertex tisztán zöld. Vegyük észre azt is, hogy TRIANGLESTRIP tipusú primitívet fogunk használni, tehát a negyedik vertex a másodikkal és a harmadikkal alkot egy háromszöget. Ez a négyzet nyílván el fog tűnni, ha megfordítjuk, mert akkor már nem pozitív bejárású lesz arról az oldalról. Erre a legjobb megoldás, ha megadunk még négy vertexet, csupán azokat ellenkező bejárással, hogy azok a túloldalról legyenek pozitív bejárásuak. Mi most egy sokkal rosszabb, ám egyszerűbb megoldást választunk: kikapcsoljuk azt a funkciót, hogy a nem pozitív bejárású primitíveket eldobja a Direct3D. De erről majd később, viszont emlékezzünk az itt elmondottakra!

Ezután teljesen azonos módon inicializáljuk a D3D-t, majd az eszközt is létrehozzuk és létrehozunk egy vertex buffert(most már négy vertexnek). Viszont most jönnek a különbségek: beszéltünk róla, hogy 3D-ben nem lehet csak úgy vaktában renderelni, hiszen meg kell mondanunk, hogy milyen nézőpontból, látószöggel, stb. szeretnénk lefékényképezni a modellteret. Hiszen ebben a modelltérben van egy négyzet, mint mondjuk az asztalunkon a valóságban, ezt pedig egy fényképezőgéppel(ezt most mi kamerának fogjuk hívni) akárhonnan lefényképezhetjük. De annak a fényképezőgépnek a lencséje állítható, így lehet, hogy a beállításai miatt például egy kört ellpiszisnek fényképez, stb. Ezért kell nekünk is megadnunk egy kamerát a modelltérben, az elhelyezkedését, valamint a beállításait. Ezen a kamerán át látható képet fogjuk mi a monitorunkon át látni.

A Direct3D ezt úgy oldja meg, hogy tárol három mátrixot: a modelltérbeli transzformáció mátrixot, a nézeti mátrixot illetve a projekciós, azaz vetítési mátrixot. Ezeken a mátrixokon a modelltérbeli pontjaink „végigfolynak”, így kerülnek a képernyőre(persze egyéb effektusok után). Az elsőről később fogunk beszélni, most egyenlőre az utolsó kettő a lényeg.

Nézzük először a nézeti mátrixot! Ezt a mátrixot már láttuk a bevezetőben hogyan kell létrehozni: a modelltérbeli pontokat kell megadni a kamera koordinátarendszerében. Direct3D-ben jelentős előny, hogy ezt a mátrixot nem nekünk kell létrehozni, ugyanis van egy D3DX-nek nevezett segédlet(függvénycsomag), amiben többek közt megtalálhatók a matematikai függvények is, mint például mátrixszorzás, létrehozás, stb. Ennek segítségével fogjuk létrehozni a nézeti mátrixot.

Emlékezzünk vissza, mivel is jellemezzük egy kamera térbeli elhelyezkedését? A poziciójával(eye), azzal a ponttal, ahova néz(lookat), illetve a felfele iránnyal(up). Ezért is hozunk először létre három vektort, melyek ezeket az adatokat tartalmazzák. A D3DX-ben egy 3D-s vektor típusa: D3DXVECTOR3. Ez egy osztály, így van olyan konstruktora, amellyel létrehozáskor feltölthető, nyilván sorban az x,y,z koordinátájával:

D3DXVECTOR3 vEyePt   ( 0.0f, 0.0f, -5.0f );
D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
D3DXVECTOR3 vUpVec   ( 0.0f, 1.0f, 0.0f );

Az első vektor a kamera poziciója, ez most a z tengelyen van a -5 pontban. A második vektor szerint a kamera az origót nézi, és a harmadik vektor szerint a kamera függőlegesen van, mert a felfele irány az y tengely. Ennyire egyszerű. Ezekkel a vektorokkal, és egy D3DX függvénnyel megalkothatjuk a nézeti mátrixot:

D3DXMATRIX        matView;
D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );

Itt deklaráltunk egy mátrixot, majd feltöltjük a megfelelő értékekkel. Gondolom a függvény paraméterezése egyértelmű, csupán a nevében rejlő LH-ról említeném meg, hogy az Left Hand-et jelent, és a balkezes koordinátarendszerre utal, amelyikről már beszéltünk, hiszen azt használjuk. Lehetőség van arra, hogy jobbkezest használjunk, akkor a z tengely kifele mutat a képernyőből.

Beszéljünk most a vetítési mátrixról! A vetítés mint mondtam, jellemezhető a látószöggel(ez most 45 fokos lesz), az aspektus aránnyal és az elülső és hátulsó vágósíkokkal. Ez utóbbi kettő 1.0-tól 100.0-ig lesz, az aspekus arány pedig nyílván az ablakunk oldalainak az aránya, azaz 800/600. A vetítési mátrixot ugyancsak létrehozhatjuk egy D3DX függvénnyel:

D3DXMATRIX        matProj;
D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI/4, 800.0f/600.0f, 1.0f, 100.0f );

Itt a második paraméter a látószög(pi/4=45 fok, ha nem lenne világos), a harmadik a aspektus arány, a negyedik és ötödik a vágósíkok(nyílván csak ezek közt renderelünk).

Most hasznosítani kellene ezeket a mátrixokat, ezért megismerünk egy fontos függvényt. Ezt is ügyesen oldották meg, hiszen a világ, nézeti és vetítési mátrixok beállítása nem három függvény, hanem egy, aminek a paraméterlistájában meg kell adni, hogy éppen melyiket szeretnénk beállítani:

lpD3D_Device->SetTransform( D3DTS_VIEW, &matView );
lpD3D_Device->SetTransform( D3DTS_PROJECTION, &matProj );

A konstansok szerintem egyértelműek(van még egy, ez pedig a modelltérbeli transzformáció, D3DTS_WORLD), a második paraméter pedig természetesen a mátrixok. Ezzel megadtuk azt, hogy például amikor a Direct3D nézeti transzformációt hajt végre, akkor a megadott mátrixot használja. Ez pedig a továbbiakban nagyon hasznos lesz, meglátjuk. Hangsúlyozom, hogy ezen transzformációk egyike sem módosítja a vertex bufferben lévő vertexadatokat, hiszen akkor az első képkocka után elvesznének! A Direct3D csupán lemásolja a vertexeket, majd a másolatokat transzformálja.

A következő nagyon fontos függvényünk a renderelési opciók beállítását végző függvény. Ez is egy függvény, csupán meg kell adni melyik opciót szeretnénk állítani, majd értéket adunk neki. Akkor nézzük ezzel a függvénnyel például a világítás kikapcsolását:

lpD3D_Device->SetRenderState(D3DRS_LIGHTING, false);

Az opciókat leíró konstansokat, és azok lehetséges értékeit az SDK dokumentációja tartalmazza(a beállítható opciók D3DRS_-el kezdődnek, utalva a RenderState-re). Itt most a világítást azért kapcsoltuk ki, mert akkor (fény híján) minden teljesen fekete lenne a hátteret kivéve. A másik beállítandó opcióról már beszéltünk, azaz nem szeretnénk, hogy a nézőpontból nem pozitív bejárású primitíveket eldobjuk. Cullmode-nak hívjuk, ha ezeket a primitíveket megtartjuk. Ezt általában kapcsoljuk ki(alapállapotban a D3D eldobja ezeket a primitíveket), ugyanis lassít(pl. egy kocka belsejét minek lerenderelni). Ennek a beállítása:

lpD3D_Device->SetRenderState(D3DRS_CULLMODE, true);

Ezzel látható lesz a primitívek „hátoldala” is.

Ezek után már meg is jeleníthető a négyzet térben, ez semmiben sem különbözik a 2D-s háromszög megjelenítésétől, csupán 2 primitívet jelenítünk meg, és a primitív típusa D3DPT_TRIANGLESTRIP. Viszont említettem, hogy forgatni szeretnénk. Forgatni a koordinátatengelyek körül tudunk, így figyelni kell a négyzetünk poziciójára. De már észrevettük, hogy pont jó helyen van, hogy az y tengely körüli 360 fokos forgatással önmagába menjen át. Ezért nézzünk egy mátrixot, ami az y tengely körül forgat:

D3DXMATRIX matWorld;
D3DXMatrixRotationY( &matWorld, 0.01f );

Ez is egy D3DX függvény, és most a matWorld mátrixot feltöljük a 0.01 radiánnal való forgatáshoz szükséges értékkel. A z tengely körüli forgatáshoz nyilván a D3DXMatrixRotationZ(…) függvény kell. Most jön az érdekesség. Mint már ezt is mondtam, be kell állítani, hogy a modelltér összes vertexére alkalmazza ezt a transzformációt. Ezt ugyebár a SetTransform (…) függvénnyel tudjuk beállítani, méghozzá megmondva, hogy a modeltérre alkalmazza:

lpD3D_Device->SetTransform( D3DTS_WORLD, &matWorld );

De ha ezután lefuttatnánk a programot, a négyzet nem forogna, hanem csak egyhelyben állna, igaz 0.01 radiánnal elforgatva. Ez azért van, mert a Direct3D bölcsen NEM nyúl hozzá a vertex bufferbeli vertexeinkhez, hanem minden egyes periódusban(képkockarenderelénél) kiveszi onnan a vertexeinket, majd azokat transzformálja. Tehát minden periódusban az eredeti modellteret transzformálja, nem a transzformáltat. Így a forgatáshoz minden periódusban egyre nagyobbat kell forgatni:

D3DXMatrixRotationY( &matWorld, i+=0.01f );

ahol i egy statikus változó, azaz kívülről nem látható, de megőrzi értékét a render() függvényen belül. Ekkor már tényleg forgatni fogunk. Aki nagyon sokáig szeretne forgatni, figyeljen a túlcsordulásra, hiszen i-t vég nélkül növeljük, mert kihasználjuk a forgatás periódicítását.

Észrevehetjük, hogyha a négyzetnek az y tengely nem forgástengelye, akkor a négyzet nem a tengelye körül fog forogni, hanem akkor is az y tengely körül(próbáljuk ki!). Ezen tudunk segíteni, méghozzá a már megbeszélt módon, méghozzá transzformációk sorozatával: a négyzetet eltoljuk az origóba(ahová a feladatban is raktuk), ott forgatjuk, majd visszatoljuk az eredeti helyére. Az a kérdés, ezt hogy tudjuk megtenni? De ismereteink birtokában tudnunk kell a választ: egy transzformációsorozat az egyes transzformációk mátrixainak a sorrendbeli szorzata. A mátrixszorzásra pedig van egy D3DXMatrixMultiply(…) függvény. Az eltolásra pedig ott van a D3DXMatrixTranslation(…). Ekkor a fenti műveletet így tudjuk elvégezni:

D3DXMatrixTranslation(&mat,-x,-y,-z);
D3DXMatrixRotationY( &matWorld, angle );
D3DXMatrixMultiply(&matWorld,&mat,&matWorld);
D3DXMatrixTranslation(&mat,x,y,z);
D3DXMatrixMultiply(&matWorld,&matWorld,&mat);

Itt mat és matWorld mátrixok. Tehát ha a test középpontja(ami körül forgatni akarjuk) az (x,y,z) pontban van, akkor először eltoljuk (-x,-y,-z)-vel, ebből kapunk egy mátrixot(mat). Csinálunk egy angle szöggel való forgatás mátrixot(matWorld). Ezeket összeszorozzuk sorrendben(D3DXMatrixMultiply(…) első paramétere az eredmény, a másik kettő sorrendben a szorzatban érvényes sorrend:), azaz először az eltolás(mat), aztán a forgatás(matWorld), majd az eredmény a matWorld-be kerül, ami azért jó, mert így nem kell egy csomó mátrixot deklarálni. Szóval ezek után csinálunk egy (x,y,z)-vel való eltolás mátrixot mat-ba, ugyanis annak tartalma már nem kell, és azt összeszorozzuk matWorld-el, persze matWorld*mat sorrendben. Az eredmény matWorld-be kerül, láthatjuk, hogy mat csak ideiglenesen kellett… Végül matWorld-ben lesz a transzformáció, ami nekünk kell, ezt beállíthatjuk modelltér transzformációnak. Kész is vagyunk.

forráskód: d3d2.exe


2006. február 8.

Crusader


Kapcsolódó linkek:

A cikksorozat további részei:

Értékelés: 10.00

Új hozzászólás
Nincs megjegyzés