„Számítógépes grafika házi feladat tutorial” változatai közötti eltérés
| 10. sor: | 10. sor: | ||
== Az OpenGL és GLUT alapjai == | == Az OpenGL és GLUT alapjai == | ||
=== Az OpenGL === | === Az OpenGL === | ||
* Az OpenGL (Open Graphics Library) egyszerű térbeli alakzatok (primitívek), pl.: pontok, vonalak, háromszögek rajzolására specializálódott. | * Az OpenGL (Open Graphics Library) egyszerű térbeli alakzatok (primitívek), pl.: pontok, vonalak, háromszögek rajzolására specializálódott. Ezekből az építőkockákból csodákat lehet művelni, a mai számítógépes játékok nagyrészt hárszömszögek rajzolásából építkeznek. A primitíveket nagyon sokféleképpen és nagyon hatékonyan lehet az OpenGL-lel kirajzolni, de ezen kívül mást nem tud, pl. képek betöltéséhez külső könyvtárat kell használnod, nem tud árnyékokat számolni, de még egy kocka kirajzolásához is "küzdeni" kell. | ||
* A hatékonyság növelése érdekében az OpenGL a videókártyát is használja a rajzoláshoz. | |||
* Egy rajzolófüggvény viselkedése több száz paramétertől függ, persze nem kell az összeset függvény argumentumként átadni, ehelyett az '''OpenGL egy állapotgépen alapszik'''. Ha valamit átállítasz (pl. a rajzolószínt), akkor az onnantól kezdve minden rajzolás-hívást befolyásol. | |||
* A legtöbb OpenGL függvényt több különböző típusú paraméterrel is meg lehet hívni. Viszont az OpenGL egy C könyvtár, amiben nincs függvény overload. Ennek kiküszöbélésére a függvények neve Hungarian Notation szerűen a név végén tartalmaz pár karaktert, ami a paraméterekre utal, pl.: glVertex3f() - 3f = 3 db float, glTexCoord2iv() - 2iv = 2 elemű int vector (azaz pointer egy tetszőleges memóriaterületre, ahol 2 db int van egymás után). | |||
* A legtöbb OpenGL függvényt több különböző típusú paraméterrel is meg lehet hívni | |||
* Az OpenGL elnevezési konvenciója: | * Az OpenGL elnevezési konvenciója: | ||
** A függvények neve mindig kisbetűvel kezdődik, a gl, glu vagy glut szavakkal, attól függően hogy melyik függvénykönyvtárban található a függvény | ** A függvények neve mindig kisbetűvel kezdődik, a gl, glu vagy glut szavakkal, attól függően hogy melyik függvénykönyvtárban található a függvény | ||
** Ha több szóból áll, azokat egybeírjuk, és a szavakat nagy betűvel kezdjük (camelCase). | ** Ha több szóból áll, azokat egybeírjuk, és a szavakat nagy betűvel kezdjük (camelCase). pl.: glDrawElementsInstancedBaseVertexBaseInstance() - amúgy ez a leghosszabb nevű OpenGL függvény, de a tárgyból nincs rá szükség. | ||
** A függvényeknek lehetnek változatai különböző tulajdonságok alapján, ilyenkor ez a tulajdonság megjelenik a névben is. | |||
* A grafikában a lebegőpontos számokkal float alakban szeretünk dolgozni. A double-el nem az a gond, hogy kétszer annyi helyet foglal, hanem hogy a double pontosságú műveletvégzés sokkal lassabb, mint ha megelégednénk a float által elvárt pontossággal. Ez az oka annak, hogy a videókártyák, kb 2011-ig csak floatokkal tudtak számolni (double-el csak szoftveresen emulálva, nagyjából 25-ször olyan lassan). Az OpenGL-nek az a verziója, amit a tárgyban kell használni (1.1), csak floatokkal tud dolgozni a videókártyán, ami azt jelenti, hogy ha double-t adsz neki, attól nem csak, hogy nem lesz pontosabb, de még plusz munkát is adsz neki ezzel, mert neki vissza kell kasztolni a számot floattá, és ez a kasztolás nagyon nincs ingyen. A floatok használatának egy további előnye, hogy az x86 processzorok 4 db float műveletet tudnak egyszerre elvégezni SSE-vel. Ez az oka annak, hogy a tárgyból a legtöbb függvénynek csak a floatot váró alakját lehet használni, például a glVertex3f-et lehet, de a glVertex3d-t nem. | * A grafikában a lebegőpontos számokkal float alakban szeretünk dolgozni. Az esetek többségében a float és double használatával előállított kép között nem lehet szabad szemmel észrevenni a különbséget, viszont a teljesítményben elég jelentős eltérések adódhatnak. A double-el nem az a gond, hogy kétszer annyi helyet foglal, hanem hogy a double pontosságú műveletvégzés sokkal lassabb, mint ha megelégednénk a float által elvárt pontossággal. Ez az oka annak, hogy a videókártyák, kb 2011-ig csak floatokkal tudtak számolni (double-el csak szoftveresen emulálva, nagyjából 25-ször olyan lassan). Az OpenGL-nek az a verziója, amit a tárgyban kell használni (1.1), csak floatokkal tud dolgozni a videókártyán, ami azt jelenti, hogy ha double-t adsz neki, attól nem csak, hogy nem lesz pontosabb, de még plusz munkát is adsz neki ezzel, mert neki vissza kell kasztolni a számot floattá, és ez a kasztolás nagyon nincs ingyen. A floatok használatának egy további előnye, hogy az x86 processzorok 4 db float műveletet tudnak egyszerre elvégezni SSE-vel. Ez az oka annak, hogy a tárgyból a legtöbb függvénynek csak a floatot váró alakját lehet használni, például a glVertex3f-et lehet, de a double-t váró alakot, a glVertex3d-t nem. | ||
* Az OpenGL RGB színskálán állítja elő a képet, és neki is RGB értéket kell adni, ha egy színt akarunk leírni. A grafikában általában nem a megszokott komponensenként egy byte-on (0, 255) specifikáljuk a színeket. Ezzel alapvetően az baj, hogy a színhez tartozó fényerősség csak nagyon kis tartományon változhat, ahhoz képest amekkora különbségek a valóságban előfordulnak, pl. a Nap színe, vagy éjszaka egy sötét szobának a színei között több mint 10000-szeres fényerősségbeli különbség van. A másik gond, hogy a byte színekkel nehéz műveletet végezni, pl. a valóságban két fehér fény összege egy még fényesebb fehér, míg az egy byte-on leírt színeknél a fehér már önmagából a lehető legvilágosabb szín, amit meg tudunk jeleníteni. Ennek az orvoslására a színeket komponensenként floatokkal írjuk le. Nem véletlen egybeesés, hogy a megvilágítást a videókártya számolja az OpenGL-ben, ami mint tudjuk, floatokkal szeret dolgozni. Egy fényt két dolog is jellemez, az egyik a színe (hullámhossza), ami jelen esetben az RGB komponensek aránya. Ha csak ezt akarjuk megadni, akkor a komponenseket a (0, 1) tartományon írjuk le. De a fényt jellemzi még az erőssége (a fotonok száma) is, ami független magától a színtől. A fényerősség tetszőlegesen nagy, vagy kicsi, | * Az OpenGL RGB színskálán állítja elő a képet, és neki is RGB értéket kell adni, ha egy színt akarunk leírni. A grafikában általában nem a megszokott komponensenként egy byte-on (0, 255) specifikáljuk a színeket. Ezzel alapvetően az baj, hogy a színhez tartozó fényerősség csak nagyon kis tartományon változhat, ahhoz képest amekkora különbségek a valóságban előfordulnak, pl. a Nap színe, vagy éjszaka egy sötét szobának a színei között több mint 10000-szeres fényerősségbeli különbség van. A másik gond, hogy a byte színekkel nehéz műveletet végezni, pl. a valóságban két fehér fény összege egy még fényesebb fehér, míg az egy byte-on leírt színeknél a fehér már önmagából a lehető legvilágosabb szín, amit meg tudunk jeleníteni. Ennek az orvoslására a színeket komponensenként floatokkal írjuk le. Nem véletlen egybeesés, hogy a megvilágítást a videókártya számolja az OpenGL-ben, ami mint tudjuk, floatokkal szeret dolgozni. Egy fényt két dolog is jellemez, az egyik a színe (hullámhossza), ami jelen esetben az RGB komponensek aránya. Ha csak ezt akarjuk megadni, akkor a komponenseket a (0, 1) tartományon írjuk le. De a fényt jellemzi még az erőssége (a fotonok száma) is, ami független magától a színtől. A fényerősség tetszőlegesen nagy, vagy kicsi, sőt, grafikában akár még negatív is lehet, speciális hatások eléréséhez. Ha egy fényforrást akarunk leírni, akkor a szín és a fényerősség szorzatára vagyunk kíváncsiak, a (-végtelen, végtelen) tartományon komponensenként (erre a sugárkövetésnél lesz szükség), de ha az OpenGL-nek akarunk megadni egy színt, akkor azt a (0, 1), esetleg a (-1, 1) tartományon tegyük. Technikailag byte-ot is lehet adni az OpenGL-nek, de pedagógia okokból a házikban kötelező float színeket használni. | ||
* C++-ban a beépített típusok mérete nem fix. Ezt sajnos az OpenGL nem nagyon veszi figyelembe, ő pl. int alatt mindig 32 bites intet ért. Viszont definiál nekünk jó pár makrót, pl. a GLint-et, ami minden architektúrán garantáltan olyan méretű lesz, mint ami neki kell. Ezeknek a típusoknak az elnevezési konvenciója egyszerű: GL{u}típusnév. Az 'u' betű az unsignedra utal, a GLushort az megegyezik a uint16_t-vel, és általában az unsigned short-tal is. Egy kivétel van az elnevezési konvencióba: nincs GLchar, helyett GLbyte meg GLubyte van. Ezeket a típusokat nagyon ajánlott használni, főleg ha az adatunkat cím szerint adjuk át, például tömbök esetén. | * C++-ban a beépített típusok mérete nem fix. Ezt sajnos az OpenGL nem nagyon veszi figyelembe, ő pl. int alatt mindig 32 bites intet ért. Viszont definiál nekünk jó pár makrót, pl. a GLint-et, ami minden architektúrán garantáltan olyan méretű lesz, mint ami neki kell. Ezeknek a típusoknak az elnevezési konvenciója egyszerű: GL{u}típusnév. Az 'u' betű az unsignedra utal, például a GLushort az megegyezik a uint16_t-vel, és általában az unsigned short-tal is. Egy kivétel van az elnevezési konvencióba: nincs GLchar, helyett GLbyte meg GLubyte van. Ezeket a típusokat nagyon ajánlott használni, főleg ha az adatunkat cím szerint adjuk át, például tömbök esetén. | ||
* Az OpenGL csak a rajzolással foglalkozik, az, hogy hogyan jön létre az a valami (célszerűen egy ablak), amire ő tud rajzolni, az viszont már nem az ő dolga. Itt jön a képbe a GLUT. | * Az OpenGL csak a rajzolással foglalkozik, az, hogy hogyan jön létre az a valami (célszerűen egy ablak), amire ő tud rajzolni, az viszont már nem az ő dolga. Itt jön a képbe a GLUT. | ||
| 30. sor: | 26. sor: | ||
* A GLUT (OpenGL Utility Toolkit) egy platformfüggetlen ablak- és eseménykezelő, lényegében egy híd az oprendszer és az OpenGL context között. A GLUT beállításának nagy része a keretben előre meg van írva, csak az eseménykezelő függvényekről kell gondoskodnunk, amiket majd a GLUT meghív (ezek a függvények határozzák meg, hogy mit csinál a programunk). | * A GLUT (OpenGL Utility Toolkit) egy platformfüggetlen ablak- és eseménykezelő, lényegében egy híd az oprendszer és az OpenGL context között. A GLUT beállításának nagy része a keretben előre meg van írva, csak az eseménykezelő függvényekről kell gondoskodnunk, amiket majd a GLUT meghív (ezek a függvények határozzák meg, hogy mit csinál a programunk). | ||
* GLUT eseménykezelő függvények: | * GLUT eseménykezelő függvények: | ||
** <code>onDisplay()</code> - a legfontosabb függvény, ide írjuk a képernyő törlését, majd a szükséges rajzoló részeket. Ha valami változás hatására frissíteni szeretnénk a képernyőt, azaz szeretnénk az <code>onDisplay()</code>-t lefuttatni, hívjuk meg a <code>glutPostRedisplay()</code> függvényt (ne közvetlenül az <code>onDisplay()</code>-t!). Fontos hogy az '''<code>onDisplay()</code>-en belül tilos meghívni a <code>glutPostRedisplay()</code>-t,''' az így megírt program elvi hibás (a képernyő mindig érvénytelen marad), ez a beadón nem | ** <code>onDisplay()</code> - a legfontosabb függvény, ide írjuk a képernyő törlését, majd a szükséges rajzoló részeket. Ha valami változás hatására frissíteni szeretnénk a képernyőt, azaz szeretnénk az <code>onDisplay()</code>-t lefuttatni, hívjuk meg a <code>glutPostRedisplay()</code> függvényt (ne közvetlenül az <code>onDisplay()</code>-t!). Fontos hogy az '''<code>onDisplay()</code>-en belül tilos meghívni a <code>glutPostRedisplay()</code>-t,''' az így megírt program elvi hibás (a képernyő mindig érvénytelen marad), és annak ellenére, hogy ez nálad valószínűleg működni fog, a beadón sajnos nem. | ||
** <code>onInitialization()</code> - | ** <code>onInitialization()</code> - inicializálós rész, pl. globális változók inicializálására. Tipikus hiba, hogy a globális változóknak egy gl/glu/glut függvény visszatérési értéket adjuk, vagy a változó konstruktorában meghívunk ilyen függvényt. Így ugyanis még a main() kezdete előtt futattnánk le egy ilyen típusú függvényt, amikor még ezek a könyvtárak nincsenek inicializálva. Ennek az elkerülésére van ott az <code>onInitialization()</code> - ebben már nyugodtan használhatunk bármilyen függvényt. | ||
** <code>onKeyboard()</code> - Itt tudjuk kezelni egy billentyű lenyomását. Erre a házikban általában csak minimális szükség van. | ** <code>onKeyboard()</code> - Itt tudjuk kezelni egy billentyű lenyomását. Ez az esemény nem csak akkor generálódik, amikor a billentyűt éppen lenyomtuk, hanem akkor is, ha egy lenyomott betű hatására egy újabb karaktert gépelnénk be. Erre a házikban általában csak minimális szükség van. | ||
** <code>onMouse()</code> - Itt kapunk értesítést arról, ha valamelyik egérgomb állapota megváltozott, és azt is megtudjuk, hogy az ekkor az egér az ablak koordinátái szerint | ** <code>onKeyboardUp()</code> - Itt tudjuk kezelni egy billentyű felengedést. Valós játékokban ez nagyon hasznos tud lenni, de házikhoz ezt szinte soha nem kell használni. | ||
** <code>onMouse()</code> - Itt kapunk értesítést arról, ha valamelyik egérgomb állapota megváltozott, és azt is megtudjuk, hogy az ekkor az egér az ablak koordinátái szerint hol volt. | |||
** <code>onMouseMotion()</code> - Itt tudjuk meg, ha a felhasználó lenyomott egér gomb mellett mozgatta az egeret. A koordinálta értékére ugyan az vonatkozik, mint az onMouse esetén. | ** <code>onMouseMotion()</code> - Itt tudjuk meg, ha a felhasználó lenyomott egér gomb mellett mozgatta az egeret. A koordinálta értékére ugyan az vonatkozik, mint az onMouse esetén. | ||
** <code>onIdle()</code> - Ez a függvény az idő múlását hivatott jelezni, így itt kell kezelni mindent ami az időtől függ (animáció). | ** <code>onIdle()</code> - Ez a függvény az idő múlását hivatott jelezni, így itt kell kezelni mindent ami az időtől függ (animáció). | ||