Hirdetés

Alkalmazásfejlesztés badára: Rajzolás alapok, nem csak kezdőknek

Bevezető

A mai bejegyzésben végre elkezdjük az OpenGL programozást. Első körben a keretrendszer által kreált Frame Based Application két függvényét, az OnAppInitializing-ot és az OnForeGround-ot fogjuk kiegészíteni. A hibák lekezelése felett elegánsan átsiklok, egyrész inkább az OpenGL-specifikus részre koncentrálok a posztban, másrészt ugyanazokat használom, amik a Samsung által biztosított Glescube11 nevű példaalkalmazásban láthatók. A kód természetesen letölthető a bejegyzés végén lévő linkről.

EGL és OpenGL inicializálás

Mielőtt elkezdhetnénk a rajzolást, van pár fontos inicializálni valónk, pontosabban kettő: az egyik az EGL, a másik az OpenGL. Utóbbiról már volt szó a blog keretein belül, az előbbi sem maradhat ki, ha már szükség van rá. Szóval az EGL (Embedded system Graphics Library) egy interfész az OpenGL ES és a készülék natív ablakozórendszere között. Tehát ahhoz, hogy rajzolgathassunk az OpenGL segítségével egy beágyazott rendszeren, muszáj az EGL-hez nyúlnunk. Nem kell megijedni, inicializálás után transzparensen teszi a dolgát. Hozzunk létre egy függvényt, mondjuk legyen initEGL(). Ezen belül hozzá kell kötnünk az alkalmazásunkhoz az OpenGL API-t (eglBindAPI(EGL_OPENGL_ES_API)), el kell kérnünk a kijelző referenciáját (eglGetDisplay( (EGLNativeDisplayType) EGL_DEFAULT_DISPLAY)), ezt inicializáljuk, majd hozzárendelünk egy rakás egyéb beállítást, mint az egyes színkomponensek mérete (RGBA - vörös, zöld, kék és alfa-csatorna), a felület típusa (EGL_SURFACE_TYPE: létezik BUFFER típusú, ami a memóriába rajzol, PIXMAP, ami egy pixeltérképre, valamint a WINDOW, ami az ablakra: természetesen ez kell nekünk), s a többi. Ezeket megetetjük az eglChooseConfig függvénnyel. Következnek az utolsó lépések: az eglCreateWindowSurface segítségével létrehozzuk a displayünket, majd létrehozzuk az OpenGL kontextusát az eglCreateContext függvénnyel. Az így elkészült függvényt hívjuk majd meg az OnAppInitializing törzsében. Ezekkel a lépésekkel sikeresen összekötöttük a Wave-et az OpenGL-lel, gratulálok magunknak!

A nehezén túl vagyunk, jöhet az OpenGL inicializálás. Itt már minden magáért beszél, nem ilyen EGL-szerű krikszkraksz. Aki már használt OpenGL-t, biztosan észrevette, hgoy a függvények gl-lel kezdődnek, sőt, saját típusok is vannak, amik mind emlékeztetnek standard C/C++ típusokra, például GLint, GLfloat, stb. Ennek a magyarázata, hogy a szokványos típusok máshogy viselkednek eltérő architektúrákon, például 32 és 64 bit esetén. Ezért az OpenGL-t hegesztő srácok megalkották a saját típusaikat, hogy az alkalmazás hordozhatóságával ne legyen gond, ne lassuljon be az app a konvertálások miatt. Csapjunk bele a tutiba: első lépésként állítsuk be a háttérszínt a glClearColor függvénnyel. Ez négy változót vár, RGBA módban adhatjuk meg a színt. A fehér az 1,1,1, valamint a fekete a csupa 0. Ezt követően állítsuk be a képernyőnk méreteit a glViewport-tal: az első két paraméter a bal alsó koordináták (azaz 0,0), az utolsó kettő a jobb felső sarok, azaz a kijelzőnk maximális mérete (ez lekérdezhető, nem kell fejből tudnunk). Következőnek állítsuk be a nézeti transzformációt. Itt két fajtából választhatunk, az egyik az ortografikus, a másik a perspektívikus. Az ortografikust a glOrtho függvénnyel állíthatjuk be, a lényege, hogy az objektumaink megtartják az egymáshoz viszonyított méreteiket, egy 2D-s játék esetében javasolt. Mi a perspektívikus torzulással megáldott módot preferáljuk. A perspektívikus torzulás lényege, hogy a távolság változásával eltérőnek látjuk a tárgyak méreteit. Úgy lehet a legegyszerűbben megfigyelni, ha beállunk a sínek közé (de figyeljünk a vonatra), és látni fogjuk, hogy a sínek egyre inkább összetartanak, de legbelül tudjuk, hogy párhuzamosak. Mivel 3D-s programot írunk, ez utóbbit fogjuk használni. Ehhez viszont be kell állítani az úgynevezett frustum paramétereit. Ez a látható területet írja le, a megadása hosszadalmas és nem egyértelmű (olvastam például olyat, ahol a srác rosszul állította be és az x tengelye az ellentétes irányba futott, Déscartes forgott is a sírjában, mint egy motolla), viszont OpenGL-ben létezik egy glu nevő library, ahol ez egy paranccsal megadható (gluPerspective). Az ottani függvényt egy kis átírással és ctrl-c-vel megspékelve beilleszthetjük a kódunkba, így nem kell trigonometrikus egyenletekkel kiszámolni a koordinátákat, három paraméter megadásával ugyanott vagyunk: egy látószög (például 45°), az elülső levágás és a hátsó levágás (részletek az első OpenGL-es bejegyzésben).

A következő hasznos glu nyúlás a gluLookAt, ezt az előzőhöz hasonló módszerrel szerezhetjük be. Segítségével megadhajtuk a kamera irányvektorát, hat paramétert beírva teljesíti minden ilyen irányú kívánságunkat, melyek a következők: az irányvektor kiindulási- és végpont-koordinátái, valamint az úgynevezett up-vektor koordinátái. Ez utóbbi egy kis magyarázatra szorul: rajta keresztül beállítható, hogy milyen vektor mentén legyen a “felfele-irány”. Ugye ez a valóságban megfelel a (0,1,0) vektornak, itt is ezt fogjuk használni, viszont kameratranszformációhoz jól jöhet, ha nem kell összevissza csavarni a mátrixokat, hanem egy vektor segítségével belőhetjük a kívánt irányt. Túl vagyunk a viewporton, jöhetnek a modellezés-specifikus beállítások: állítsuk be az árnyalási modellt (glShadeModel()) GL_SMOOTH-ra, így Goraud-árnyalást kapunk (erről majd pár bejegyzés múlva), majd engedélyezzük az úgynevezett culling-ot (glEnable(GL_CULL_FACE)). Ez utóbbi miatt azokat a háromszögeket, amelyek az adott pozícióban nem látszanak, nem rendereli ki a GPU, ez által nagy teljesítménynövekedést érhetünk el. Következhet a mélység-tesztelés beállítása (egyesek z-tesztnek csúfolják). Ha jó helyre billentjük ezt a bitet (értsd: be), az OpenGL kezelni fogja az egymásba lógó objektumokat, azaz csak a kamerához legközelebbi pontot rendereli ki, á lá raytrace. Ezeken kívül létezik még pár hasznos bebillentendő bit (fények, normálvektorok), de a mai bejegyzés szempontjából ezek érdektelenek. Az elkészített függvényt szintén az OnAppInitializing-ben helyezzük el.

Itt az idő, hogy rajzoljuk is valamit, durranjunk hát be! 3D-ben mozgunk, így három koordinátát használunk. Érdemes ezért létrehozni egy Vector (vagy Vertex vagy Pixel) osztályt, ami három float-ot tartalmaz, ezek az egyes koordinátákat reprezentálják, így rögtön átláthatóbbá válik a kódunk. A függvényünk elején ki kell tisztítani az előző képkocka megrajzolása miatt hátrahagyott nyomokat, hívjunk is rögtön egy glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)-et. Mint már írtam, az OpenGL két mátrixot használ, a modellezéshez nekünk a modelview mátrixra lesz szükségünk. A mátrixok additivitási tulajdonsága miatt minden egyes képkocka rajzolásánál érdemes egységmátrix-szá alakítanunk a modelview-t, ezt a glLoadIdentity segítségével tehetjük meg. Kezdjük a rajzolást egy háromszöggel, de előtte adjuk meg a színét a glColor3f-fel, mondjuk pirosra (1,0,0). Mivel az OpenGL egy állapotgép, amíg meg nem változtatjuk a színt újra, piros lesz az összes objektumunk. Vegyük fel a háromszög pontjainak koordinátáit egy GLfloat tömbbe, majd engedélyezzük a vertextömb-alapú rajzolást a glEnableClientState(GL_VERTEX_ARRAY) paranccsal. Sokan már az OpenGL inicializálásakor engedélyezik, én inkább biztosra megyek, és csak akkor, amikor ténylegesen rajzolok. Ezután etessük meg a tömbünket a glVertexPointer függvénnyel, így a következő rajzolásnál ezeket a pontokat fogjuk használni. A függvény paraméterei egy kis magyarázatot igényelnek: az első paraméter megmondja, hogy hány dimenziósak a pontjaink (jelen esetben 3), a második a koordináták típusa (GL_FLOAT), a harmadik pedig az offszet, mely   egy hasznos paraméter: ha a későbbi projektjeinknél egy tömbben szeretnénk tárolni például egy objektum pontjait és normálvektorait, megadhatunk egy eltolást, és az ennek megfelelő értékeket olvassa csak be. A negyedik paraméter egy referencia a tömbünkre. Nosza, rajzoljuk is ki: hívjuk meg a glDrawArrays függvényt. Ennek először meg kell mondani, hogy milyen módszerrel rajzoljon (lásd kép), állítsuk GL_TRIANGLES-re, a második és harmadik param a tömbből felhasznált pontok intervalluma, nálunk 0 és 3.


Az eltérő háromhálórajzoló algoritmusokkal más-más eredményt kapunk

A függvényünk végén hívjuk meg a eglSwapBuffers függvényt a dupla bufferelés (apropó dupla bufferelés: ez az algoritmus a képernyő villódzása miatt született: lényege: a videokártya a képernyő helyett egy memóriában lévő képre rajzol, és csak akkor cseréli le a kijelzőnkön lévőt, ha teljesen kész a rajzolással) miatt, majd futtassunk. A kapott eredmény: egy háromszög a megfelelő koordinátákon, nem túl impresszív, rögtön be is ugrik egyik kedves fórumozónk kérdése: ebből hogy lesz autós játék? Hát, valahol el kell kezdeni.

Következő lépés, a tömbünkhöz vegyünk hozzá egy negyedik pontot, és a glDrawArrays rajzolási módszerét állítsuk át GL_TRIANGLE_STRIPS-re. A futtatás után egy négyszöget láthatunk. Már látható, hogyan kell háromszögekből felépíteni a hálónkat, de ezek kezelése így elég nehézkes, nézzünk hát meg egy kicsit felhasználóbarátabb módszert! Egy komplexebb objektumban nem ritka, hogy egy vertexet többször is felhasználunk, emiatt sokszor redundánsan kell deklarálni. Ezt küszöbölhetjük ki az indexelt rendereléssel, amikor a vertextömbünkhöz elkészítünk egy indexekből (a vertexek “sorszámai”) álló tömböt, amely megadja, mely pontokat kell egymás után használni. Ezt egyszerűen, a glDrawElements paranccsal hozzáköthetjük a rajzoló-algoritmushoz. A rajzolásban segítségünkre van három transzformációs függvény: a glTranslatef segítségével eltolhatjuk az objektumainkat, a glRotatef-fel egy adott szöggel forgathatunk egy vektor körül, a glScalef segítségével pedig x, y és z irányú skálázást végezhetünk, ezek mindegyikét érdemes kipróbálgatni, hogyan is működnek.

A három bemutatott példakód eredménye
A három bemutatott példakód eredménye

Ezzel a végére is értünk a mai bejegyzéshez, a példakód innen letölthető, az egyes session-ok kommentezve vannak, ezek állítgatásával tudjátok kipróbálni őket. A lookAt és perspective függvények miatt pedig dícséret illeti a srácokat a mesa3d-nél, (www.mesa3d.org), sok portolás található itt, kevés változtatással rá lehet erőszakolni őket badára is. A következő posztban a gömbtesszellálás lesz a terítéken.

holdmester

Azóta történt

Előzmények