A számítógépes grafikában nagyon fontos szerepet töltenek be a normálvektorok. Az árnyalási feladatoknál, a látható felszínek szűrésénél, illetve a megvilágítási feladatoknál egyszerűen nélkülözhetetlenek. A fogalommal már az előző bejegyzések során megismerkedtünk, de azért nem árt még egyszer górcső alá venni a témát.
De mi is a normálvektor?
A normálvektor (vagy normális) egy olyan egységhosszúságú vektor, ami arra mutat, amerre a háromszögünk néz, avagy egy merőleges a háromszögre. Mielőtt tovább mennénk érdemes leszögezni, hogy ha vektorról beszélünk, akkor minden esetben az origóból kiinduló vektorra kell gondolni, azaz helyvektorra. Ekkor értelemszerűen a vektornak nem két pontja lesz, hanem csak egy (ahová mutat), hisz az első pontja mindig fixen az origó, így a pont és vektor egymással egyenlő fogalom. Vagyis, ha meg akarunk adni egy vektort, elég csak a végpontjának koordinátáit megadni.
Egy síkfelület esetén, a merőleges irány a felület összes pontjára ugyanaz, de egy nem egyenletes felület esetén, a normális a felület minden pontján más és más lehet. Az OpenGL-ben minden poligonhoz és minden vertexhez tartozik normális. Ugyanazon poligon vertexei ugyanazzal a normálissal is rendelkezhetnek, de lehetnek különböző normálisaik is. Egy poligon normálisát az OpenGL általában a vertexeinek a normálisaiból számítja ki. Normálisokat azonban csak vertexekhez specifikálhatunk.
Hogyan számolható a felületi normális?
A normálisokat a számítógépes grafikában több helyen is alkalmazzák. Ahhoz, hogy egy sík egyenletét le tudjuk írni, szükségünk van a sík normálisára, vagyis egy, a síkra merőleges vektor koordinátáira. Ekkor a sík egyenlete a következőképpen néz ki: Ax + By + Cz + D = 0
A vertexek normálisának számítása a vektoriális szorzattal történik (angolosan Cross Product):
Nx = Ay * Bz - Az * By
Ny = Az * Bx - Ax * Bz
Nz = Ax * By - Ay * Bx
Ny = Az * Bx - Ax * Bz
Nz = Ax * By - Ay * Bx
ahol A, B és N is egy háromelemű vektor. A számított N(Nx,Ny,Nz) vektor egy olyan vektor mely pontosan merőlegesen mutat kifelé az A és B által határolt síkból. Most már csak meg kell határoznunk az A és B vektort. Legyen a háromszögünk három csúcsa V1, V2 és V3 (ezek ugye vektorok):
A = V3 - V1
B = V2 - V1
B = V2 - V1
A normálvektornak mindenképpen egységvektornak kell lennie, ha nem, akkor az árnyalás helytelen képet adhat. Egységvektort nagyon egyszerű képezni bármilyen vektorból, ugyanis annyi, hogy el kell osztani minden iránykomponensét (x, y, z koordinátáját) a vektor hosszával. A vektor hossza az iránykomponensek négyzetösszegének négyzetgyöke (Pitagorasz-tétel)).
|N| = sqrt(Nx^2 + Ny^2 + Nz^2)
A fenti számítással a vektor rendelkezésre álló adataiból meghatároztuk annak hosszát. Ez persze csak akkor van így, ha a vektorunk a (0,0,0) pontból mutat a (Nx, Ny, Nz) pontba. Ha mondjuk egy (Ox, Oy, Oz) kezdőpontú vektorról lenne szó, azt el kellene tolni előbb az origóba. A hossz ismeretében végül a vektor koordinátáit leosztjuk, s megkapjuk az egységvektort:
Nx = Nx / |N|
Ny = Ny / |N|
Nz = Nz / |N|
Ny = Ny / |N|
Nz = Nz / |N|
Ezzel a számítással máris kiszámoltuk egy lap felületi normálisát. Igazándiból a fenti műveletre nincs is szükség ugyanis erre az OpenGL tartalmaz egy automatikus módszert, amit csak engedélyeznünk kell: glEnable(GL_NORMALIZE); Ekkor automatikusan normalizálja a normálvektorokat. Egyetlen-egy baj van vele, hogy nagyon lassú ezért érdemes, ha mi magunk számoljuk ki ezeket a feladatnak megfelelően, és csak akkor, amikor valóban kell. Megjegyzés: a glScale utasítás hatására a normálvektorokat mindig újra kell normalizálni, mert egyébként hibás lesz a megvilágítás, ilyenkor hasznos a fenti OGL parancs.
A baj csak az, hogy nekünk a vertex-ekhez kell normálist számolni, egy vertex-hez pedig több lap kapcsolódik általában. Normálvektort egy adott pontba csak úgy számolhatunk, ha a kérdéses pontot tartalmazó összes háromszög felületi normálisát számítjuk, majd ezek átlagát, mint pszeudo normálist fogadjuk el megoldásként (lenti ábra).
Az egyszerű átlag helyett célszerű valamilyen szempont szerint súlyozni a normálisokat, hiszen könnyen belátható, hogy a háromszögek nagyságuk és alakjuk alapján eltérő módon befolyásolják a pontban értelmezett normálist. A legegyszerűbb és legelterjedtebb módszer az, hogy a háromszögek területével arányos súlyokat alkalmazunk. A másik „pontosabb” módszer pedig az, hogy a kérdéses pont és a hozzá csatlakozó háromszög által meghatározott közös szögértékkel arányos súlyokat vezetünk be.
Fények, fények, ragyogjatok
A legutóbbi OGL példában a megvilágítással foglalkoztunk ugyan mégsem számítottuk ki a felületi normálisokat. Ezt ezért tehettük meg, mert a GLUT elvégezi helyettünk a számításokat, ha valamely előre definiált objektumot szeretnénk kirajzolni az alábbiak közül:
- glutSolidTeapot
- glutSolidSphere
- glutSolidCube
- glutSolidCone
- glutSolidTorus
- glutSolidDodecahedron
- glutSolidOctahedron
- glutSolidTetrahedron
- glutSolidIcosahedron
Felmerülhet a kérdés, hogy hogyan adhatjuk meg a normálisokat, ha nem a GLUT előre definiált objektumait rajzoljuk. Nos, erre van egy glNormal3f (legtöbb esetben, de mint a legtöbb OpenGL parancsnak, ennek is több verziója van, a normális adatok típusához megfelelően, s külön, ha tömbként adjuk meg) parancs, amit a glVertex paranccsal párban kell meghívnunk.
A fények és a normálisok szorosan összekapcsolódnak, de mit sem érnek anyagbeállítások nélkül. Legutóbb az anyagtulajdonságok beállítására a glMaterial parancsot alkalmaztuk, ez viszont elég lassú és bonyolultabb esetekben pedig használhatatlan is. Ezért létezik egy másik módszer, amit inkább ajánlanak a glMaterial helyett, ez a „color tracking” módszer. Hogy ezt használhassuk, engedélyeznünk kell:
glEnable(GL_COLOR_MATERIAL);
Ezután az előző anyag meghatározással egyenlő a következő:
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
glColor3f(1.0f, 0.5f, 0.2f);
glColorMaterial(GL_FRONT_AND_BACK, GL_SPECULAR);
glColor3f(1.0f, 1.0f, 1.0f);
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 32.0f);
Ha jobban megnézzük, akkor elsőre kiszúrjuk, hogy a glColorMaterial és a glMaterial parancs majdnem megegyezik, annyival, hogy a glColorMaterial esetében, csak a poligon oldalait, és a kívánt tulajdonságot határozzuk csak meg, viszont ezek értékeit már a jól ismert glColor parancs adja meg. Egyetlen hátrány, hogy a visszavert fény erősségét (GL_SHININESS) továbbra is a glMatrial-lal tudjuk csak megadni.
A klasszikus teáskanna |
A példaprogram nagyon hasonló az előző tutoriálhoz, csak itt a klasszikus teáskanna modellen próbáljuk ki a fények és anyagok beállítását. A ColorMaterial témához kapcsolódó példaprogram letölthető: DOWNLOAD
A háromszögek körbe-körbe járnak
Érdemes még tudni, hogy az oldallapok normálisának számításakor a normálvektor irányát nagyban befolyásolja a lap bejárási iránya (hogy milyen sorrendben adtuk meg a pontjait). Ez a sorrend nem csak ennél a normálvektor számításnál lényeges. Az OpenGL ennek segítségével dönti el, hogy a poligonnak melyik oldala hátsó és melyik elülső. Ha helyesen adtuk meg a bejárási irányt minden oldallapnál, akkor akár gyorsíthatunk is a programunkon, ha bekapcsoljuk a hátra felé néző lapok eltávolítását: glEnable(GL_CULL_FACE); Ekkor a rendszer minden poligonhoz normálvektort számol és ennek és a nézőpontnak (nézési irány alapján) veszi a közbe zárt szögét és ez alapján dönti el, hogy hátsó lap-e. Nyilván, ha a normálvektor felénk mutat, akkor hegyes szög lesz, ha meg ellenkező irányba, akkor tompaszög. Mivel nagyjából minden második poligon hátsó lap így akár megduplázhatjuk a program sebességét.
0 megjegyzés :
Megjegyzés küldése