„Számítógépes grafika házi feladat tutorial” változatai közötti eltérés

Rohamcsiga (vitalap | szerkesztései)
Rohamcsiga (vitalap | szerkesztései)
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.
** 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 hatékonyság növelése érdekében az OpenGL a videókártyát is használja a rajzoláshoz.  
** A primitíveket nagyon sokféleképpen és nagyon hatékonyan lehet az OpenGL-lel kirajzolni, de ezen kívül semmi mást nem tud, nem tud képet betölteni, árnyékokat számolni, de még egy kocka kirajzolásához is "küzdeni" kell.
* 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 hatékonyság növelése érdekében az OpenGL a videókártyát is használja a rajzoláshoz.  
* 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).  
** 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ép'''-en 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.: 3f - 3 db float, 2iv - 2 elemű int vector (azaz pointer egy tetszőleges memóriaterületre, ahol 2 db int van egymás után)
** pl.: glVertex3f()
* 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.
*** 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, de akár még negatív is lehet. 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.
* 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 fog működni.  
** <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> - inicializáló 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 az inicializációhoz.
** <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 (figyelem itt bal felül van az origó!) hol volt.
** <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ó).