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 19. - Direct3D 8. (Test kiválasztása a modelltérben) 2006.08.12 12:56


Tárgy kiválasztása a modelltérben

Ez a cikk egy nagyon fontos játéktechnikai dologról fog szólni, méghozzá egy test vagy tárgy egérrel történő kiválasztásáról(kattintással például). Ez fontos például stratégiajátékokban egységek kiválasztására, First Person Shooter játékokban pedig például kapcsolók stb. kiválasztására.

A példaprogram megpróbálja a lehető legegyszerűbben ábrázolni ezt a dolgot, kiválasztáskor csak egy üzenetablakkal értesíti velünk, hogy kiválasztottuk. Továbbá szemléletesen rajzol egy sugarat is, amiről majd később fogunk beszélni.

Szemléltetésül a legegyszerűbb dolgot fogjuk vizsgálni, méghozzá a testet egy gömbnek képzeljük, és ennek a gömbnek a kiválasztását vizsgáljuk. Ez két dolog miatt sem jelent nagy korlátozást:
  1. A testek nagy része, illetve például stratégiajátékokban az egységek célszerűen közelíthetőek gömbbel
  2. Az itt ismertetett módszer könnyen átvihető precíz, poligonnal való metszés vizsgálatára
Továbbá ha lehetséges, célszerű gömbbel közelíteni, hiszen jelentős számítási teljesítményt spórolunk meg. Igaz, a példában a kockák gömbbel való közelítése ’nem túl’ pontos:), de a módszer bemutatása céljából tökéletes.

Nézzük először a példaprogramban a köritést. Az első lényeges dolog a kamera külön osztályba tétele, de ez nem hiszem, hogy különösen érthetetlen lenne. Egy függvény érdekes benne, ez a GetRay(…) függvény, de erről később. A másik fontos dolog a cPrimitive absztrakt osztály. Ez nyilván magában használhatatlan, hiszen a Render() tagfüggvénye megvalósítatlan(mivel fogalmunk sincs, mit rendereljünk), viszont remek öröklődési alap, hiszen egy gömbbel reprezentálható primitívet valósít meg. Itt primitív alatt értsük például a kockát, gúlát, stb. Azért fontos ez az osztály, mert a kiválasztást úgy fogjuk vizsgálni, hogy megnézzük, hogy a befolgaló gömböt kiválasztottuk-e. Ez az osztály valósítja meg az InterSect függvényt is, ami később ugyancsak fontos lesz. Fontos, hogy megvalósítja az osztály a textúrázást is.

Minden testre, ami a cPrimitive osztályból öröklődött, külön odafigyelés nélkül megvalósul a befoglaló gömb segítségével való kiválasztás lehetősége. Ezért célszerű egy ilyen osztály megírása. Ez az osztály tartalmazza nyilván a befoglaló gömb sugarát is(radius).

Példának okáért implementáltam egy osztályt, méghozzá egy kockát(origóba létrehoz egy kockát, amit ugye a SetPos fügvénnyel akárhova rakhatunk). Ez a cCube osztály a cPrimitive osztályból öröklődik, látható, hogy így milyen egyszerű dolgunk adódik. Egyetlen fontos dolog a Render() függvény megvalósítása. Így egy kocka létrehozása a méretének, helyének és textúrájának megadásával történik, renderelése pedig a cCube::Render() függvény meghívásával. Remélem érthető ezen osztályok célszerűsége.

További alakzatokat a cPrimitiv alaposztályból való öröklődéssel adhatunk hozzá. Így automatikusan működni fog rá a kiválasztás.


Nézzük akkor a tényleges elvét a kiválasztásnak. Már az elején felvetődik a kérdés, hogy mi is a kiválasztásunk elvi alapja? Milyen megközelítésben fussunk neki? Tehát ha kattintunk a képernyőn, kapunk két számot, egy x és egy y koordinátát pixelben. Namost, mit jelentenek ezek a számok a modelltérben? Erről fogunk beszélni.

Nagyon helyesen gondolkodunk, ha a kamera működése szempontjából vizsgáljuk a dolgot. Hiszen a kamera készít a modelltérbeli dolgokról egy pixeltérképet a képernyőn. Megbeszéltük, hogy a kamera úgy működik, hogy van egy nézőpont, ezt adja meg az Eye vektor, majd a modellteret ezen a nézőponton keresztül az elülső vágósíkra képezi le. És igen, mi ezen az elülső vágósíkon jelölünk ki egy pontot, majd ennek a kamera-koordinátarendszerbeli koordinátáit ismerjük.

A kiválasztás alapja pedig alább látható:


Itt látszik egy kocka a modelltérben és a kamera is(a kamerán látszik a kocka képe is). Ekkor válasszunk ki egy pontot a kocka képében a képernyőn (a képernyő a kamerának, azaz a gúlának az alapja, a téglalap). Ezen a ponton keresztül a kamera csúcsából(ez a kamera poziciója, Eye) húzzunk egy félegyenest. Ha ez metszi a gömböt, akkor kiválasztjuk a kockát, ha nem metszi, akkor nem. Ez lenne tömören a kiválasztás alapja. Látható, hogy a problémát a perspektivikus vetítés jelenti, így ezt fogjuk most részletesen beszélni, hiszen nem elég érteni, hanem meg kell tudnunk csinálni visszafele is… Ugyanis ha megcsináljuk visszafele, akkor helyesen cselekszünk, hiszen minden pont azon az egyenesen a képernyőn kiválasztott pontra képződött le a vetítésnél.

Először a kamera koordinátarendszert kell megbeszélnünk. Mi most az 1.0 távolságra lévő síkra való leképezést beszéljük, hiszen a mi esetünkben(adott látószög) ez mindig érvényes. Ekkor a kamera koordinátarendszerében az egységek modelltérbeli távolságokkal értendőek. Már csak a két bázisvektor kell, ezek pedig a felfele(up) és a jobbramutató(right) egységvektorok. Itt most a felfele vektor nem az eddig megismert vektor, mert ez a vektor mindig a kamerához képest mutat felfele, azaz ha előredőlünk, akkor már nem ugyanoda mutat, azaz ez a vektor is előrefordul. Tehát mindig merőleges a nézeti irányra. Az origó a lemez középpontja. Ezek a vektorok így képzelhetőek el:


A felfelé mutató az up, a balra mutató a right, ami azért right, mert a nézőpont szempontjából jobbra mutat. Ez a két vektor nyilván a kamera koordinátarendszerében az up(0.0,1.0) és right(1.0,0.0) koordinátájuak(lévén bázisvektorok). Bonyolúltabb a helyzet, ha ezen vektorok modelltérbeli koordinátájú pontjaira vagyunk kiváncsiak. Pedig ez nagyon fontos nekünk. Kérdés, hogy hogyan kaphatóak meg ezek a vektorok a megismert Eye,LookAt és az Up vektorokból? Legyen Dir=LookAt-Eye, és legyen Dir normalizált(azaz egységvektor). Ekkor:

és

A keresztszorzatok iránya balkézzel számítandó, hiszen balkezes koordinátarendszert használunk! Természetesen az összes vektort normalizálni kell. Direct3D-ben a keresztszorzat a D3DXVec3Cross(…) függvénnyel számolható.

A következő izgalmas kérdés a kamera lemezének a mérete. Ezek kiszámítására három adatunk van: a csúcs lemeztől való távolsága(ez most 1.0), az y tengely irányú látószög(fov) és az aspect ratio(aspect). Nézzük akkor a függőleges méretét:


Tehát a magasság fele egy egyszerű tangens függvénnyel számítható. A szélesség a magasságnak az aspect ratio-szorosa. Így a méret: (2*tg(fov/2));(2*aspect*tg(fov/2)).

Remélem érthető. Mivel a kamera koordinátarendszere és a képernyő pixelei között egyenes arányosság van, ezért könnyedén felirhatjuk a kiválasztott pixelekből a kamera koordinátarendszerbeli koordinátákat. Ehhez nyilván először el kell tolni a képernyő koordinátarendszerét a képernyő közepébe, hiszen a képernyőn az ablak bal felső sarka az origó, a kamerában pedig a lemez közepe. Így legyen a kiválasztott pont az (xPos,yPos), (x,y) pedig a kamera koordinátarendszerbeli koordináták, (width,height) pedig a képernyő mérete(pl. 800*600). Felirva az egyenes arányosságot a koordinátákra:




Utóbbinál a negatív előjel azért kell, mert a kamera koordinátarendszerben az y tengely felfele, míg a képernyőn lefele mutat. Ezekből x és y megkapható. Ekkor már könnyen megkapható a kiválasztott pont modelltérbeli koordinátája. Ehhez kellettek az előbb az bázisvektorok, hiszen a kamera lemezének középpontjából a kiválasztott pontba mutató vektor nyilván a bázisvektorokkal leírva:

x*right+y*up

Ehhez már csak a kamera lemezének a közepébe mutató vektort kell hozzáadni, hogy modelltérbeli koordinátákat kapjunk, ez pedig egyszerűen az Eye vektor(azaz a kamera csúcsának poziciója) és a Dir egységvektor összege(mivel a lemez 1.0 távolságra és Dir irányban van a csúcstól). Ekkor a tényleges pont:

point=Eye+Dir+(x*right+y*up)

Tehát meg is határoztuk a pontot. Akkor a kamera csúcsából induló, és ezen a ponton(azaz a kiválasztott ponton) átmenő sugár:

ray=Eye+t*(Dir+(x*right+y*up))

Ez lényegében egy egyenes egyenlete, ahol t a paraméter. Az a kérdés, hogy ez metszi-e a kockát, vagy testet befoglaló gömböt? Ha igen, akkor az azt jelenti, hogy ez az egyenes a gömb sugaránál közelebb van a test középpontjához, ami könnyen vizsgálható a pont és egyenes távolságával(ezért is mondtam, hogy könnyű gömbre metszéspontot számolni). De ezt már nem írom le, hiszen az első Matematika cikkben erről beszéltünk. Csupán segítségnek írom, hogy a skaláris szorzást a D3DXVec3Dot(…) függvénnyel, vektor hosszat pedig a D3DXVec3Length(…) függvénnyel számolhatunk. És vigyázzunk, hogy a számoláshoz egységirányvektor kell!

A forráskódban a cCamera::GetRay(…) függvény számolja ki a megadott pixelen átmenő sugarat, amit s_ray struktúrában ad vissza, ami tartalmazza a sugár kezdőpontját és irányát(nem egységvektor!). A cPrimitive típusú primitívekre a cPrimitive::InterSect(…) függvény adja meg, hogy van-e metszéspont a paraméterben adott s_ray típusú sugárral. Ha igen, true értékkel tér vissza.

Megjegyzem, hogy a sugár ismeretében nagyon könnyű poligonokra is vizsgálni a metszéspontot, csupán az sokkal több időt vesz igénybe, hiszen például kocka esetén 12 háromszögre kell vizsgálni a metszést, és egy háromszögre elvégezni a számolást is számításigényesebb. Útmutatásul javasolom a D3DXIntersectTri függvényt az SDK-ban, illetve a Matematika cikkek közt az első részhez tartozó feladatok közül az utolsó feladatot.

pick.exe


2006. február 25.

Crusader


Kapcsolódó linkek:

A cikksorozat további részei:

Értékelés: 0

Új hozzászólás
KergeDelfin          2006.08.27 22:52
A példaprogi 404-et dob. ^^