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

Rohamcsiga (vitalap | szerkesztései)
 
(63 közbenső módosítás, amit 5 másik szerkesztő végzett, nincs mutatva)
19. sor: 19. sor:
** 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 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. 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.
* 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, 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. De még ha lehetne se ajánlanám a 'char' szín használatát, rengeteg szívás van vele. Az egyik nyilvánvaló probléma, hogy a 'char'-nak az előjele nem definiált, mindenképpen 'unsigned char'-t kell használnunk, illetve pl. a glColor3ub helyett könnyű glColor3b-t írni és máris teljesen mást csinál a program. Ami viszont még fontosabb, hogy a karakterként átadott számot az OpenGL egy fix-pontos értékként kezeli, ezért az, hogy 255 értékhez melyik szín tartozik, az platformfüggő, ahol a byte 8 bit, ott fehér, ahol a byte 9 bit ott szürke, egy beágyazott rendszeren, ahol a byte 36 bit, ott meg gyakorlatilag fekete. Hogy a kódunk hordozható legyen, egy x értékből csak a x * numeric_limits<unsigned char>::max() / 255 kifejezésessel tudunk színt előállítani, ami sokkal rondább, mint ha floatot használtunk volna.
* 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 (az egy bytehoz képest) nem fix. Ezt sajnos az OpenGL nem nagyon veszi figyelembe, ő pl. int alatt mindig 4 byteos 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 olyan gépeken, ahol a bájt 8 bit, 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 (az egy bytehoz képest) nem fix. Ezt sajnos az OpenGL nem nagyon veszi figyelembe, ő pl. int alatt mindig 4 byteos 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 olyan gépeken, ahol a bájt 8 bit, 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.
31. sor: 31. sor:
** <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>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>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 ugyanaz 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ó).


== Az első házihoz szükséges elmélet ==
== 2D OpenGL ==


=== Rajzolás az OpenGL segítségével ===
=== Rajzolás az OpenGL segítségével ===


Az OpenGL néhány csak néhány típusú primitívet tud rajzolni, ezekből kell építkeznünk. A típusok:
Az OpenGL csak néhány típusú primitívet tud rajzolni, ezekből kell építkeznünk. A típusok:
* Pontok: <code>GL_POINTS</code>
* Pontok: <code>GL_POINTS</code>
* Vonalak: <code>GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP</code>
* Vonalak: <code>GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP</code>
100. sor: 100. sor:
   glVertex2f(center_x, center_y);
   glVertex2f(center_x, center_y);


   for(int i = 0; i <= CIRCLE_RESOLUTION; i++) {
   for(int i = 0; i < CIRCLE_RESOLUTION; i++) {
     float angle = float(i) / CIRCLE_RESOLUTION * 2.0f * M_PI;
     float angle = float(i) / CIRCLE_RESOLUTION * 2.0f * M_PI;
     // Itt a kor paramtetrikus alakjat hasznaljuk: x = x0 + r*cos(t), y = y0 + r * sin(t)
     // Itt a kor paramtetrikus alakjat hasznaljuk: x = x0 + r*cos(t), y = y0 + r * sin(t)
114. sor: 114. sor:
   glVertex2f(center_x, center_y);
   glVertex2f(center_x, center_y);
    
    
   for(int i = 0; i <= CIRCLE_RESOLUTION; i++) {
   for(int i = 0; i < CIRCLE_RESOLUTION; i++) {
     float angle = float(i) / CIRCLE_RESOLUTION * 2.0f * M_PI;
     float angle = float(i) / CIRCLE_RESOLUTION * 2.0f * M_PI;
     glColor3f(0.0f, 0.5f + 0.5f*cos(angle), 0.5f + 0.5f*sin(angle));
     glColor3f(0.0f, 0.5f + 0.5f*cos(angle), 0.5f + 0.5f*sin(angle));
137. sor: 137. sor:


http://i.imgur.com/6yfh7q2.png
http://i.imgur.com/6yfh7q2.png


=== Eseménykezelés ===
=== Eseménykezelés ===
259. sor: 258. sor:


http://i.imgur.com/ezFQ4l4.png
http://i.imgur.com/ezFQ4l4.png
== A második házihoz szükséges elmélet ==


=== Koordináta rendszerek ===
=== Koordináta rendszerek ===


Az első háziba valószínűleg feltűnt, hogy a pontok NDC (normalizált eszköz koordináta) megadása nem túl kényelmes, még akkor se ha, a világnak mindig ugyan azt a részét nézzük. De mit tegyük akkor, ha a képzeletbeli kamera amivel "lefényképezzük" a jelenetet mozoghat, sőt akár még foroghat is. Az OpenGL kitalálóinak az ötlete az volt erre, hogy a kamera mindig maradjon egy helyben, de ha pl. balra akarnánk forgatni, akkor helyette inkább a világ forogjon jobbra, a kamera viszont maradjon ugyanott, ezzel is ugyan azt a hatást érjük el. Első ránézésre nem látszik, hogy ez miért jó, de ez valójában egy nagyon jó ötlet. Szerencsére ha a világot el kell forgatnunk, akkor nem kell minden egyes pontot nekünk külön-külön elforgatni egy ronda trigonometrikus képlet alapján, ezt rábízhatjuk az OpenGL-re is, hogy mindig mielőtt rajzolna, azelőtt végezzen el valamilyen transzformációt a pontokat amiket kapott.
Az korábbi példákban valószínűleg feltűnt, hogy a pontok NDC (normalizált eszköz koordináta) megadása nem túl kényelmes, még akkor se ha, a világnak mindig ugyanazt a részét nézzük. De mit tegyük akkor, ha a képzeletbeli kamera amivel "lefényképezzük" a jelenetet mozoghat, sőt akár még foroghat is. Az OpenGL kitalálóinak az ötlete az volt erre, hogy a kamera mindig maradjon egy helyben, de ha pl. balra akarnánk forgatni, akkor helyette inkább a világ forogjon jobbra, a kamera viszont maradjon ugyanott, ezzel is ugyanazt a hatást érjük el. Első ránézésre nem látszik, hogy ez miért jó, de ez valójában egy nagyon jó ötlet. Szerencsére ha a világot el kell forgatnunk, akkor nem kell minden egyes pontot nekünk külön-külön elforgatni egy ronda trigonometrikus képlet alapján, ezt rábízhatjuk az OpenGL-re is, hogy mindig mielőtt rajzolna, azelőtt végezzen el valamilyen transzformációt a pontokat amiket kapott.


A grafikában általában affin (egyenestartó) transzformációkat szoktunk használni, a vetítéseket leszámítva. Ezeket a transzformációkat a legkényelmesebb a mátrixuk segítségével tudjuk megadni, több egymás utáni transzformáció pedig egyszerűen a mátrixok szorzatát jelenti. Viszont fontos megjegyezni, hogy 3D-ben az eltolás nem lineáris transzformáció, és nem is lehet mátrixszal felírni. Pedig erre a műveletre biztos, hogy szükségünk lesz. Ennek kiküszöbölésére használhatunk 4D-be 'w' = 1 koordinátával beágyazott 3D-s koordináta-rendszert, ahol az eltolás is egy lineáris trafó.
A grafikában általában affin (egyenestartó) transzformációkat szoktunk használni, a vetítéseket leszámítva. Ezeket a transzformációkat a legkényelmesebb a mátrixuk segítségével tudjuk megadni, több egymás utáni transzformáció pedig egyszerűen a mátrixok szorzatát jelenti. Viszont fontos megjegyezni, hogy 3D-ben az eltolás nem lineáris transzformáció, és nem is lehet mátrixszal felírni. Pedig erre a műveletre biztos, hogy szükségünk lesz. Ennek kiküszöbölésére használhatunk 4D-be 'w' = 1 koordinátával beágyazott 3D-s koordináta-rendszert, ahol az eltolás is egy lineáris trafó.
309. sor: 305. sor:
* <code> glScalef(GLfloat x, GLfloat y, GLfloat z); </code>
* <code> glScalef(GLfloat x, GLfloat y, GLfloat z); </code>


A projekciós mátrixot állító függvényekkel ellentétben, ezeket akkor is szoktuk használni, ha a modellezési mátrix nem egységmátrix. Ezen függvényeknek tetszőleges kombinációját lehet használni, de a sorrend nem mindegy. Egy transzformáció meghívásakor annak a mátrixa hozzászorzódik a GL_MODELVIEW mátrixhoz (balról). Emlékeztető: a mátrix szorzás szorzás asszociatív. Ez azt jelenti, hogy két transzformációs mátrix összeszorzása után az eredmény ugyan úgy transzformál egy tetszőleges vektort, mint ha a két mátrixszal külön szoroztuk volna be.
A projekciós mátrixot állító függvényekkel ellentétben, ezeket akkor is szoktuk használni, ha a modellezési mátrix nem egységmátrix. Ezen függvényeknek tetszőleges kombinációját lehet használni, de a sorrend nem mindegy. Egy transzformáció meghívásakor annak a mátrixa hozzászorzódik a GL_MODELVIEW mátrixhoz (balról). Emlékeztető: a mátrix szorzás szorzás asszociatív. Ez azt jelenti, hogy két transzformációs mátrix összeszorzása után az eredmény ugyanúgy transzformál egy tetszőleges vektort, mint ha a két mátrixszal külön szoroztuk volna be.


A transzformációk fordított sorrendben fejtik ki hatásukat, mint ahogy meghívjuk őket, de ez így intuitív, így haladhatunk a hierarchiában föntről lefele, ha nem így lenne, akkor pl. egy autó kirajzolásánál, azzal kéne kezdenünk, hogy megmondjuk, hogy a dísztárcsa a kerékhez képest hogy helyezkedik el, és csak a legvégén mondhatnánk meg, hogy egyáltalán hol van az az autó, aminek a részeiről eddig beszéltünk.
A transzformációk fordított sorrendben fejtik ki hatásukat, mint ahogy meghívjuk őket, de ez így intuitív, így haladhatunk a hierarchiában föntről lefele, ha nem így lenne, akkor pl. egy autó kirajzolásánál, azzal kéne kezdenünk, hogy megmondjuk, hogy a dísztárcsa a kerékhez képest hogy helyezkedik el, és csak a legvégén mondhatnánk meg, hogy egyáltalán hol van az az autó, aminek a részeiről eddig beszéltünk.
342. sor: 338. sor:
Tanulság: általában az eltolás - forgatás - nagyítás sorrendet szeretjük. Ez nem jelenti azt, hogy más sorrendnek ne lenne értelme, vagy egy konkrét problémának ne lehetne egyszerűbb megoldása másmilyen sorrendet használva.
Tanulság: általában az eltolás - forgatás - nagyítás sorrendet szeretjük. Ez nem jelenti azt, hogy más sorrendnek ne lenne értelme, vagy egy konkrét problémának ne lehetne egyszerűbb megoldása másmilyen sorrendet használva.


De egy probléma még felmerül, a transzformációk hatása permanens, azaz, ha egyszer elforgattad a világot, akkor az úgy marad, amíg vissza nem forgatod. Tehát ha egy objektum kirajzolása miatt akarsz használni egy transzformációt akkor a rajzolás után azt mindenképpen, mindig vissza is kell csinálnod. De mi van ha egy összetett objektum kirajzolásához akár több száz transzformáció is kellet? Akkor a végén az összeset egybe vissza kell csinálni? Nincs erre jobb megoldás? A válasz természetesen az, hogy van, ez a megoldás a mátrix stack.
De egy probléma még felmerül, a transzformációk hatása permanens, azaz, ha egyszer elforgattad a világot, akkor az úgy marad, amíg vissza nem forgatod. Tehát ha egy objektum kirajzolása miatt akarsz használni egy transzformációt akkor a rajzolás után azt mindenképpen, mindig vissza is kell csinálnod. De mi van ha egy összetett objektum kirajzolásához akár több száz transzformáció is kellet? Akkor a végén az összeset egyenként vissza kell csinálni? Nincs erre jobb megoldás? A válasz természetesen az, hogy van, ez a megoldás a mátrix stack.


=== Matrix stack ===
=== Matrix stack ===
404. sor: 400. sor:


<br/>
<br/>
A 3 dimenzió ennek a kódnak a felhasználásával csak annyiban fog különbözni a 2 dimenziótól, hogy van egy harmadik koordináta is. Az OpenGL kamera koordináta-rendszerében (és az általam definiált világ koordináta-rendszerben is) az X tengely továbbra is jobbra, az Y tengely pedig továbbra is felfele mutat, mint ahogy 2D-ben is, csak bejött egy új tengely, a Z ami felénk (a monitorból kifele) mutat.
Természetesen ez eltér attól a koordináta-rendszertől, amit matematikában szoktunk használni (X felénk, Y jobbra, Z felfele), ami valószínűleg sokakat zavarni fog.
Nekik annyi vigaszt tudok mondani, hogy a GL_MODELVIEW mátrix segítésével tetszőleges koordináta-rendszert definiálhattok magatoknak, ami akár megegyezhet azzal, amit matekórán használni szoktunk.
Én ennek ellenére a példaprogramokban nem fogok így tenni, aminek az egyik oka, hogy a példakódokban nem a konkrét vektorok értéken van a lényeg, hanem az ötleteken, és így legalább kisebb késztetést fogsz érezni, hogy rólam másolj. Másrészt meg az OpenGL-ben ezt a koordináta-rendszert "szokás" használni, ahol az Y mutat felfele. Ez főleg az árnyékoló programok világában fontos, ahol sokkal több koordináta-rendszerrel kell dolgozni, mint itt, és ott rengeteg hibalehetőséggel jár, ha mindegyik koordináta-rendszerben mást jelentenek a tengelyek.


Ezt felhasználva a példaprogram: [[Média:Grafpp_robot_kar.cpp|Robot kar]]
Ezt felhasználva a példaprogram: [[Média:Grafpp_robot_kar.cpp|Robot kar]]
535. sor: 539. sor:
=== Görbék ===
=== Görbék ===


Görbék alatt grafikában olyan függvényeket értünk, amik diszkrét ponthalmazból folytonos ponthalmazt állítanak elő. Például a Nyugatitól el akarunk jutni az egyetemig, de úgy, hogy közbe megadott sorrendben érinteni akarjuk a három kedvenc kocsmánkat. Az útvonal, amin ezt megtudjuk tenni, az egy görbe. Az öt helyet, amit érinteni akarunk, kontrollpontnak nevezzük a görbék esetében. Nem csak egy ilyen útvonal létezik, mint ahogy a különböző típusú görbéknek is lehet más a kimenete, ugyan az a bemenet mellett.
Görbék alatt grafikában olyan függvényeket értünk, amik diszkrét ponthalmazból folytonos ponthalmazt állítanak elő. Például a Nyugatitól el akarunk jutni az egyetemig, de úgy, hogy közbe megadott sorrendben érinteni akarjuk a három kedvenc kocsmánkat. Az útvonal, amin ezt megtudjuk tenni, az egy görbe. Az öt helyet, amit érinteni akarunk, kontrollpontnak nevezzük a görbék esetében. Nem csak egy ilyen útvonal létezik, mint ahogy a különböző típusú görbéknek is lehet más a kimenete, ugyanaz a bemenet mellett.


A görbéket szinte mindig valamilyen mozgás leírására használjuk. A görbe kimenete egy hely-idő pontpárokból álló halmaz (vagy ezzel ekvivalens egy folytonos függvény aminek az idő a paramétere, és a hely a visszatérési értéke), ami azt jelenti, hogy a görbéből még a sebesség és a gyorsulás is kiolvasható.
A görbéket szinte mindig valamilyen mozgás leírására használjuk. A görbe kimenete egy hely-idő pontpárokból álló halmaz (vagy ezzel ekvivalens egy folytonos függvény aminek az idő a paramétere, és a hely a visszatérési értéke), ami azt jelenti, hogy a görbéből még a sebesség és a gyorsulás is kiolvasható.
549. sor: 553. sor:
A görbéknek a képletét nem szokás fejből tudni, bár általában könnyen levezethetőek. Az a tapasztalat a korábbi házikból, hogy azoknak a görbéknek a képlete benne szokott lenni az előadásdiákba, amiket a házihoz használni kell. Sőt, amikor több görbét kell használni, akkor általában legalább az egyiknek pszeudokóddal meg is szokták adni az implementációját is.   
A görbéknek a képletét nem szokás fejből tudni, bár általában könnyen levezethetőek. Az a tapasztalat a korábbi házikból, hogy azoknak a görbéknek a képlete benne szokott lenni az előadásdiákba, amiket a házihoz használni kell. Sőt, amikor több görbét kell használni, akkor általában legalább az egyiknek pszeudokóddal meg is szokták adni az implementációját is.   
        
        
Példaprogram: [[Média:Grafpp_tcr_gorbe.cpp|Tenziós Catmull-Rom görbe]]
Példaprogram: ''<Törölve, túl sokan másolták>''


http://i.imgur.com/C1iKaHx.gif
http://i.imgur.com/C1iKaHx.gif


== A harmadik házihoz szükséges elmélet ==
== Sugárkövetés ==


A harmadik házinál a programod teljes mértékben a CPU-n fog futni, ami a videókártya segítsége nélkül nagyon meg fog izzadni, hogy a képet előállítsa neked. Az előző házikkal ellentétben itt az optimalizálás nagyon sokat segít, egy release build (-O3) és egy debug build (-O0) között akár több mint ötszörös sebességkülönbség is lehet. Ezért, ha éppen nem debuggolsz, mindenképpen release buildet fordíts.
A sugárkövetős házinál a programod teljes mértékben a CPU-n fog futni, ami a videókártya segítsége nélkül nagyon meg fog izzadni, hogy a képet előállítsa neked. Az előző feladatokkal ellentétben itt az optimalizálás nagyon sokat segít, egy release build (-O3) és egy debug build (-O0) között akár több mint ötszörös sebességkülönbség is lehet. Ezért, ha éppen nem debuggolsz, mindenképpen release buildet fordíts.


=== A Sugárkövetés alapjai ===  
=== A Sugárkövetés alapjai ===  


A második házihoz szükséges példaprogramok között volt egy 3D-s is. Most ezzel a témakörrel fogunk foglalkozni. Technikailag abban a példaprogramban a 3D rajzolást a GLUT csinálta helyettünk. De mielőtt belemennénk a részletekbe, hogy pontosan mit is csinált (ezt majd a 4. háziba), vegyük észre, mi is ki tudunk rajzolni egy olyan kockát, mint amit a GLUT csinált, akár az OpenGL segítsége nélkül is.
A 2D OpenGL-es példaprogramok között volt egy 3D-s is. Most ezzel a témakörrel fogunk foglalkozni. Technikailag abban a példaprogramban a 3D rajzolást a GLUT csinálta helyettünk. De mielőtt belemennénk a részletekbe, hogy pontosan mit is csinált (ezt majd a 3D OpenGL résznél), vegyük észre, mi is ki tudunk rajzolni egy olyan kockát, mint amit a GLUT csinált, akár az OpenGL segítsége nélkül is.


Először gondoljuk át hogy a valóságban hogyan csinálnánk képet egy kockáról. Szükségünk van egy fényforrásra, enélkül garantáltan nem látnánk semmit, és szükségünk van egy ernyőre is (pl: retina), amin a képet felfoghatjuk. Továbbá nem árt, ha van egy kocka is, amit lefényképezhetünk.
Először gondoljuk át hogy a valóságban hogyan csinálnánk képet egy kockáról. Szükségünk van egy fényforrásra, enélkül garantáltan nem látnánk semmit, és szükségünk van egy ernyőre is (pl: retina), amin a képet felfoghatjuk. Továbbá nem árt, ha van egy kocka is, amit lefényképezhetünk.
565. sor: 569. sor:
Ha pontosan azt akarnánk lemodellezni, ahogy a valóságban a kép keletkezik, akkor gondba lennénk, mert a számítógép teljesítményéhez képest gyakorlatilag végtelen fotonnal kéne dolgoznunk. És ráadásul a fényforrásból kiinduló fotonok döntő többsége még csak nem is megy az ernyőnek a közelébe se. Ezt kiaknázandó, a sugárkövetés egyik alapötlete, hogy az ernyőből induljuk ki, ne a fényforrásból, és megfordított irányú fotonokat kövessünk, így csak a releváns fény részecskékkel fogunk foglalkozni.
Ha pontosan azt akarnánk lemodellezni, ahogy a valóságban a kép keletkezik, akkor gondba lennénk, mert a számítógép teljesítményéhez képest gyakorlatilag végtelen fotonnal kéne dolgoznunk. És ráadásul a fényforrásból kiinduló fotonok döntő többsége még csak nem is megy az ernyőnek a közelébe se. Ezt kiaknázandó, a sugárkövetés egyik alapötlete, hogy az ernyőből induljuk ki, ne a fényforrásból, és megfordított irányú fotonokat kövessünk, így csak a releváns fény részecskékkel fogunk foglalkozni.


A másik alapötlet, hogy a fotonok olyan sokan vannak, hogy a Nagy Számok Törvénye alapján gyakorlatilag teljesen pontos becsléseket kaphatunk a fotonok viselkedéséről, anélkül, hogy azokkal egyesével foglalkoznunk kellene. Ezt felhasználva igazándiból nagy mennyiségű fotonból álló csomagok úgynevezett sugarak útját követjük, és nem fotonokét. Ez talán megmagyarázza, hogy miért hívjuk a technikát sugárkövetésnek. A sugárkövetéshez szükségünk van egy képzeletbeli kamerára, és egy síkra (téglalapra), amit ernyőként használhatunk. A téglalapot felosztjuk annyi egyenlő részre, ahány pixelből áll az ablakunk. Jelen esetben, 600x600-as ablak esetében ez azt jelenti, hogy a téglalap négyzet lesz. Ezek után az ablak minden egyes pixelére azt a színt rajzoljuk ki, amit a képzeletbeli kamera látna a téglalapnak a pixelhez tartozó részén keresztül.
A másik alapötlet, hogy a fotonok olyan sokan vannak, hogy a Nagy Számok Törvénye alapján gyakorlatilag teljesen pontos becsléseket kaphatunk a fotonok viselkedéséről, anélkül, hogy azokkal egyesével foglalkoznunk kellene. Ezt felhasználva nagy mennyiségű fotonból álló csomagok, úgynevezett sugarak útját követjük, és nem fotonokét. Ez talán megmagyarázza, hogy miért hívjuk a technikát sugárkövetésnek. A sugárkövetéshez szükségünk van egy képzeletbeli kamerára, és egy ernyőre (egy téglalapra). A téglalapot felosztjuk annyi egyenlő részre, ahány pixelből áll az ablakunk. Jelen esetben, 600x600-as ablak esetében ez azt jelenti, hogy a téglalap négyzet lesz(két egység élhosszúságú). Ezek után az ablak minden egyes pixelére azt a színt rajzoljuk ki, amit a képzeletbeli kamera látna az ernyőnek a pixelhez tartozó részén keresztül.


Az OpenGL használata nélkül ezt úgy kivitelezhetnénk, hogy képet mint egy színekből álló tömböt eltároljuk magunknak, abba renderelünk, majd valamilyen megfelelő kép formátumába kiírjuk ezt egy fájlba. Ezt a megoldást viszont nem lenne túl kényelmes használni. De az OpenGL-t is megkérhetjük arra, hogy jelenítse meg a képet, amit lerendereltünk a <code>glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)</code> függvény segítségével. A házikban tipikusan az utóbbi megoldást szoktuk használni. Például egy lehetséges megvalósítása:
Az OpenGL használata nélkül ezt úgy kivitelezhetnénk, hogy képet mint egy színekből álló tömböt eltároljuk magunknak, abba renderelünk, majd valamilyen megfelelő kép formátumába kiírjuk ezt egy fájlba. Ezt a megoldást viszont nem lenne túl kényelmes használni. De az OpenGL-t is megkérhetjük arra, hogy jelenítse meg a képet, amit lerendereltünk a <code>glDrawPixels(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels)</code> függvény segítségével. A házikban tipikusan az utóbbi megoldást szoktuk használni. Például egy lehetséges megvalósítása:
585. sor: 589. sor:
* Azt is tudnunk kell, hogy melyik iránynak felel meg a felfele ("What's up?"). Kódban pl nevezzük <code>up</code>-nak.
* Azt is tudnunk kell, hogy melyik iránynak felel meg a felfele ("What's up?"). Kódban pl nevezzük <code>up</code>-nak.
* Tegyük fel, hogy téglalap (vagy sík) egységnyi távolságra van a kamerától. Ekkora annak a középpontja: <code>pos + fwd</code>.
* Tegyük fel, hogy téglalap (vagy sík) egységnyi távolságra van a kamerától. Ekkora annak a középpontja: <code>pos + fwd</code>.
* Tudnunk kell még, hogy melyik irány van jobbra. Ezt az előre és a felfele pozícióból ki tudjuk számolni: <code>right = cross(fwd, up)</code>.
* Tudnunk kell még, hogy melyik irány van jobbra. Ezt az előre és a felfele pozícióból ki tudjuk számolni: <code>right = cross(fwd, up) // a cross-product a vektor szorzat angolul, a dot-product pedig a skalár szorzat. </code>
* A felfele vektor amit megadtunk nem biztos, hogy merőleges az előre vektorra, pedig nekünk olyanra van szükségünk. Pl: ha rézsútosan előre és lefele nézünk, de az 'up' vektor az ég fele mutat. Ez igazándiból nem baj, mert a jobbra és előre vektor ismeretében már ki tudjuk számolni a pontos felfele vektort: <code>up = cross(right, fwd)</code>.
* A felfele vektor amit megadtunk nem biztos, hogy merőleges az előre vektorra, pedig nekünk olyanra van szükségünk. Pl: ha rézsútosan előre és lefele nézünk, de az 'up' vektor az ég fele mutat. Ez valójában nem baj, mert a jobbra és előre vektor ismeretében már ki tudjuk számolni a pontos felfele vektort: <code>up = cross(right, fwd)</code>.
* Ha ezek megvannak, akkor ki kell tudnunk számolni, hogy egy (x, y) koordinátájú pixelnek a téglalap (ami most egy egység oldalhosszúságú négyzet) melyik része felel meg. Ezt így tehetjük meg:
* Ha ezek megvannak, akkor ki kell tudnunk számolni, hogy egy (x, y) koordinátájú pixelnek a téglalap (ami most egy 2 egység oldalhosszúságú négyzet) melyik része felel meg. Ezt így tehetjük meg:


<br/> <syntaxhighlight lang="c">  
<br/> <syntaxhighlight lang="c">  
Vector pos_on_plane = Vector(
Vector pos_on_plane = Vector(
   (x - Screen::width/2) / (Screen::width/2),
   (x - Screen::width/2) / (Screen::width/2),
  // Itt nem kell megfordítani az y tengelyt. A bal fölső sarok az origó most.
   (y - Screen::height/2) / (Screen::height/2),  
   (y - Screen::height/2) / (Screen::height/2),  
   0
   0
601. sor: 604. sor:
Vector plane_intersection = plane_pos + pos_on_plane.x * right + pos_on_plane.y * up;
Vector plane_intersection = plane_pos + pos_on_plane.x * right + pos_on_plane.y * up;
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>
Az előadásdiának az ide vágó ábrája talán segíti a megértést (a lookat-nek a plane_pos felel meg nálam):
http://i.imgur.com/J6PfVhq.png
* És innen már tudunk mindent a sugárról, amit követnünk kell. Ezeket az adatok célszerű egy struktúrába zárni:
* És innen már tudunk mindent a sugárról, amit követnünk kell. Ezeket az adatok célszerű egy struktúrába zárni:
<br/> <syntaxhighlight lang="c">
<br/> <syntaxhighlight lang="c">
610. sor: 618. sor:


Megjegyzések az algoritmussal kapcsolatban:
Megjegyzések az algoritmussal kapcsolatban:
* Az ernyő (a téglalap) az, amin a kép keletkezik, az viselkedik úgy mint a szemünk. Ha a téglalap helyére állnánk, akkor látnánk ugyan azt a képet, mint amit meg fogunk jeleníteni. Ezért célszerű kezdetben a kamera pozíciója helyett a téglalap pozícióját megadni. A kamera pozíciója amúgy irreleváns, az tetszőlegesen távol lehet a téglalaptól. Ha a távolsággal arányosan növeljük a téglalap méretét, akkor ugyan azt a képet fogjuk kapni.
* Azzal, hogy kijelentettük, hogy téglalap egy 2 egység élhosszúságú négyzet, és egységnyi távolságra van a kamerától, implicit kimondtuk, hogy a kamera látószöge 2 * arctg(1/1) = 90 fok. Egy nem túl arányos rajz arról, hogy ez hogy jött ki:
* Azzal, hogy kijelentettük, hogy téglalap egység négyzet, és egységnyi távolságra van a kamerától, implicit kimondtuk, hogy a kamera látószöge arctg(1) = 45 fok. De nem biztos, hogy ennyit szeretnénk, úgyhogy a látószög (Field of View - Fov) is legyen inkább paraméter. A kamera-téglalap távolságot célszerűbb változtatni, mint a téglalap méretét, mert így nem kell eltárolni a FoV-ot. Az arány amit akarunk az 0.5*ctg(fov/2)
 
* Ha teljesen korrektek akarnánk lenni, akkor fél pixellel el kéne tolni a síkot metsző pontokat, hogy azok ne a pixelek bal fölső sarkán keresztül haladjanak át, hanem a közepükön. Bár én szabad szemmel nem látok különbséget ez a változtatás után.  
http://i.imgur.com/gC7f6l1.png
 
De nem biztos, hogy ennyit szeretnénk, úgyhogy a látószög (Field of View - Fov) is legyen inkább paraméter. Én az ernyő méretét fogom a változtatni, és az ernyő-kamera távolságot meghagyom egységnyinek. Az arány amit akarunk az a tan(fov/2).
* Ha teljesen korrektek akarnánk lenni, akkor fél pixellel el kéne tolni az ernyőt metsző pontokat, hogy azok ne a pixelek bal alsó sarkán keresztül haladjanak át, hanem a közepükön. Bár én szabad szemmel nem látok különbséget ez a változtatás után.  


Ezeket a változtatásokat is felhasználva egy lehetséges megvalósítás:
Ezeket a változtatásokat is felhasználva egy lehetséges megvalósítás:
621. sor: 632. sor:


   Camera(float fov, const Vector& eye, const Vector& target, const Vector& plane_up)  
   Camera(float fov, const Vector& eye, const Vector& target, const Vector& plane_up)  
       : pos(eye - (target-eye).normalize() / (2*tan((fov*M_PI/180)/2))), plane_pos(eye)  
       : pos(eye), plane_pos(eye + (target-eye).normalize())  
   {  
   {  
       Vector fwd = (plane_pos - pos).normalize();
       Vector fwd = (plane_pos - pos).normalize();
       right = cross(fwd, plane_up).normalize();
      float plane_half_size = tan((fov*M_PI/180)/2);
       up = cross(right, fwd).normalize();
     
      // A jobbra és felfele vektorokban is lehet tárolni a sík méretét, bár nem szép...
       right = plane_half_size * cross(fwd, plane_up).normalize();
       up = plane_half_size * cross(right, fwd).normalize();
   }  
   }  


637. sor: 651. sor:
     Vector pos_on_plane = Vector(
     Vector pos_on_plane = Vector(
       (x + 0.5f - Screen::width/2) / (Screen::width/2),
       (x + 0.5f - Screen::width/2) / (Screen::width/2),
      // Itt nem kell megfordítani az y tengelyt. A bal fölső sarok az origó most.
       (y + 0.5f - Screen::height/2) / (Screen::height/2),  
       (y + 0.5f - Screen::height/2) / (Screen::height/2),  
       0
       0
649. sor: 662. sor:
}
}
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>
Megjegyzés: én általában nem ilyen kamerát szoktam használni, de ez ugyanazt az eredményt adja, mint amit a 3D OpenGL résznél fogunk kapni, a gluLookAt() függvény segítségével.


Most már mindent tudunk a sugárkövetésről, azt leszámítva, hogy hogyan kell egy sugarat követni.
Most már mindent tudunk a sugárkövetésről, azt leszámítva, hogy hogyan kell egy sugarat követni.
728. sor: 743. sor:
       Nézzük meg az x és y pontra ezt az algoritmust.
       Nézzük meg az x és y pontra ezt az algoritmust.
       A cross(ab, ax), a cross(bc, bx), és a cross(ca, cx) és kifele mutat a  
       A cross(ab, ax), a cross(bc, bx), és a cross(ca, cx) és kifele mutat a  
       képernyőből, ugyan abba az irányba mint a normál vektor. Ezt amúgy a  
       képernyőből, ugyanabba az irányba mint a normál vektor. Ezt amúgy a  
       dot(cross(ab, ax), normal) >= 0 összefüggéssel egyszerű ellenőrizni.
       dot(cross(ab, ax), normal) >= 0 összefüggéssel egyszerű ellenőrizni.
       Az algoritmus alapján az x a háromszög belsejében van.
       Az algoritmus alapján az x a háromszög belsejében van.
736. sor: 751. sor:
       hogy az y pont a háromszögön kívül van.  
       hogy az y pont a háromszögön kívül van.  
        
        
       - A ötlet lehetőség a bary-centrikus koordinátáknak azt a tulajdonságát használja
       - A másik lehetőség a bary-centrikus koordinátáknak azt a tulajdonságát használja
       ki, hogy azok a háromszög belsejében lévő pontokra kivétel nélkül nem negatívak,  
       ki, hogy azok a háromszög belsejében lévő pontokra kivétel nélkül nem negatívak,  
       míg a háromszögön kívül lévő pontokra legalább egy koordináta negatív.
       míg a háromszögön kívül lévő pontokra legalább egy koordináta negatív.
835. sor: 850. sor:
==== Az irány fényforrás ====
==== Az irány fényforrás ====


Egy másik fontos fényforrás az irányfényforrás. Ilyen például a Nap. A Nap olyan távol van tőlünk, hogy a Föld felszínén egy adott területen nagyjából teljesen mindegy, hogy hol helyezkedik el egy objektum, a nap mindig ugyan olyan irányból és intenzitással világítja meg. Az irányfényforrás irányának természetesen fontos szerepe van, itt a számolás lényegesen bonyolultabb, mint az ambiens fényforrás esetén. Például egy felülről megvilágított szobában az asztal teteje sokkal világosabb, mint az asztal alja. Ahhoz, hogy ezt a hatást el tudjuk érni, kizárólag egyszerű fizikára van szükségünk. Tegyük fel, hogy egy anyagra két azonos erősségű fénysugár esik, az egyik merőlegesen, a másik theta szögben.
Egy másik fontos fényforrás az irányfényforrás. Ilyen például a Nap. A Nap olyan távol van tőlünk, hogy a Föld felszínén egy adott területen nagyjából teljesen mindegy, hogy hol helyezkedik el egy objektum, a nap mindig ugyanolyan irányból és intenzitással világítja meg. Az irányfényforrás irányának természetesen fontos szerepe van, itt a számolás lényegesen bonyolultabb, mint az ambiens fényforrás esetén. Például egy felülről megvilágított szobában az asztal teteje sokkal világosabb, mint az asztal alja. Ahhoz, hogy ezt a hatást el tudjuk érni, kizárólag egyszerű fizikára van szükségünk. Tegyük fel, hogy egy anyagra két azonos erősségű fénysugár esik, az egyik merőlegesen, a másik theta szögben.
* Ha a merőlegesen eső sugár átmérője egységnyi, akkor a theta szögben eső sugár esetében az a felület amin ugyan annyi energia eloszlik sokkal nagyobb. Könnyen levezethető, hogy az egységnyi felületre eső energia (azaz a megvilágítás ereje) cos(theta)-val arányos.
* Ha a merőlegesen eső sugár átmérője egységnyi, akkor a theta szögben eső sugár esetében az a felület amin ugyanannyi energia eloszlik sokkal nagyobb. Könnyen levezethető, hogy az egységnyi felületre eső energia (azaz a megvilágítás ereje) cos(theta)-val arányos.
* A beesési szög kiszámításához szükségünk van a felületi normálra. Még jó, hogy korábban gondoltunk erre. A cos(theta) kiszámításának egy egyszerű módja a skaláris szorzat használata. Ugyanis definíció szerint u * v = |u| * |v| * cos(theta). De ha u-t és v-t úgy választjuk meg, hogy egységnyi hosszúak legyenek, akkor a skaláris szorzat a cos(theta)-t adja. Ha a cos(theta) negatív, akkor a test takarásban van, és az irányfény semmit nem befolyásol a színén.
* A beesési szög kiszámításához szükségünk van a felületi normálra. Még jó, hogy korábban gondoltunk erre. A cos(theta) kiszámításának egy egyszerű módja a skaláris szorzat használata. Ugyanis definíció szerint u * v = |u| * |v| * cos(theta). De ha u-t és v-t úgy választjuk meg, hogy egységnyi hosszúak legyenek, akkor a skaláris szorzat a cos(theta)-t adja. Ha a cos(theta) negatív, akkor a test takarásban van, és az irányfény semmit nem befolyásol a színén.


Ezzel a modellel csak olyan anyagok jeleníthetőek meg jól, amikre egy felületi pont, konstans megvilágítás esetén mindig ugyanúgy néz ki, akárhonnan is nézzük. Ilyen anyag például a legtöbb műanyag, vagy mondjuk egy szivacs. Ezek diffúz anyagok, a fényt minden irányba ugyan olyan intenzitással szórják. Ez a modell kiegészítés nélkül nem működik az olyan anyagokra, amiken a fény megtud csillanni, vagy amikben látjuk a tükörképünket, és azokra se amiken átlátunk.
Ezzel a modellel csak olyan anyagok jeleníthetőek meg jól, amikre egy felületi pont, konstans megvilágítás esetén mindig ugyanúgy néz ki, akárhonnan is nézzük. Ilyen anyag például a legtöbb műanyag, vagy mondjuk egy szivacs. Ezek diffúz anyagok, a fényt minden irányba ugyanolyan intenzitással szórják. Ez a modell kiegészítés nélkül nem működik az olyan anyagokra, amiken a fény megtud csillanni, vagy amikben látjuk a tükörképünket, és azokra se amiken átlátunk.
   
   
Ennek a modellnek egy lehetséges implementációja:
Ennek a modellnek egy lehetséges implementációja:
883. sor: 898. sor:
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


Az eddigi elmélet összerakva egy programmá: [[Média:Grafpp_raytrace_kocka.cpp‎|Kocka-tracer]]
Az eddigi elmélet összerakva egy programmá: ''<Törölve, túl sokan másolták>''
<br/>
<br/>
Az eredménye, összehasonlítva azzal, amit az OpenGL tud, hasonló beállítások mellett:  
Az sugárkövetés eredménye(baloldalt), összehasonlítva azzal, amit az OpenGL tud (glutSolidCube, jobboldalt), hasonló beállítások mellett:  
<br/>
<br/>
<br/>
<br/>
___________'''Sugárkövetett kocka'''---------------------------------------------- '''glutSolidCube(1.0f)'''_____________


{|
{|
|-
|-
| http://i.imgur.com/tgmGj7A.png http://i.imgur.com/PA2A3eQ.png <br/>
| http://i.imgur.com/ZC86LKY.jpg || http://i.imgur.com/PA2A3eQ.png  
|}
|}


==== A pontfényforrás ====
==== A pontfényforrás ====


A pontfényforrás a grafikában egy nagyon gyakran használt fényforrás, de a valóságban nem létezik. A valóságos fényforrásoknak egyáltalán nem elhanyagolható a kiterjedése, nem pontszerűek. Viszont a kisebb fényforrásokat, például egy izzót, közelíthetünk ezzel a modellel, és így sokkal könnyebb lesz velük számolni. A pont fényforrás az irányfényforrástól annyiban különbözik, hogy az intenzitása és az iránya se állandó.  
A pontfényforrás a grafikában egy nagyon gyakran használt modell, de a valóságban nem létezik. A valós fényforrásoknak egyáltalán nem elhanyagolható a kiterjedése, nem pontszerűek, viszont a kisebbeket  például egy izzót közelíthetünk így, és ezzel sokkal könnyebb lesz vele számolni. A pontfényforrás az irányfényforrástól annyiban különbözik, hogy az intenzitása és az iránya se állandó.  


Az intenzitásról annyit tudunk mondani, hogy az bármely, a fényforrást körülvevő zárt térfogat felületén állandó (hiszen a fotonok nem vesznek el a semmibe, és nem is születnek a semmiből). Ebből következik, hogy a fényforrás középpontú gömbök felületén is állandó az energia. Viszont erről a felületről tudjuk, hogy a távolság négyzetével arányos (A = 4*r^2*Pi), így az egy pontra jutó energia a távolság négyzetének reciprokával lesz arányos. A fény iránya pedig egyszerűen a fényforrásból az megvilágítandó anyag felületi pontjába mutató egységvektor. A beesési szög figyelembevételéhez itt is használhatjuk azt a koszinuszos képlet.
Az intenzitásról annyit tudunk mondani, hogy az bármely, a fényforrást körülvevő zárt felületen állandó (hiszen a fotonok nem vesznek el a semmibe, és nem is születnek a semmiből). Ebből következik, hogy a fényforrás középpontú gömbök felületén is állandó az energia. Viszont erről a felületről tudjuk, hogy a távolság négyzetével arányos (A = 4*r^2*Pi), így az egy pontra jutó energia a távolság négyzetének reciprokával lesz arányos. Az arányossági tényező persze jelenettől függ, általában ez egy szabad paraméter. A fény iránya pedig egyszerűen a fényforrásból az megvilágítandó anyag felületi pontjába mutató egységvektor. A beesési szög figyelembevételéhez itt is használhatjuk az irányfényforrásoknál megismert koszinuszos képlet.


A pontfényforrás esetében fontos, hogy ne csak a színét adjuk meg, hanem az energiáját is, pl. egy húsz wattos zölden világító izzót a Color(0.0f, 20.0f, 0.0f) vektorral jellemezhetünk. Természetesen itt egy egység nem fog pontosan megfelelni egy wattnak, ez függ attól is, hogy a távolságot hogyan választottuk meg.
A pontfényforrás esetében fontos, hogy ne csak a színét adjuk meg, hanem az energiáját is, pl. egy húsz wattos zölden világító izzót a Color(0.0f, 20.0f, 0.0f) vektorral jellemezhetünk. Természetesen itt egy egység nem fog pontosan megfelelni egy wattnak, ez függ attól is, hogy a távolságot hogyan választottuk meg.
916. sor: 930. sor:
A kamera fölül - fejlámpaként - világító pontfényforrás hatása:
A kamera fölül - fejlámpaként - világító pontfényforrás hatása:


http://i.imgur.com/3ZjMobS.png <br/>
http://i.imgur.com/k3hzUir.jpg <br/>


==== A spot lámpa ====
==== A spot lámpa ====
942. sor: 956. sor:
A spot lámpa segítségével sokkal meggyőzőbb fejlámpát lehet csinálni. Az más kérdés, hogy nekem nem sikerült... :D
A spot lámpa segítségével sokkal meggyőzőbb fejlámpát lehet csinálni. Az más kérdés, hogy nekem nem sikerült... :D


http://i.imgur.com/5KBCzk8.png <br/>
http://i.imgur.com/kW7kwHl.jpg <br/>


=== Árnyékok ===
=== Árnyékok ===
950. sor: 964. sor:
Ezen változtassuk, rakjuk rá a kockát valami talajra, hogy ne lebegjen (így legalább csak a talaj lebeg, nem a kocka), és használjunk valami hihetőbb háttérszínt, mint a teljesen fekete, a megvilágítás szemléltetéséhez.
Ezen változtassuk, rakjuk rá a kockát valami talajra, hogy ne lebegjen (így legalább csak a talaj lebeg, nem a kocka), és használjunk valami hihetőbb háttérszínt, mint a teljesen fekete, a megvilágítás szemléltetéséhez.


Például: [[Média:Grapp_raytrace_egyszeru_kornyezet.cpp|Egyszerű környezet]]
Például: ''<Törölve, túl sokan másolták>''


http://i.imgur.com/cBNRcbX.png <br/>
http://i.imgur.com/16ohvtS.jpg <br/>


Ez máris egy fokkal jobb, de sajnos a valósághűbb környezet választása nem oldotta meg minden problémánkat, hanem inkább újakat vetett fel, például, hogy a kocka nem vet árnyékot a síkra, mint ahogy azt a valóságban tenné.
Ez máris egy fokkal jobb, de sajnos a valósághűbb környezet választása nem oldotta meg minden problémánkat, hanem inkább újakat vetett fel, például, hogy a kocka nem vet árnyékot a síkra, mint ahogy azt a valóságban tenné.
960. sor: 974. sor:
Technikailag mindkét algoritmust fogjuk használni. A második - bár bonyolultabbnak tűnhet - de irányfényforrásokra csak az módszer működik. Az irányfényforrások ugyanis végtelen távol vannak, ahonnan nem tudok elindulni, viszont a felületi pontból a fény irányába el tudunk. A pontfényforrások esetén viszont az első algoritmus egyszerűbb egy picivel.
Technikailag mindkét algoritmust fogjuk használni. A második - bár bonyolultabbnak tűnhet - de irányfényforrásokra csak az módszer működik. Az irányfényforrások ugyanis végtelen távol vannak, ahonnan nem tudok elindulni, viszont a felületi pontból a fény irányába el tudunk. A pontfényforrások esetén viszont az első algoritmus egyszerűbb egy picivel.


Fontos megjegyezni, hogy például irányfényforrások esetén a sugarat nem indíthatjuk pontosan a felületi pontról, hiszen akkor az ahhoz legközelebbi metszéspont maga a felületi pont lenne amiből indítottuk. Ez persze nem mindig teljesülne, a számolási pontosság miatt, ezért néhol árnyékban lesz, néhol nem. Ezt a jelenséget "árnyék pattanás"-nak hívja a szakirodalom (shadow acne). Ez nagyon jellemző mintázatot okoz, ami általában könnyű detektálni. Az alábbi kép mutat egy példát erre:
Fontos megjegyezni, hogy például irányfényforrások esetén a sugarat nem indíthatjuk pontosan a felületi pontról, hiszen akkor az ahhoz legközelebbi metszéspont maga a felületi pont lenne amiből indítottuk. Ez persze nem mindig teljesülne, a számolási pontosság miatt, ezért néhol árnyékban lesz, néhol nem. Ezt a jelenséget "árnyék pattanás"-nak hívja a szakirodalom (shadow acne). Ez nagyon jellemző mintázatot okoz, amit általában könnyű detektálni. Az alábbi kép mutat egy példát erre:


http://i.imgur.com/TeDR81x.png
http://i.imgur.com/TeDR81x.png
996. sor: 1 010. sor:
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


Példaprogram: [[Média:Grafpp_raytrace_arnyekok.cpp|Árnyékok]]
Példaprogram: ''<Törölve, túl sokan másolták>''


<br/>
<br/>
1 002. sor: 1 016. sor:
Pont fényforrás esetén:
Pont fényforrás esetén:


http://i.imgur.com/E8mjq9d.png
http://i.imgur.com/3PNCclh.jpg


Irány fényforrás esetén:
Irány fényforrás esetén:


http://i.imgur.com/Ty2pjDQ.png
http://i.imgur.com/1o1ose4.jpg


Irány és pont fényforrás esetén egyszerre:
Irány és pont fényforrás esetén egyszerre:
   
   
http://i.imgur.com/tnqsQ2g.png <br/>
http://i.imgur.com/Kkc48I9.jpg <br/>


=== Tonemapping ===
=== Tonemapping ===
1 020. sor: 1 034. sor:
http://i.imgur.com/ZRgJgHx.png <br/>
http://i.imgur.com/ZRgJgHx.png <br/>


A kép baloldala jól néz ki, van két egészen hihető árnyékunk. Maga a kocka is egész jó. Viszont a képnek szinte a teljes jobb oldala kiégett, és minden ugyan olyan fehér, ordít róla, hogy mű. És ezek csak 2 db 20 W-os izzó...
A kép baloldala jól néz ki, van két egészen hihető árnyékunk. Maga a kocka is egész jó. Viszont a képnek szinte a teljes jobb oldala kiégett, és minden ugyanolyan fehér, ordít róla, hogy mű. És ezek csak 2 db 20 W-os izzó...


Vegyünk két 50 W-os izzót:
Vegyünk két 50 W-os izzót:
1 026. sor: 1 040. sor:
http://i.imgur.com/z4fAqu0.png <br/>
http://i.imgur.com/z4fAqu0.png <br/>


A kocka még mindig jól néz ki. Ellenben itt már árnyékok is kiégtek, és az árnyékok nagy része ugyan olyan fényes, mint az, ami közvetlenül meg van világítva. És ezek még mindig teljesen hétköznapi értékek, két db 50 W-os izzó... Ha ez nem megy, akkor a Nappal mit kezdjünk?
A kocka még mindig jól néz ki. Ellenben itt már árnyékok is kiégtek, és az árnyékok nagy része ugyanolyan fényes, mint az, ami közvetlenül meg van világítva. És ezek még mindig teljesen hétköznapi értékek, két db 50 W-os izzó... Ha ez nem megy, akkor a Nappal mit kezdjünk?


Egy megoldás lehetne, hogy mindent sokkal sötétebbre veszünk, úgy, hogy a Nap fénye legyen a teljesen fehér. Annál világosabbal nem nagyon szoktunk dolgozni. Igen ám, de az emberi szem egy sötét szobában se teljesen vak, ha hihető képet akarunk, akkor egy olyan algoritmus kéne, ami ilyen körülmények között is élvezhető képet ad.
Egy megoldás lehetne, hogy mindent sokkal sötétebbre veszünk, úgy, hogy a Nap fénye legyen a teljesen fehér. Annál világosabbal nem nagyon szoktunk dolgozni. Igen ám, de az emberi szem egy sötét szobában se teljesen vak, ha hihető képet akarunk, akkor egy olyan algoritmus kéne, ami ilyen körülmények között is élvezhető képet ad.
1 057. sor: 1 071. sor:
{|
{|
|-
|-
| http://i.imgur.com/z4fAqu0.png http://i.imgur.com/P4cANQa.png  
| http://i.imgur.com/z4fAqu0.png || http://i.imgur.com/P4cANQa.png  
|} <br/>
|} <br/>


1 067. sor: 1 081. sor:
{|
{|
|-
|-
| http://i.imgur.com/2Fp5XFB.png http://i.imgur.com/rSwo1ot.png
| http://i.imgur.com/2Fp5XFB.png || http://i.imgur.com/rSwo1ot.png
|} <br/>
|} <br/>


1 080. sor: 1 094. sor:


<br/> <syntaxhighlight lang="c">
<br/> <syntaxhighlight lang="c">
   x = max(0, InputColor-0.004);
   x = max(0, InputLuminance-0.004);
   OutputColor = (x*(6.2*x+0.5))/(x*(6.2*x+1.7)+0.06);
   OutputLuminance = (x*(6.2*x+0.5))/(x*(6.2*x+1.7)+0.06);
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


1 087. sor: 1 101. sor:


<br/>
<br/>
_____ Tonemap nélküli kép ____________ Reinhard tonemap ______________ Filmic tonemap _______
A tonemap nélküli kép bal oldalt, Reinhard tonemap középen, míg a Filmic tonemap jobb oldalt látható.
 
{|
{|
|-
|-
1 110. sor: 1 123. sor:
=== Spekulráis anyagok ===
=== Spekulráis anyagok ===


Az anyagok amikkel eddig dolgoztunk teljesen diffúzak voltak. A teljesen diffúz anyag egy pontja, konstans megvilágítás mellett mindig ugyan úgy néz ki, akárhonnan, akármilyen szögből is nézzük. A valós anyagok viszont nem mind így viselkednek.
Az anyagok amikkel eddig dolgoztunk teljesen diffúzak voltak. A teljesen diffúz anyag egy pontja, konstans megvilágítás mellett mindig ugyanúgy néz ki, akárhonnan, akármilyen szögből is nézzük. A valós anyagok viszont nem mind így viselkednek.


Most azzal fogunk foglalkozni, hogy bizonyos anyagokon egy lámpa fénye meg tud csillanni, ha megfelelő irányból nézzük. Ilyen pl. egy lakkozott fa felület. Fontos megjegyezni, hogy ez nem úgy viselkedik mint egy tükör, nem látjuk rajta a tükörképünket, csak egy megcsillanó foltot.
Most azzal fogunk foglalkozni, hogy bizonyos anyagokon egy lámpa fénye meg tud csillanni, ha megfelelő irányból nézzük. Ilyen pl. egy lakkozott fa felület. Fontos megjegyezni, hogy ez nem úgy viselkedik mint egy tükör, nem látjuk rajta a tükörképünket, csak egy megcsillanó foltot.
1 137. sor: 1 150. sor:
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


Itt se feledkezzünk el az árnyékokról. Szerencsére az árnyékszámítás itt is teljesen ugyan az, ezért célszerű a láthatóságot egy külön függvényben eldönteni.
Itt se feledkezzünk el az árnyékokról. Szerencsére az árnyékszámítás itt is teljesen ugyanaz, ezért célszerű a láthatóságot egy külön függvényben eldönteni.


A 'shininess' meghatározása teljesen mértékben hasra-ütésre, próbálgatással szokott menni. Én személy szerint a kettőhatvány shinnines értékekkel szoktam először próbálkozni (8 - 16 - 32 - 64 a leggyakoribb nálam), és utána esetleg "finom hangolom" az értéket, az alapján, hogy melyik éréték néz ki jól.
A 'shininess' meghatározása teljesen mértékben hasra-ütésre, próbálgatással szokott menni. Én személy szerint a kettőhatvány shinnines értékekkel szoktam először próbálkozni (8 - 16 - 32 - 64 a leggyakoribb nálam), és utána esetleg "finom hangolom" az értéket, az alapján, hogy melyik éréték néz ki jól.
1 143. sor: 1 156. sor:
A spekuláris megcsillanások nagyon sokat tudnak dobni egy kép hihetőségén.
A spekuláris megcsillanások nagyon sokat tudnak dobni egy kép hihetőségén.


Például: [[Média:Grafpp_raytrace_specular_highlights.cpp‎|Spekuláris megcsillanás egy kockán]]
Például: ''<Törölve, túl sokan másolták>''


http://i.imgur.com/VBb5ODK.png
http://i.imgur.com/uCHgf9g.jpg


=== A tökéletes tükör ===
=== A tökéletes tükör ===
1 182. sor: 1 195. sor:
Például ha a padló anyagát lecserélem egy tükörre, akkor az eredmény így néz ki:
Például ha a padló anyagát lecserélem egy tükörre, akkor az eredmény így néz ki:


http://i.imgur.com/lPlWNjr.png
http://i.imgur.com/GLBoT7h.jpg


Ezzel akad egy érdekes probléma, ami nekem egy héten át fel se tűnt. A költői kérdés a következő: Ha tökéletes a tükrünk, és pont ugyan azt látjuk benne, mint ami felette is van, akkor miért különöl el ilyen egyértelműen a háttérszíntől? A válasz persze az, hogy 5 példaprogrammal ezelőtt hoztam egy olyan döntést, hogy a tonemap-et a háttérszínre nem alkalmazom. Ez akkor még jó ötletnek tűnt, de a tükröző anyagok bevezetésével teljesen fals eredményt okoz. Ha a tonemapet a teljes képernyőre alkalmazom, akkor ezt az eredményt kapom:
Ezzel akad egy érdekes probléma, ami nekem egy héten át fel se tűnt. A költői kérdés a következő: Ha tökéletes a tükrünk, és pont ugyanazt látjuk benne, mint ami felette is van, akkor miért különöl el ilyen egyértelműen a háttérszíntől? A válasz persze az, hogy 5 példaprogrammal ezelőtt hoztam egy olyan döntést, hogy a tonemap-et a háttérszínre nem alkalmazom. Ez akkor még jó ötletnek tűnt, de a tükröző anyagok bevezetésével teljesen fals eredményt okoz. Ha a tonemapet a teljes képernyőre alkalmazom, akkor ezt az eredményt kapom:


http://i.imgur.com/pl1QVnW.png
http://i.imgur.com/M09ZnHA.jpg


Ez már valóban egy tükörnek néz ki, de két apró probléma még akad vele... Az egyik, hogy lámpa fénye nem csillan meg rajta. Erre korrekt megoldást majd csak később fogunk tudni adni. A másik probléma, az az, hogy mi történik ezzel a modellel, ha két tükröt rakunk egymással szembe? A sugár a végtelenségig fog pattogni a kettő között? Nem egészen. Ugyanis ez egy rekurzív algoritmus, ahol a függvényhívásoknak a stackbe is lesz nyoma, ahol viszont a hely előbb utóbb elfogy, és ilyenkor a programunk megáll.
Ez már valóban egy tükörnek néz ki, de két apró probléma még akad vele... Az egyik, hogy lámpa fénye nem csillan meg rajta. Erre korrekt megoldást majd csak később fogunk tudni adni. A másik probléma, az az, hogy mi történik ezzel a modellel, ha két tükröt rakunk egymással szembe? A sugár a végtelenségig fog pattogni a kettő között? Nem egészen. Ugyanis ez egy rekurzív algoritmus, ahol a függvényhívásoknak a stackbe is lesz nyoma, ahol viszont a hely előbb utóbb elfogy, és ilyenkor a programunk megáll.
1 205. sor: 1 218. sor:
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


Példaprogram: [[Média:Grafpp_raytrace_tukrok.cpp‎|Két szemben lévő tükör]]
Példaprogram: ''<Törölve, túl sokan másolták>''


http://i.imgur.com/pOURr4W.jpg
http://i.imgur.com/jwtHLsb.jpg


=== A valós tükröző anyagok ===
=== A valós tükröző anyagok ===
1 253. sor: 1 266. sor:
Az 'n' és 'k' paraméterek az anyagra jellemzőek, általában a házi kiírásban meg vannak adva. Például ezüst esetén n = (0.14, 0.16, 0.13), k = (4.1, 2.3, 3.1)
Az 'n' és 'k' paraméterek az anyagra jellemzőek, általában a házi kiírásban meg vannak adva. Például ezüst esetén n = (0.14, 0.16, 0.13), k = (4.1, 2.3, 3.1)


Példaprogram: [[Média:Grafpp_raytrace_ezust.cpp‎|Ezüst]]
Példaprogram: ''<Törölve, túl sokan másolták>''
 
http://i.imgur.com/vJKsR53.jpg


http://i.imgur.com/hgwrBI5.jpg
Ez egyelőre rosszabbnak tűnik mint volt, de ennek az az oka, hogy a jelenet nagyon üres, az ezüstön kívül egy darab kocka van benne összesen. Összetettebb jelenetekben a fémek általában nagyon jól néznek ki.


=== Valós spekuláris anyagok ===
=== Valós spekuláris anyagok ===
1 292. sor: 1 307. sor:
A Fresnelhez használandó beesési szög, az jelen esetben a félszög vektor és a nézeti vektor között értendő. Azért ez a két vektor kell nekünk, mert a visszaverődésben résztvevő mikro-tükröknek a félszög vektor a felületi normálja, és a nézeti irányba verődnek vissza. Tehát egyszerűen a spekuláris megcsillanás színét meg kell szoroznunk a <code> F(max(dot(V, H), 0.0f)) </code> vektorral.
A Fresnelhez használandó beesési szög, az jelen esetben a félszög vektor és a nézeti vektor között értendő. Azért ez a két vektor kell nekünk, mert a visszaverődésben résztvevő mikro-tükröknek a félszög vektor a felületi normálja, és a nézeti irányba verődnek vissza. Tehát egyszerűen a spekuláris megcsillanás színét meg kell szoroznunk a <code> F(max(dot(V, H), 0.0f)) </code> vektorral.


Példaprogram: [[Média:Grafpp_raytrace_fresnel_specular.cpp‎| Fresnel spekuláris megvilágítás]]
Példaprogram: ''<Törölve, túl sokan másolták>''


Ha az F0-ra az üvegre jellemző 0.04f-re választjuk meg, az ilyen hatást eredményez:
Ha az F0-ra az üvegre jellemző 0.04f-re választjuk meg, az ilyen hatást eredményez:


(Megjegyzés: a kocka spekuláris színét a két esetben úgy választottam meg, hogy nagyjából azonos legyen a spekuláris megcsillanás fényessége az első képen, de nyilván a Fresnel tag miatt ugyan az a spekuláris szín mellet az sokkal sötétebb volt)  
(Megjegyzés: a kocka spekuláris színét a két esetben úgy választottam meg, hogy nagyjából azonos legyen a spekuláris megcsillanás fényessége az első képen, de nyilván a Fresnel tag miatt ugyanaz a spekuláris szín mellet az sokkal sötétebb volt)  


Fresnel (bal oldalt) összehasonlítása a Blinn-Phong-gal (jobb oldalt).
Fresnel (bal oldalt) összehasonlítása a Blinn-Phong-gal (jobb oldalt).
1 302. sor: 1 317. sor:
{|
{|
|-  
|-  
| http://i.imgur.com/2dKFZZr.jpg || http://i.imgur.com/okyS5z6.jpg  
| http://i.imgur.com/ps4hF5X.jpg || http://i.imgur.com/cii4bVs.jpg  
|-
|-
| http://i.imgur.com/ymaEtq6.jpg || http://i.imgur.com/LQJP9NO.jpg  
| http://i.imgur.com/detsaXr.jpg || http://i.imgur.com/wbthpam.jpg
|-
|-
| http://i.imgur.com/2vUXGic.jpg || http://i.imgur.com/9NcS6Sf.jpg  
| http://i.imgur.com/NELw5EV.jpg || http://i.imgur.com/xd1Sr99.jpg
|-
|-
| http://i.imgur.com/VO1iza9.jpg || http://i.imgur.com/d4HCNoO.jpg
| http://i.imgur.com/6bOHiXF.jpg || http://i.imgur.com/i4qxqJE.jpg
|}
|}


1 317. sor: 1 332. sor:
A spekulráis anyagokkal ellentétben a tükröző anyagokról alkotott modellünk teljesen figyelmen kívül hagyta az elsődleges fényforrásokat, színüket csak a környezetben lévő objektumokról visszaverődő fény befolyásolta. Pedig a valóságban nem az a tapasztalat, hogy ha egy tükrön keresztül nézünk a Napba, akkor nem látjuk azt.
A spekulráis anyagokkal ellentétben a tükröző anyagokról alkotott modellünk teljesen figyelmen kívül hagyta az elsődleges fényforrásokat, színüket csak a környezetben lévő objektumokról visszaverődő fény befolyásolta. Pedig a valóságban nem az a tapasztalat, hogy ha egy tükrön keresztül nézünk a Napba, akkor nem látjuk azt.


Az elsődleges fényforrások visszaverődésével a legfőbb problémánk az, hogy például egy pontfényforrás esetében a fényforrás tükörképe továbbra is pontszerű, ami olyan kicsi, hogy azt nem látjuk. Az irányfényforrásokkal is ugyan ez az eset, csak egy végtelenül kicsi térszög alól látszódnak.  
Az elsődleges fényforrások visszaverődésével a legfőbb problémánk az, hogy például egy pontfényforrás esetében a fényforrás tükörképe továbbra is pontszerű, ami olyan kicsi, hogy azt nem látjuk. Az irányfényforrásokkal is ugyanez az eset, csak egy végtelenül kicsi térszög alól látszódnak.  


Technikailag a modellnek két részlete okozza ezt az anomáliát: ideális tükröt és ideális fényforrásokat feltételezünk egyszerre. A kettő egyszerre nem az igazi, ezért az egyikről le kell mondanunk. Az ideális tükörről sokkal könnyebb lemondani, ezért én azt választom.  
Technikailag a modellnek két részlete okozza ezt az anomáliát: ideális tükröt és ideális fényforrásokat feltételezünk egyszerre. A kettő egyszerre nem az igazi, ezért az egyikről le kell mondanunk. Az ideális tükörről sokkal könnyebb lemondani, ezért én azt választom.  
1 327. sor: 1 342. sor:
Egy kis trükkre azonban szükségünk van. a pontfényforrások távolságfüggéséhez ugyanis követnünk kell, hogy a sugár összesen mennyi utat tett meg eddig.
Egy kis trükkre azonban szükségünk van. a pontfényforrások távolságfüggéséhez ugyanis követnünk kell, hogy a sugár összesen mennyi utat tett meg eddig.


Példaprogram: [[Média:Grafpp_raytrace_spekularis_tukor.cpp‎|Spekuláris tükör]]
Példaprogram: ''<Törölve, túl sokan másolták>''


A spekuláris tükör fizikailag nem túl korrekt, de jól néz ki. Például ilyen hatást lehet vele elérni:
A spekuláris tükör fizikailag nem túl korrekt, de jól néz ki. Például ilyen hatást lehet vele elérni:


http://i.imgur.com/0duO4Zq.jpg
http://i.imgur.com/6WR6EXf.jpg


=== A látható pontfényforrás ===
=== A látható fényforrások ===


Az előző példában láttuk, hogy hihetőbb képet kapunk, ha egy tükrön keresztül látjuk a fényforrásokat is. De mi van azzal az esettel, ha közvetlenül nézzük azokat? Tükörből látszanak, de direktbe nem?
Az előző példában láttuk, hogy hihetőbb képet kapunk, ha egy tükrön keresztül látjuk a fényforrásokat is. De mi van azzal az esettel, ha közvetlenül nézzük azokat? Tükörből látszanak, de direktbe nem?
1 339. sor: 1 354. sor:
A spekuláris tükör analógiájára itt használhatunk egy olyan modellt, hogy minden fényforrás, ami nincsen takarásban a képernyő egy bizonyos térszögét világosabbá teszi. Kb 10 sor, és semmi új ötlet nem kell hozzá, de legalább nem azt látjuk, hogy a fény a semmiből jön.
A spekuláris tükör analógiájára itt használhatunk egy olyan modellt, hogy minden fényforrás, ami nincsen takarásban a képernyő egy bizonyos térszögét világosabbá teszi. Kb 10 sor, és semmi új ötlet nem kell hozzá, de legalább nem azt látjuk, hogy a fény a semmiből jön.


http://i.imgur.com/fkInlFK.jpg
http://i.imgur.com/rL6DMNy.jpg


A pontfényforrásokra persze ez nem néz ki olyan jól, főleg azért, mert a képen nem látunk mélységet.
A pontfényforrásokra persze ez nem néz ki olyan jól, főleg azért, mert a képen nem látunk mélységet.
1 345. sor: 1 360. sor:
De irányfényforrásokra sokkal hihetőbb hatást ér el az ötlet.
De irányfényforrásokra sokkal hihetőbb hatást ér el az ötlet.


Például: [[Média:Grafpp_raytrace_lathato_fenyforras.cpp‎|Látható Nap]]
Például: ''<Törölve, túl sokan másolták>''


http://i.imgur.com/3jVS1W6.jpg
http://i.imgur.com/BxeEztT.jpg


Nem mondom, hogy a Nap a valóságban így néz ki, de ez nagyságrendekkel hihetőbb, mint egy láthatatlan Nap.
Nem mondom, hogy a Nap a valóságban így néz ki, de ez nagyságrendekkel hihetőbb, mint egy láthatatlan Nap.
1 380. sor: 1 395. sor:
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


Fontos még, hogy a Fresnel egyenletben kifele menet is ugyan azt a törésmutatót kell használni. Az F0 kiszámolásakor az egyesek a levegő törésmutatója helyén állnak, általános esetben a képlet a <code>((n1 - n2)^2 + k*k) / ((n1 + n2)^2  + k*k)</code> alakot veszi fel. Ez a képlet viszont n1 és n2 szempontjából szimmetrikus, így '''nem kell két F0-t számolni''', egyet a befele, egyet meg a kifele menő sugarakhoz.
Fontos még, hogy a Fresnel egyenletben kifele menet is ugyanazt a törésmutatót kell használni. Az F0 kiszámolásakor az egyesek a levegő törésmutatója helyén állnak, általános esetben a képlet a <code>((n1 - n2)^2 + k*k) / ((n1 + n2)^2  + k*k)</code> alakot veszi fel. Ez a képlet viszont n1 és n2 szempontjából szimmetrikus, így '''nem kell két F0-t számolni''', egyet a befele, egyet meg a kifele menő sugarakhoz.


Csak olyan törő anyagokkal foglalkozunk, amiknek a törésmutatója a hullámhossztól független. A nem így viselkedő anyagok, a prizmák nagyon látványos képeket tudnak eredményezni, de sokkal számításigényesebbek és bonyolultabbak, ezért most nem fogok velük foglalkozni.
Csak olyan törő anyagokkal foglalkozunk, amiknek a törésmutatója a hullámhossztól független. A nem így viselkedő anyagok, a prizmák nagyon látványos képeket tudnak eredményezni, de sokkal számításigényesebbek és bonyolultabbak, ezért most nem fogok velük foglalkozni.
1 425. sor: 1 440. sor:
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


Példaprogram: [[Média:Grafpp_raytrace_uveg.cpp|Üvegkocka]]
Példaprogram: ''<Törölve, túl sokan másolták>''
{|
{|
|-
|-
| http://i.imgur.com/SjKnAV0.jpg || http://i.imgur.com/P3bcJnt.jpg
| http://i.imgur.com/ULyutJC.jpg || http://i.imgur.com/bO4qAbx.jpg
|}
|}


A kocka egészen hihetően néz ki, a bal oldali képen, meredek szögből nézve, nagyrészt tükörként viselkedik (a tetején pl teljes visszaverődés látszódik), a jobb oldali képen pedig nagyrészt átlátszó. Viszont '''a talaj megvilágítása teljesen rossz'''. Az árnyékszámító algoritmus azt feltételezte, hogy fény a nem megy át a - jelenleg átlátszó - kockán. De ha az árnyékokat elhagynánk, akkor is teljesen rossz képet kapnánk. Az üveg kocka megtöri a fényt, de néhol tükröz is, esetleg sok fénysugarat ugyanabba a pontba fókuszál... ezeknek a jelenségeknek a hatását az eddigi megvilágítási modellünk egyáltalán nem vette figyelembe.
A kocka egészen hihetően néz ki, a bal oldali képen, meredek szögből nézve, nagyrészt tükörként viselkedik, a jobb oldali képen pedig nagyrészt átlátszó. Viszont '''a talaj megvilágítása teljesen rossz'''. Az árnyékszámító algoritmus azt feltételezte, hogy fény a nem megy át a - jelenleg átlátszó - kockán. De ha az árnyékokat elhagynánk, akkor is teljesen rossz képet kapnánk. Az üveg kocka megtöri a fényt, de néhol tükröz is, esetleg sok fénysugarat ugyanabba a pontba fókuszál... ezeknek a jelenségeknek a hatását az eddigi megvilágítási modellünk egyáltalán nem vette figyelembe.


A klasszikus megvilágítási modell (ahol az anyagok színe az ambiens, diffúz és spekuláris tagok összege) azt feltételezte, hogy a fény, a fényforrásból a jelenet bármely pontjába csak egyenes úton juthat el. Ennek az a nagy előnye, hogy egy felületi pont színéhez nem kell tudnunk a többi pont színéről semmit. Az ezzel a tulajdonsággal rendelkező világításszámoló algoritmusokat lokális illuminációnak nevezzük. Ha a jelenetben van tükröző vagy törő anyag akkor ez értelemszerűen nem működik. Az ilyen jelenteknél másképp kell megvilágítást számolnunk. Ilyenkor globális illuminációra van szükségünk.
A klasszikus megvilágítási modell (ahol az anyagok színe az ambiens, diffúz és spekuláris tagok összege) azt feltételezte, hogy a fény, a fényforrásból a jelenet bármely pontjába csak egyenes úton juthat el. Ennek az a nagy előnye, hogy egy felületi pont színéhez nem kell tudnunk a többi pont színéről semmit. Az ezzel a tulajdonsággal rendelkező világításszámoló algoritmusokat lokális illuminációnak nevezzük. Ha a jelenetben van tükröző vagy törő anyag akkor ez értelemszerűen nem működik. Az ilyen jelenteknél másképp kell megvilágítást számolnunk. Ilyenkor globális illuminációra van szükségünk.
1 454. sor: 1 469. sor:


A "foton" igazándiból foton csomagot jelent, pl. a törő anyagok kétfelé választhatnak egy ilyen csomagot.
A "foton" igazándiból foton csomagot jelent, pl. a törő anyagok kétfelé választhatnak egy ilyen csomagot.
A foton tudja magáról, hogy milyen színű. A kölcsönhatásokkor a Fresnel egyenlet következtében a foton színe megváltozhat. Az egyes fotonok energiája legyen fordítottan arányos a fotonok számával. A fényforrás energiája az összes fotonra egyenletesen oszlik szét. Ha megkétszerezzük a fotonok számát, akkor is ugyan olyan fényes jelentet akarunk kapni.
A foton tudja magáról, hogy milyen színű. A kölcsönhatásokkor a Fresnel egyenlet következtében a foton színe megváltozhat. Az egyes fotonok energiája legyen fordítottan arányos a fotonok számával. A fényforrás energiája az összes fotonra egyenletesen oszlik szét. Ha megkétszerezzük a fotonok számát, akkor is ugyanolyan fényes jelentet akarunk kapni.


A törő és tükröző anyagok pont ugyan úgy lépnek kölcsönhatásba a fotonokkal, mint ahogy a sugarakkal is. Ennek az implementáláshoz semmi új ötlet nem kell.  
A törő és tükröző anyagok pont ugyanúgy lépnek kölcsönhatásba a fotonokkal, mint ahogy a sugarakkal is. Ennek az implementáláshoz semmi új ötlet nem kell.  


Például egy felülről megvilágított üvegkocka így szórja a fényt: [[Média:Grafpp_raytrace_globalis_illum.cpp‎|Globális illumináció]]
Például egy felülről megvilágított üvegkocka így szórja a fényt: ''<Törölve, túl sokan másolták>''


{|
{|
|-
|-
| http://i.imgur.com/P5Lg7IO.jpg || http://i.imgur.com/IXr58PF.jpg
| http://i.imgur.com/1YvdpgA.jpg || http://i.imgur.com/4NYiFSE.jpg
|}
|}


=== A kétirányú sugárkövetés ===
=== A kétirányú sugárkövetés ===


A globális illumináció implementálásakor sokak fájó szívvel válnak meg a kódban a lokális illumináció résztől, lévén, hogy hiába írták meg, ha nem jó semmire. De ez nem így van. Egyrészt a negyedik és az ötödik házihoz nagyon nagy segítséget fog nyújtani, hogy érted, hogy hogyan működik a lokális illumináció, hiszen az OpenGL is ezt fogja használni. Másrészt még a sugárkövetés házi végleges formájába is hasznos lehet az a kód.
A globális illumináció implementálásakor sokak fájó szívvel válnak meg a kódban a lokális illumináció résztől, lévén, hogy hiába írták meg, ha nem jó semmire. De ez nem így van. Egyrészt a 3D OpenGL-es házikhoz nagyon nagy segítséget fog nyújtani, hogy érted, hogy hogyan működik a lokális illumináció, hiszen az OpenGL is ezt fogja használni. Másrészt még a sugárkövetés házi végleges formájába is hasznos lehet az a kód.


A kétirányú sugárkövetés ötlete, hogy használjuk a lokális és a globális illuminációt egyszerre. A diffúz anyagot világítsuk meg lokálisan, és csak azok a fotonok keltsenek rajta kausztikát, amik nem triviális úton (egyenes vonalon, végig a levegőben, kölcsönhatás nélkül) jutottak el a fényforrásból az anyagig. Tehát ha a foton ütközésekor a rekurziós szint 0, akkor az közvetlenül a fényforrásból jutott el hozzánk, azt ne mentsük el.
A kétirányú sugárkövetés ötlete, hogy használjuk a lokális és a globális illuminációt egyszerre. A diffúz anyagot világítsuk meg lokálisan, és csak azok a fotonok keltsenek rajta kausztikát, amik nem triviális úton (egyenes vonalon, végig a levegőben, kölcsönhatás nélkül) jutottak el a fényforrásból az anyagig. Tehát ha a foton ütközésekor a rekurziós szint 0, akkor az közvetlenül a fényforrásból jutott el hozzánk, azt ne mentsük el.
1 479. sor: 1 494. sor:
* Az árnyékok széle recésebb lesz. A bilineáris szűrés miatt a globális illumináció pontosabban határozza meg az árnyékok szélét, mint az a módszer, amit a lokális illuminációnál használtunk.
* Az árnyékok széle recésebb lesz. A bilineáris szűrés miatt a globális illumináció pontosabban határozza meg az árnyékok szélét, mint az a módszer, amit a lokális illuminációnál használtunk.


Példaprogram: [[Média:Grafpp_raytrace_ketiranyu_sugarkovetes.cpp‎|Kétirányú sugárkövetés]]
Példaprogram: ''<Törölve, túl sokan másolták>''


A korábbi jelenet, csak kétirányú (bal oldalt) és baloldalt (jobb oldalt) sugárkövetéssel, mindkét esetben 500 000 fotonnal
A korábbi jelenet kétirányú (bal oldalt) és csak globális (jobb oldalt) megvilágítással, mindkét esetben 500 000 fotonnal
{|
{|
|-
|-
| http://i.imgur.com/DJwySzu.jpg || http://i.imgur.com/IAJ813F.jpg
| http://i.imgur.com/fU0yrlg.jpg || http://i.imgur.com/1YvdpgA.jpg
|}
|}


1 524. sor: 1 539. sor:
}
}
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>
Az eredménye: [[Média:Grafpp_raytrace_ketiranyu_sugarkovetes_multisample.cpp‎| Egy nagyon, nagyon lassú, de szép eredményt adó program]]


http://i.imgur.com/hFG6KmA.jpg  
http://i.imgur.com/hFG6KmA.jpg  
1 542. sor: 1 555. sor:
<br/>
<br/>


== A negyedik házihoz szükséges elmélet ==
== 3D OpenGL ==


=== Kedvcsináló ===
=== Kedvcsináló ===


Sugárkövetéssel nagyon látványos képeket tudunk elérni, ha vesszük a fáradtságot, hogy érdekes objektumokat helyezzünk el a világban. Főleg a másodrendű felületek tudnak nagyon szép képeket adni. Az árnyékszámításhoz gyakorlatilag ölünkbe hullott egy algoritmus, amihez pont ugyan azt kellett csinálni, mint amit rajzoláskor is csináltunk. A globális illuminációval ráadásul olyan hatásokat is implementálni tudtunk, amikkel a mai játékokba szinte sehol nem lehet találkozni. Akkor mi a gond a sugárkövetéssel, miért nem ezt használjuk mindenhol, miért kell akkor egyáltalán OpenGL? A probléma az vele, hogy lassú. Az általam mutatott programok mindössze 14 darab háromszögből álltak, messze elmaradva a mai játékok komplexitásától, és így is, a globális illuminációval kb. 5 másodperc volt, mire a kép előállt. Ez nem tűnik soknak, de optimális esetben egy játékhoz másodpercenként legalább 60szor kéne képet alkotnunk, a sugárkövető ettől messze elmarad. Ha a kódot két héten át optimalizáltam volna, akkor akár 0.1 másodperc is lehetne a renderelési idő. Igen ám, de ez csak a képalkotásra szánt idő! A játék logika, főleg ütközés detektálások, vagy ruha-, víz szimuláció stb. egyáltalán nincsenek ingyen, és még azoknak is bele kéne férnie az időbe. És ez még mindig csak 14 darab háromszög.
Sugárkövetéssel nagyon látványos képeket tudunk elérni, ha vesszük a fáradtságot, hogy érdekes objektumokat helyezzünk el a világban. Főleg a másodrendű felületek tudnak nagyon szép képeket adni. Az árnyékszámításhoz gyakorlatilag ölünkbe hullott egy algoritmus, amihez pont ugyanazt kellett csinálni, mint amit rajzoláskor is csináltunk. A globális illuminációval ráadásul olyan hatásokat is implementálni tudtunk, amikkel a mai játékokba szinte sehol nem lehet találkozni. Akkor mi a gond a sugárkövetéssel, miért nem ezt használjuk mindenhol, miért kell akkor egyáltalán OpenGL? A probléma az vele, hogy lassú. Az általam mutatott programok mindössze 14 darab háromszögből álltak, messze elmaradva a mai játékok komplexitásától, és így is, a globális illuminációval kb. 5 másodperc volt, mire a kép előállt. Ez nem tűnik soknak, de optimális esetben egy játékhoz másodpercenként legalább 60szor kéne képet alkotnunk, a sugárkövető ettől messze elmarad. Ha a kódot két héten át optimalizáltam volna, akkor akár 0.1 másodperc is lehetne a renderelési idő. Igen ám, de ez csak a képalkotásra szánt idő! A játék logika, főleg ütközés detektálások, vagy ruha-, víz szimuláció stb. egyáltalán nincsenek ingyen, és még azoknak is bele kéne férnie az időbe. És ez még mindig csak 14 darab háromszög.
 
A általunk használt sugárkövetéssel a legnagyobb gond az, hogy nem használja a videókártyát. Hiába van a gépünkbe egy szuperszámítógép teljesítményű hardware, ha nem használjuk semmire. A mai videókártyák nem csak előre meghatározott műveletek tudnak végrehajtani, hanem már programozhatóak is. Az ezt kihasználó grafikus programok hihetetlen hatékonyak. Például az én grafika nagyházim egy 72 millió háromszögből álló jelenetet tudott valós időben (kb. 20 FPS-el) kirajzolni (fizikával együtt).


A általunk használt sugárkövetéssel a legnagyobb gond az, hogy nem használja a videókártyát. Hiába van a gépünkbe egy szuperszámítógép teljesítményű hardware, ha nem használjuk semmire. A mai videókártyák nem csak előre meghatározott műveletek tudnak végrehajtani, hanem már programozhatóak is. Az ezt kihasználó grafikus programok hihetetlen hatékonyak. Például az én grafika nagyházim egy 72 millió háromszögból álló jelenetet tudott valós időben (kb. 25 FPS-el) kirajzolni.


<div style="text-align:center;margin:0px auto;">
<div style="text-align:center;margin:0px auto;">
http://i.imgur.com/fGQv8Wp.png
http://i.imgur.com/zYUuZ0L.png
</div>
</div>


Csak emlékeztetőül, sugárkövetéssel 14 háromszög nem ment valós időben... A videókártya segítségével a 72 millió háromszög real-time kirajzolása még messze nem a maximum amit el lehet érni, a nagyházim kódja egyáltalán nincs is optimalizálva.
Csak emlékeztetőül, sugárkövetéssel 14 háromszög nem ment valós időben... A videókártya segítségével a 72 millió háromszögből álló jelenet real-time kirajzolása még messze nem a maximum amit el lehet érni.


A videókártyát használhatnánk arra, hogy gyors raytracert írjunk. Ehhez viszont meg kellene tanulni, hogy hogyan kell a videókártyát programozni... Ehelyett mi a videókártya kezelését az OpenGLre bízzuk, és az inkrementális elvet használva fogunk rajzolni (ugyanúgy, mint az első meg a második háziban, csak 3D-ben).
A videókártyát használhatnánk arra, hogy gyors raytracert írjunk. Ehhez viszont meg kellene tanulni, hogy hogyan kell a videókártyát programozni... Ehelyett mi a videókártya kezelését az OpenGLre bízzuk, és az inkrementális elvet használva fogunk rajzolni (ugyanúgy, mint a 2D OpenGL-es résznél, csak 3D-ben).


=== A 3D-s kocka ===
=== A 3D-s kocka ===


A második házinál mutattam egy glut függvényt, ami egy kockát rajzol ki. Ezt a házikhoz nem lehet használni, inkább írjunk egyet magunknak.  
A 2D OpenGL-es résznél mutattam egy glut függvényt, ami egy kockát rajzol ki. Ezt a házikhoz nem lehet használni, inkább írjunk meg magunknak.  


<br/> <syntaxhighlight lang="c">
<br/> <syntaxhighlight lang="c">
1 606. sor: 1 620. sor:
A függvény amit használhatunk az a <code> gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)</code>. Megjegyzések a paramétérek megválasztásához:
A függvény amit használhatunk az a <code> gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)</code>. Megjegyzések a paramétérek megválasztásához:
* A fov-ot szögben, nem radiánba kell megadnunk. A reális értéke 40 - 150 fok között mozog.
* A fov-ot szögben, nem radiánba kell megadnunk. A reális értéke 40 - 150 fok között mozog.
* Az ascpect a képernyő szélessége / képernyő magassága. A házikba ez 1.
* Az ascpect a képernyő szélessége / képernyő magassága. A házikba ennek az értéke egy.
* A zNear-nél sokan nagy késztetést éreznek, hogy egy nagyon kicsi értéket állítsanak be, hogy semmi se kerüljön az első vágósík elé. A gond viszont ezzel az, hogy az mélység számító algoritmus a természeténél fogva több különböző ponthoz is ugyan azt az értéket rendeli, hiszen csak egy véges tartományt használhat (általában egy 24 bites fixpontos számot). Amiért ez zavaró, az az, hogy minél nagyobb a zFar / zNear értéke, annál nagyobb tartományt kell ugyan arra a (0..1) intervallumra transzformálni. Ez pedig azzal jár hogy az egyre távolabb lévő pontokhoz is ugyan azt a mélységet fogja használni. Ilyenkor pedig nem tudjuk eldönteni, hogy mi látszódik, és mi nem, aminek szinte mindig nagyon ronda eredménye szokott lenni. Egy ökölszabály, hogy a zFar / zNear értéke ne legyen (sokkal) nagyobb 1000-nél. A házikhoz tipikusan nincs szükség 100-nál nagyobb zFar-ra, ilyenkor a zNear ne legyen 0.1-nél kisebb.
* A zNear-nél sokan nagy késztetést éreznek, hogy egy nagyon kicsi értéket állítsanak be, hogy semmi se kerüljön az első vágósík elé. A gond viszont ezzel az, hogy az mélység számító algoritmus a természeténél fogva több különböző ponthoz is ugyanazt az értéket rendeli, hiszen csak egy véges tartományt használhat (általában egy 24 bites fixpontos számot). Amiért ez zavaró, az az, hogy minél nagyobb a zFar / zNear értéke, annál nagyobb tartományt kell ugyanarra a (0..1) intervallumra transzformálni. Ez pedig azzal jár hogy az egyre távolabb lévő pontokhoz is ugyanazt a mélységet fogja használni. Ilyenkor pedig nem tudjuk eldönteni, hogy mi látszódik, és mi nem, aminek szinte mindig nagyon ronda eredménye szokott lenni. Egy ökölszabály, hogy a zFar / zNear értéke ne legyen (sokkal) nagyobb 1000-nél. A házikhoz tipikusan nincs szükség 100-nál nagyobb zFar-ra, ilyenkor a zNear ne legyen 0.1-nél kisebb.


Például:
Például:
1 762. sor: 1 776. sor:
Akkor már pixel szinten pontos képet kapunk... de sokkal sokkal drágábban. Ilyenkor rengeteg teljesen fölösleges munkát csináltatunk az OpenGLlel. Általánosságban nagyon rossz ötlet pixel szintű pontosságra törekedni a felbontás növelésével. A nem feltűnően zavaró megvilágítással általában már megelégszünk, egy összetett jelenetnél a megvilágítás pontatlansága úgy se tűnik fel.
Akkor már pixel szinten pontos képet kapunk... de sokkal sokkal drágábban. Ilyenkor rengeteg teljesen fölösleges munkát csináltatunk az OpenGLlel. Általánosságban nagyon rossz ötlet pixel szintű pontosságra törekedni a felbontás növelésével. A nem feltűnően zavaró megvilágítással általában már megelégszünk, egy összetett jelenetnél a megvilágítás pontatlansága úgy se tűnik fel.


=== Hátsólap eldobás ===
=== A megvilágítás és a transzformációk ===


Az utolsó példába, a kocka hat oldala közül egyszerre mindig csak három látszódott, de ennek ellenére mind a hatot kirajzoltuk, csak abból három a z-tárazás (GL_DEPTH_TEST) miatt nem látszódott. Ha nekünk kéne rajzolnunk, akkor a felületi normál alapján azonnal el tudnánk dönteni egy oldalról, hogy az a kamera felé néz-e vagy se. Ha ezt az ötletet az OpenGL-lel is fel tudnánk használni, akkor 3072 háromszög fölösleges kirajzolását megspórolnánk. Viszont a rajzoláshoz használt felületi normál, amit mi adunk meg, nem mindig egyezik meg a háromszögek felületi normáljával, sőt ez a vektor a háromszög három csúcspontjára különböző is lehet. Ha pedig nem ismerjük a háromszög normál vektorát, akkor nehéz az alapján optimalizálni. A háromszög három csúcsából ki tudjuk számolni a felületi normál irányát, csak annak az előjelét nem tudjuk. Az ötlet az, hogy a háromszög pontjainak legyen egy előre meghatározott körüljárási iránya, például legyenek a normál irányából nézve az óramutató járásával ellentétes (Counter Clockwise - CCW) sorrendben felsorolva a pontok. Ekkor, ha a másik oldalról nézzük a háromszöget, akkor a körüljárási irány megfordul, és onnan az óramutató járásával megegyező (Clockwise - CW) sorrendet fogok látni. És ezt már az OpenGL is fel tudja használni.
Korábban említettem, hogy egy fényforrás esetében a glLightfv függvénnyel a GL_POSITION konstans segítségével lehet állítani egy fényforrás pozícióját, ami egy négy dimenziós vektor.


Nekünk innentől csak annyi a dolgunk, hogy megadjuk, hogy a mi objektumainkra, a normál irányából nézve (ez a "front face") CW vagy CCW körüljárási irányt használunk. Pl:
Viszont egy vektor önmagában nem jelent semmit, amíg meg nem mondjuk, hogy melyik koordináta-rendszerben van értelmezve.  


<br/> <syntaxhighlight lang="c">
A rövid válaszom az, hogy a GL_MODELVIEW által definiált koordináta-rendszerben, vagyis a glLightfv-ben megadott helyzetnek a GL_MODELVIEW-val transzformált eredménye fogja meghatározni a fényforrás helyét a kamera koordináta-rendszerben. Ha érted, hogy ez mit jelent és mivel jár, akkor fojtasd a következő fejezettel.
glFrontFace(GL_CCW); // Az normál irányából nézve CCW a körüljárási irány
glCullFace(GL_BACK); // A hátsó oldalt akarjuk eldobni
glEnable(GL_CULL_FACE); // És engedélyezzük a lapeldobást.
</syntaxhighlight> <br/>


Ugyan azt látjuk, mint a három sor nélkül, de fele annyi erőfeszítéssel.
Ennek az egyik legfontosabb következménye, hogy a világítás működése függ attól, hogy azt a kódban hol állítjuk be.  


http://i.imgur.com/dzjcMuZ.png
A lehetőségek bemutatásához azt feltételezem, hogy az onDisplay elején a GL_MODELVIEW egységmátrixra van állítva, majd ugyanebben a függvényben később, de még a rajzolás előtt a kamera a gluLookAt függvénnyel úgy van beállítva, hogy egy ellipszis pályán mozogjon.


<br/>
Például:


Viszont ha az első lapokat dobjuk el a <code>glCullFace(GL_FRONT);</code> függvénnyel, akkor ezt kapjuk:
<br/> <syntaxhighlight lang="c">
void setCamera() {
  float time = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;
  gluLookAt(3*cos(time), 2, 2*sin(time), 0, 0, 0, 0, 1, 0);
}


http://i.imgur.com/OsxWbsw.png
void onDisplay() {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


=== Színek ===
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
 
  // ...
  setCamera();
 
  // rajzolás
}
</syntaxhighlight> <br/>


A színekkel kapcsolatban egy megjegyzés: a GL_COLOR_MATERIAL-ról azt írtam, hogy az csak egy átmeneti megoldás. Ami a másik fajta szín bevezetését motiválta, az az, hogy az anyagoknak lehet különböző az ambiens a diffúz és a spekuláris színe. Az OpenGL megvalósításában az anyagoknak még emisszív színe is lehet. Az emisszív színnel azt lehet elérni, hogy az objektum világítson, sötétben is látszódjon, de nem úgy, mint egy fényforrás, az emisszív anyag a környezetének a színét nem befolyásolja. A másik előnye az OpenGL implementációjának hogy az első és hátsó lapoknak külön színe is lehet.
A három lehetőség:
* A világítást a setCamera() előtt állítjuk be. Ilyenkor az értékek kamera koordináta-rendszerben értetődnek. Ez azt jelenti, hogy ha a pozíciót (-1, 0, 0, 0)-nak állítjuk be, akkor az mindig balról fog világítani, akármerre is néz a kamera. Ezt tipikusan olyankor szoktuk használni, ha a fényforrás a nézőhöz kötődik, pl egy FPS játékban a karakterünk fegyverén a lámpa.


Az egyes színeket állító függvény a <code>glMaterialfv(GLenum face, GLenum pname, const GLfloat *params);</code>
http://i.imgur.com/iZJlFr9.gif


A leggyakoribb paraméterei:
* A megvilágítást közvetlenül a setCamera() függvény után állítjuk be. Ekkora a glLightfv-ben megadott pozíció a világ koordináta-rendszerben lesz értelmezve, és a statikus objektumoknak "mindig ugyanaz az oldala lesz fényes". Ezt általában akkor szoktuk használni, ha a fényforrás a jelentben egy helyben marad.
* A 'face' értékei:
** GL_FRONT
** GL_BACK
** GL_FRONT_AND_BACK
* A 'pname' értékei:
** GL_AMBIENT
** GL_DIFFUSE
** GL_AMBIENT_AND_DIFFUSE
** GL_SPECULAR
** GL_EMISSION


Illetve egy másik hasznos függvény a <code>glMaterialf(GLenum face, GLenum pname, GLfloat param)</code>
http://i.imgur.com/cUPVzeT.gif
* Ezt a GL_SHININESS állítására szoktuk használni.


=== Tesszelláció ===
* A megvilágítást a setCamera(), és további transzformációk használata után állítjuk be. Így tipikusan a jelentünkben valamelyik objektummal együtt mozgó fényforrásokat szoktuk kezelni.


Avagy hogyan bontsuk fel primitívekre (tipikusan háromszögekre) az objektumainkat.
http://i.imgur.com/UZT2nMr.gif


Először is egy apró trükk azoknak, akik az előző példába nem látták át, hogy a kocka hogyan épül fel négyszögekre:
=== Hátsólap eldobás ===
Az OpenGL-t meg lehet kérni rá, hogy ne töltse ki a primitíveket, csak a határukat rajzolja ki a <code>glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);</code> függvénnyel. Ez olyan, mint ha GL_LINES-ra lecseréltük volna a rajzolandó primitívet, csak így nem kell a pontok felsorolásán módosítani, elég ezt az egy sort beírni és már látjuk is az objektumunk "drótvázát".


http://i.imgur.com/IBsec5K.png
Az utolsó példába, a kocka hat oldala közül egyszerre mindig csak három látszódott, de ennek ellenére mind a hatot kirajzoltuk, csak abból három a z-tárazás (GL_DEPTH_TEST) miatt nem látszódott. Ha nekünk kéne rajzolnunk, akkor a felületi normál alapján azonnal el tudnánk dönteni egy oldalról, hogy az a kamera felé néz-e vagy se. Ha ezt az ötletet az OpenGL-lel is fel tudnánk használni, a háromszögek felének fölösleges kirajzolását megspórolnánk. Viszont a rajzoláshoz használt felületi normál, amit mi adunk meg, nem mindig egyezik meg a háromszögek felületi normáljával, sőt ez a vektor a háromszög három csúcspontjára különböző is lehet. Ha pedig nem ismerjük a háromszög normál vektorát, akkor nehéz az alapján optimalizálni. A háromszög három csúcsából ki tudjuk számolni a felületi normál irányát, csak annak az előjelét nem tudjuk. Az ötlet az, hogy a háromszög pontjainak legyen egy előre meghatározott körüljárási iránya, például legyenek a normál irányából nézve az óramutató járásával ellentétes (Counter Clockwise - CCW) sorrendben felsorolva a pontok. Ekkor, ha a másik oldalról nézzük a háromszöget, akkor a körüljárási irány megfordul, és onnan az óramutató járásával megegyező (Clockwise - CW) sorrendet fogok látni. És ezt már az OpenGL is fel tudja használni.


Persze a megvilágítás a vonalakra is érvényes. Ha ez zavaró, akkor kapcsoljuk ki a megvilágítást, és pl. rajzolunk mindent fehérrel.
Nekünk innentől csak annyi a dolgunk, hogy megadjuk, hogy a mi objektumainkra, a normál irányából nézve (ez a "front face") CW vagy CCW körüljárási irányt használunk. Pl:


A kis kitérő után a tesszellációhoz visszatérve, a primitívekre bontástól a legalapvetőbb elvárásunk, hogy visszaadja azt az alakot, amit ki akartunk rajzolni.
<br/> <syntaxhighlight lang="c">
De ezen kívül is van pár fontos jellemzője a tesszellációknak, ami alapján az egyik primtívekre bontásra azt mondhatjuk, hogy jobb, mint a másik:
glFrontFace(GL_CCW); // Az normál irányából nézve CCW a körüljárási irány
* Például, hogy nagyjából azonos területű primitívekből épül-e fel.
glCullFace(GL_BACK); // A hátsó oldalt akarjuk eldobni
** Például a gömb tesszellációk tekintetében nagyon nagy különbség van az "UV sphere" és az "icosphere" között.
glEnable(GL_CULL_FACE); // És engedélyezzük a lapeldobást.
** Az "UV sphere" úgy készül, hogy vesszük a gömb paraméteres egyenletét (gömbi koordináta rendszerből kiindulva, a sugárt fix értékűre választjuk), a két szög mondjuk legyen béta (-90 < B < 90) és lambda (0 < L < 360), ezeken az értékeken végigiterálunk valamilyen felbontással, az adott szögekhez kiszámoljuk a Descart-koordinátákat, és ezekből négyzeteket (vagy háromszögeket) rajzolunk ki. Ha pl 'b' a B-t bejáró ciklusváltozó, 'l' azt 'L'-t bejáró ciklusváltozó, b_inc a két B érték közötti szög különbség, l_inc pedig amivel az L értékeket inkrementáljuk, akkor nekünk a (b, l), (b+b_inc, l), (b+b_inc, l+l_inc), (b, l+l_inc) négyzeteket kell kirajzolnunk az összes 'b' és 'l' értékre. Ha már elfelejtetted volna a gömbi koordinátákat egy kis emlékeztető:
</syntaxhighlight> <br/>


http://upload.wikimedia.org/wikipedia/hu/2/22/Gomb-pol.jpg
Ugyanazt látjuk, mint a három sor nélkül, de fele annyi erőfeszítéssel.


<br/>
http://i.imgur.com/dzjcMuZ.png
 
* Az icosphere ezzel szemben az öt szabályos test egyikéből, az ikozaéderből indul ki, és ennek növeli a polygon számát a Catmull-Clark algoritmussal. A golflabda is ilyen.
 
http://previewcf.turbosquid.com/Preview/2011/04/19__09_20_06/Perspective_normals.jpg4d727076-639f-4e38-8e93-b7b6ca4b6d52Large.jpg


<br/>
<br/>


* A két gömb összehasonlítva:
Viszont ha az első lapokat dobjuk el a <code>glCullFace(GL_FRONT);</code> függvénnyel, akkor ezt kapjuk:  


http://i.imgur.com/qNnAKse.png
http://i.imgur.com/OsxWbsw.png


* Az UV sphere esetében a sarkoknál közel nulla területűek a négyszögek, ott teljesen fölöslegesen kell számolnia az OpenGL-nek. Az icosphere esetében viszont a polygonok egyenletesen vannak elhelyezve, és ugyanakkora területűek. Az icosphere tehát jobb tesszelációja a gömbnek, mint az UV sphere. De az UV sphere-t megírni sokkal egyszerűbb, és a házikhoz teljesen elegendő.
=== Színek ===
* Két tesszelláció között nyilván különbség lehet, hogy mennyire jellegzetes pontokat ragad meg az eredeti objektumból. Például vegyünk egy síkot, amibe vannak keskeny kiugróan magas pontok. A tesszellációba a kiugró pontok elhagyása az alakzat összhatását teljesen el tudja rontani, míg ha ezeket is ugyan olyan súllyal vesszük be, mint a többi sík pontot, akkor összességébe az alakzatunk dimbes-dombos lesz, mind a sík volta, mind a kiugró pontok jellegzetessége elveszik.
* Két tesszelláció között nagyon fontos különbséget okozhatnak az árnyaló normálok. De mielőtt ebbe beleszaladnánk, nézzük meg, hogy mik is azok.


Megjegyzés: Általában parametrikus felületeket kell tesszellálni. Itt az összes pontnak a felsorolása - az alakzattól függetlenül - két darab for loop. Az objektum kirajzolása ettől mindössze annyiban különbözik, hogy a szomszédos pontokból primitíveket is kell alkotni. De szerencsére általában a szomszédos pontok paraméterei is szomszédosak. Bár elsőre meglepőnek hangozhat, de egy gömb és egy tórusz tesszellációja csak annyiban különbözik, hogy a két futó paraméterhez hogyan rendeljük hozzá a 3D-s pontot, vagyis a két alakzatnak csak az egyenlete más, ezt leszámítva a két objektum kirajzolásának a kódja teljesen ugyanaz. Sőt, amikor a kockának az egyes oldalait, a négyzeteket bontottam fel nagyobb részletességűre a megvilágításhoz, még ott is ugyan az az algoritmus került elő, mint ami a gömbnél vagy a tórusznál kell, egyedül az alakzat paraméteres egyenlete volt más.
A színekkel kapcsolatban egy megjegyzés: a GL_COLOR_MATERIAL-ról azt írtam, hogy az csak egy átmeneti megoldás. Ami a másik fajta szín bevezetését motiválta, az az, hogy az anyagoknak lehet különböző az ambiens a diffúz és a spekuláris színe. Az OpenGL megvalósításában az anyagoknak még emisszív színe is lehet. Az emisszív színnel azt lehet elérni, hogy az objektum világítson, sötétben is látszódjon, de nem úgy, mint egy fényforrás, az emisszív anyag a környezetének a színét nem befolyásolja. A másik előnye az OpenGL implementációjának hogy az első és hátsó lapoknak külön színe is lehet.


http://i.imgur.com/KCJ9s0Q.jpg
Az egyes színeket állító függvény a <code>glMaterialfv(GLenum face, GLenum pname, const GLfloat *params);</code>


=== Az árnyaló normálok ===
A leggyakoribb paraméterei:
* A 'face' értékei:
** GL_FRONT
** GL_BACK
** GL_FRONT_AND_BACK
* A 'pname' értékei:
** GL_AMBIENT
** GL_DIFFUSE
** GL_AMBIENT_AND_DIFFUSE
** GL_SPECULAR
** GL_EMISSION


A gömb tesszelációról azért beszéltem ennyit, mert ezen keresztül meg tudom mutatni, hogy mi az az árnyalási normál, és miért van rá szükség. Az OpenGL lehetőséget ad arra, hogy a rajzoláskor ne a háromszögek normáljait használjuk, hanem - a teljesen alakzat ismeretében - minden egyes pontban külön-külön adjuk meg a normálvektort. Ezeket árnyalási normálnak hívjuk, ezek nem a tesszellált, hanem az eredeti objektumnak a normáljai. Egy gömbnél például az összes normál a gömb középpontjából az adott felületi pontba mutató egységvektor lenne. A csúcsonként megadott normálok azért jók, mert így egy háromszöget úgy tudunk árnyalni, mintha az nem sík lenne. Így háromszögekkel meglepően jól lehet közelíteni még egy gömb felületét is.
Illetve egy másik hasznos függvény a <code>glMaterialf(GLenum face, GLenum pname, GLfloat param)</code>
* Az OpenGL-be a <code>glShadeModel(GL_SMOOTH);</code> függvénnyel lehet bekapcsolni, hogy figyelembe vegye az árnyaló normálokat.
* Ezt a GL_SHININESS állítására szoktuk használni.
* Én ennek a demonstrálásához a glutSolidSphere(GLdouble radius, GLint slices, GLint stacks); függvényt használom, de ezt házikba nem lehet használni.
* A példaprogram: [[Média:Grafpp4_gomb_sima_arnyalassal.cpp|Gömb sima árnyalással]]


A <code>GL_FLAT</code> árnyékolás (baloldalt) összehasonlítása <code>GL_SMOOTH</code> árnyékolással (jobboldalt)
=== Tesszelláció ===


{|
Avagy hogyan bontsuk fel primitívekre (tipikusan háromszögekre) az objektumainkat.
|-
| http://i.imgur.com/9luiuQI.png || http://i.imgur.com/IWMFbU4.png
|}


=== A trafók vs az árnyaló normálok ===
Először is egy apró trükk azoknak, akik az előző példába nem látták át, hogy a kocka hogyan épül fel négyszögekre:
Az OpenGL-t meg lehet kérni rá, hogy ne töltse ki a primitíveket, csak a határukat rajzolja ki a <code>glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);</code> függvénnyel. Ez olyan, mint ha GL_LINES-ra lecseréltük volna a rajzolandó primitívet, csak így nem kell a pontok felsorolásán módosítani, elég ezt az egy sort beírni és már látjuk is az objektumunk "drótvázát".


Amikor a világot transzformáljuk, akkor a normálokkal mi történjen? Az eltolás kérdése egyszerű, a normál az egy irányvektor, az eltolás nem hat rá. A forgatás természetesen ugyanúgy hat rá mint a helyvektorokra. A nagyítással kapcsolatban viszont már más a helyzet.
http://i.imgur.com/IBsec5K.png


Ezzel kapcsolatban módosítsuk az utolsó példaprogramot, kérjünk fele akkora gömböt a GLUT-tól, és azt nagyítsuk kétszeresére <code>glScalef()</code>-el. Az eredménye az, hogy a gömb fele olyan világos lesz:
Persze a megvilágítás a vonalakra is érvényes. Ha ez zavaró, akkor kapcsoljuk ki a megvilágítást, és pl. rajzolunk mindent fehérrel.


http://i.imgur.com/yy8eRvZ.png
A kis kitérő után a tesszellációhoz visszatérve, a primitívekre bontástól a legalapvetőbb elvárásunk, hogy visszaadja azt az alakot, amit ki akartunk rajzolni.
De ezen kívül is van pár fontos jellemzője a tesszellációknak, ami alapján az egyik primtívekre bontásra azt mondhatjuk, hogy jobb, mint a másik:
* Például, hogy nagyjából azonos területű primitívekből épül-e fel.  
** Például a gömb tesszellációk tekintetében nagyon nagy különbség van az "UV sphere" és az "icosphere" között.
** Az "UV sphere" pontjait a gömb paraméteres egyenletéből tudjuk kiszámolni (gömbi koordináta rendszerből kiindulva, a sugárt fix értékűre választjuk).


A világítás számításakor csak a normál változott meg, és nem nehéz kitalálni, hogy fele olyan hosszú lett. De nem kétszeresére nagyítottuk a világot? Akkor a normál miért lett fele akkora. Ha ugyan az maradt volna a normál, akkor működne jól. De akkor az OpenGL miért rontotta el?
http://upload.wikimedia.org/wikipedia/hu/2/22/Gomb-pol.jpg


Ahhoz, hogy ezt megértsük, vegyünk nem uniform nagyítást, ami a tengelyek irányába különböző mértékben nagyít. Pl. nagyítsuk a (3, 1, 1) vektorral. Ez nyilván drasztikusabb alakváltozással, és így a normálok lényegesebb megváltozásával is jár. Ennek az eredménye egy ellipszis lesz:
Az egyes pontokból pedig úgy csinálunk gömböt, hogy a szomszédos pontokat (amik a paraméterekben szomszédosak), "összekötjük", azaz egy sokszöget alkotunk belőlük. Például az például az összes paraméter értékpárra kirajzolunk egy-egy négyzetet.


http://i.imgur.com/RG9NlDC.png
http://i.imgur.com/YGa1jJw.png


Az ellipszisen egyrészt nagyon jól látszódik, hogy az a tesszellációs felbontás, ami a gömbhöz elég volt, itt már csúnya képet eredményez. De most nem ezen van a lényeg, hanem az árnyaló normálokon. Miben másak az ellipszis normáljai, mint a gömbé? Az X tengely mentén ugyan annyi változás 3-szor akkora távon következik be, ezért értelemszerűen, amíg a Y és a Z tengely mentén a egységnyit változik a felületi pont helye, addig az X tengely mentén ez az érték 1/3. Termesztésen ez a pongyola megfogalmazás a parciális deriváltak számszerű értékére vonatkozott. És a gradiens, a parc. deriváltakból álló vektor, a felületi normál amit mi kerestünk. Tehát a normálon a (3, 1, 1) nagyítás hatására (1/3, 1, 1) vektorral való skálázás történik. Általánosságban az igaz, hogy a normálokra a nagyítások inverz transzformációja hat.
* Az icosphere ezzel szemben az öt szabályos test egyikéből, az ikozaéderből indul ki, és ennek növeli a polygon számát a Catmull-Clark algoritmussal. A golflabda is ilyen.


Szerencsére ezt az OpenGL automatikusan megcsinálja helyettünk. A gond viszont ezzel az, hogy az ilyen transzformációk után a normál már nem feltétlen lesz egységvektor, mint ahogy azt a korábbi példákba is láttuk. De erre a megoldás egyszerű, a <code>glEnable(GL_NORMALIZE);</code> függvény megkéri az OpenGL-t, hogy a világítás számolásakor normalizálja a normálokat.
http://previewcf.turbosquid.com/Preview/2011/04/19__09_20_06/Perspective_normals.jpg4d727076-639f-4e38-8e93-b7b6ca4b6d52Large.jpg


=== A textúrázás ===
<br/>


Eddigi ismereteink szerint, ha egy objektumba egy plusz színt akartunk vinni, akkor kénytelenek voltunk egy plusz pontot is hozzáadni, ami semmi plusz információt nem hordozott, csak más volt a színe. De cserébe lényegesen többet kellett számolni.
* A két gömb összehasonlítva:


A plusz színek hozzáadásának hatékonyabb megoldására lehet használni a textúrázást. Ezt használva egy háromszögön belül minden egyes pixelnek különböző színe lehet, a háromszög csúcspontjaihoz rendelt színtől függetlenül. Viszont ha a megvilágítás csúcspontonként történik, akkor a megvilágítás hogyan tudja figyelembe venni ezeket az új színeket? A rövid válasz az, hogy sehogy, de mégis használhatunk megvilágítást és textúrázást egyszerre. Ehhez egy ügyes trükkre lesz szükségünk, de erről majd bővebben akkor, amikor már tudjuk, hogy hogyan kell textúrázni. Addig kapcsoljuk ki a megvilágítást.
http://i.imgur.com/qNnAKse.png


==== A textúrázás alapjai ====
* Az UV sphere esetében a sarkoknál közel nulla területűek a négyszögek, ott teljesen fölöslegesen kell számolnia az OpenGL-nek. Az icosphere esetében viszont a polygonok egyenletesen vannak elhelyezve, és ugyanakkora területűek. Az icosphere tehát jobb tesszelációja a gömbnek, mint az UV sphere. De az UV sphere-t megírni sokkal egyszerűbb, és a házikhoz teljesen elegendő.
* Két tesszelláció között nyilván különbség lehet, hogy mennyire jellegzetes pontokat ragad meg az eredeti objektumból. Például vegyünk egy síkot, amibe vannak keskeny kiugróan magas pontok. A tesszellációba a kiugró pontok elhagyása az alakzat összhatását teljesen el tudja rontani, míg ha ezeket is ugyanolyan súllyal vesszük be, mint a többi sík pontot, akkor összességébe az alakzatunk dimbes-dombos lesz, mind a sík volta, mind a kiugró pontok jellegzetessége elveszik.
* Két tesszelláció között nagyon fontos különbséget okozhatnak az árnyaló normálok. De mielőtt ebbe beleszaladnánk, nézzük meg, hogy mik is azok.


Először egy egyszerű 6 db quadból álló kockán fogom megmutatni, hogy a textúrázás hogyan működik. A cél, hogy minden oldalra kiírjak egy számot, hogy azt az oldalt hányadikként rajzoltam ki. Ezt csak kódból fogom megoldani, nem használok hozzá külső programmal, pl. photoshoppal előállított képet, mert azt a házikhoz se lehet. Néhány szám esetleg fordítva fog állni, de ez engem nem zavar. Ilyen eredményt fogunk kapni:
Megjegyzés: Általában parametrikus felületeket kell tesszellálni. Itt az összes pontnak a felsorolása - az alakzattól függetlenül - két darab for loop. Az objektum kirajzolása ettől mindössze annyiban különbözik, hogy a szomszédos pontokból primitíveket is kell alkotni. De szerencsére általában a szomszédos pontok paraméterei is szomszédosak. Bár elsőre meglepőnek hangozhat, de egy gömb és egy tórusz tesszellációja csak annyiban különbözik, hogy a két futó paraméterhez hogyan rendeljük hozzá a 3D-s pontot, vagyis a két alakzatnak csak az egyenlete más, ezt leszámítva a két objektum kirajzolásának a kódja teljesen ugyanaz. Sőt, amikor a kockának az egyes oldalait, a négyzeteket bontottam fel nagyobb részletességűre a megvilágításhoz, még ott is ugyanaz az algoritmus került elő, mint ami a gömbnél vagy a tórusznál kell, egyedül az alakzat paraméteres egyenlete volt más.


{|
http://i.imgur.com/KCJ9s0Q.jpg
|-
| http://i.imgur.com/Bh5G5f8.png || http://i.imgur.com/0lSPkjh.png
|}


Először is, mi is az a textúra? A textúra egy színeket tartalmazó tömb. OpenGL 1.1-be egy vagy két dimenziós lehet, és szabvány szerint kettőhatvány méretűnek kell lennie. Annak ellenére, hogy a textúra színeket tartalmaz, és a színeket az OpenGL floatként szereti kezelni, a textúrák esetében nem annyira szeretünk floatokat használni. Itt tényleg csak (0-1) tarmotány beli LDR színekre vagyunk kíváncsiak, itt float helyett elég egy fix pontos szám is, pl komponensenként egy byte. De gyakran mindhárom komponenst le tudjuk írni mindössze egy bájtban. A float textúrák sokkal több helyet foglalnak, külön megizzasztják a memóriát, ami már enélkül is szűk keresztmetszet, ráadásul ezt nagyjából feleslegesen tesszük, a float értékkészletének nagy részét nem is használjuk ki.
=== Az árnyaló normálok ===


De hogyan állítsuk elő a számokat? Vegyünk egy unsigned char tömböt és kézzel írjuk be az összes pixelre, hogy milyen színű? Majdnem, de ez ilyen formába használhatatlan lenne. Semmi vizuális visszacsatolásunk se lenne, hogy a textúra hogy néz ki. Én ehelyett egy karakter tömbbe (magyarul stringbe) fogok ascii-art számokat rajzolni. Például a '.' karakter jelentsen fehér színt, a '*' feketét, a '+' meg szürkét. Én 8*8-as textúrátkat fogok csinálni minden egyes számnak. A számokat én így álmodtam meg (ér ezeknél szebbeket csinálni):  
A gömb tesszelációról azért beszéltem ennyit, mert ezen keresztül meg tudom mutatni, hogy mi az az árnyalási normál, és miért van rá szükség. Az OpenGL lehetőséget ad arra, hogy a rajzoláskor ne a háromszögek normáljait használjuk, hanem - a teljesen alakzat ismeretében - minden egyes pontban külön-külön adjuk meg a normálvektort. Ezeket árnyalási normálnak hívjuk, ezek nem a tesszellált, hanem az eredeti objektumnak a normáljai. Egy gömbnél például az összes normál a gömb középpontjából az adott felületi pontba mutató egységvektor lenne. A csúcsonként megadott normálok azért jók, mert így egy háromszöget úgy tudunk árnyalni, mintha az nem sík lenne. Így háromszögekkel meglepően jól lehet közelíteni még egy gömb felületét is.  
* Az OpenGL-be a <code>glShadeModel(GL_SMOOTH);</code> függvénnyel lehet bekapcsolni, hogy figyelembe vegye az árnyaló normálokat.
* Én ennek a demonstrálásához a glutSolidSphere(GLdouble radius, GLint slices, GLint stacks); függvényt használom, de ezt házikba nem lehet használni.
* A példaprogram: [[Média:Grafpp4_gomb_sima_arnyalassal.cpp|Gömb sima árnyalással]]


<br/> <syntaxhighlight lang="c">
A <code>GL_FLAT</code> árnyékolás (baloldalt) összehasonlítása <code>GL_SMOOTH</code> árnyékolással (jobboldalt)
"........"          "........"          "........"
"...**..."          "..***+.."          "..***+.."
"....*..."          "....+*.."          "....+*.."
"....*..."          "....+*.."          "....+*.."
"....*..."          "...+*+.."          "...**+.."
"....*..."          "..+*+..."          "....+*.."
"...***.."          "..****.."          "..***+.."
"........",        "........",        "........",


"........"          "........"          "........"
{|
"....*..."          "..****.."          "........"
|-
"...+*..."          "..*....."          "..+**..."
| http://i.imgur.com/9luiuQI.png || http://i.imgur.com/IWMFbU4.png
"..+**..."          "..***+.."          "..*....."
|}
"..+.*..."          "....+*.."          "..***+.."
"..****.."          "....+*.."          "..*..*.."
"....*..."          "..***+.."          "..+**+.."
"........",        "........",        "........"
</syntaxhighlight> <br/>


Technikailag már ezek a stringek is lehetnének unsigned char tömbök. És három szín van összesen, amit egy byte-on is bőven le lehetne írni. Viszont én az egyszerűség kedvéért inkább maradok a komponensenként egy byte-nál, a 3 komponensre összesen egy byte helyett, és nem kezdek el bitműveletekkel játszani a GL_R3_G3_B2-höz.
=== A trafók vs az árnyaló normálok ===


Termesztésen ezekből a szín előállítása egyszerűen két egymásba ágyazott for ciklus. Pl:
Amikor a világot transzformáljuk, akkor a normálokkal mi történjen? Az eltolás kérdése egyszerű, a normál az egy irányvektor, az eltolás nem hat rá. A forgatás természetesen ugyanúgy hat rá mint a helyvektorokra. A nagyítással kapcsolatban viszont már más a helyzet.


<br/> <syntaxhighlight lang="c">
Ezzel kapcsolatban módosítsuk az utolsó példaprogramot, kérjünk fele akkora gömböt a GLUT-tól, és azt nagyítsuk kétszeresére <code>glScalef()</code>-el. Az eredménye az, hogy a gömb fele olyan világos lesz:
GLubyte texture_data[6][64][3];
for(int t = 0; t < 6; t++) {
  for(int i = 0; i < 64; i++) {
    switch(ascii_textures[t][i]) {
      case '*':
        for(int j = 0; j < 3; j++) {
          texture_data[t][i][j] = 0;
        }
        break;
      case '+':
        for(int j = 0; j < 3; j++) {
          texture_data[t][i][j] = 127;
        }
        break;
      default:
        for(int j = 0; j < 3; j++) {
          texture_data[t][i][j] = 255;
        }
        break;
    }
  }
}
</syntaxhighlight> <br/>


Először is, a kódban szerepel két konstans amiknek nem kéne ott lennie. A 127 és 255 csak azokon a gépeken jelenti azt a színt amit szeretnénk ahol a byte 8 bit. Viszont ahhoz, hogy megtudjuk, hogy mi a GLubyte maximuma, kéne a <limits> vagy <climits> header, amit nem include-olhatunk a háziban. Ha esetleg tudnánk, hogy az adott gép a negatív számokat kettes komplemens kódolásban használja (amit a C++ szabvány nem köt ki), akkor megkaphatnánk a GLubyte maximumát a GLubyte(-1) kifejezéssel, ugyanis kettes komplemens kódolásban a -1-hez tartozó bitminta a csupa egyes. Ennek ellenére ez egy nagyon ronda megoldás lenne a beadó kicselezésére, amire amúgy nincs szükség, mert a beadón egy byte 8 bit...  
http://i.imgur.com/yy8eRvZ.png


Oké, megvan a textúra tartalma, és akkor most ezt hogyan adjuk oda az OpenGLnek?
A világítás számításakor csak a normál változott meg, és nem nehéz kitalálni, hogy fele olyan hosszú lett. De nem kétszeresére nagyítottuk a világot? Akkor a normál miért lett fele akkora. Ha ugyanaz maradt volna a normál, akkor működne jól. De akkor az OpenGL miért rontotta el?


Először is szükségünk van "egy textúra mutató pointerre/referenciára", egy névre, amivel hivatkozhatunk majd később a textúrára. Ez a referencia a textúrák esetében (és a modern OpenGLbe gyakorlatilag az összes objektum esetében) egy GLuint (unsigned int) lesz. A textúrázással kapcsolatos függvények dokumentációjába ez "a textúra neve"-ként lesz megemlítve.
Ahhoz, hogy ezt megértsük, vegyünk nem uniform nagyítást, ami a tengelyek irányába különböző mértékben nagyít. Pl. nagyítsuk a (3, 1, 1) vektorral. Ez nyilván drasztikusabb alakváltozással, és így a normálok lényegesebb megváltozásával is jár. Ennek az eredménye egy ellipszis lesz:


Nekünk hat darab textúra kell, ezért definiáljuk egy 6 elemú GLuint tömböt:
http://i.imgur.com/RG9NlDC.png
 
Az ellipszisen egyrészt nagyon jól látszódik, hogy az a tesszellációs felbontás, ami a gömbhöz elég volt, itt már csúnya képet eredményez. De most nem ezen van a lényeg, hanem az árnyaló normálokon. Miben másak az ellipszis normáljai, mint a gömbé? Az X tengely mentén ugyanannyi változás 3-szor akkora távon következik be, ezért értelemszerűen, amíg a Y és a Z tengely mentén a egységnyit változik a felületi pont helye, addig az X tengely mentén ez az érték 1/3. Termesztésen ez a pongyola megfogalmazás a parciális deriváltak számszerű értékére vonatkozott. És a gradiens, a parc. deriváltakból álló vektor, a felületi normál amit mi kerestünk. Tehát a normálon a (3, 1, 1) nagyítás hatására (1/3, 1, 1) vektorral való skálázás történik. Általánosságban az igaz, hogy a normálokra a nagyítások inverz transzformációja hat.
 
Szerencsére ezt az OpenGL automatikusan megcsinálja helyettünk. A gond viszont ezzel az, hogy az ilyen transzformációk után a normál már nem feltétlen lesz egységvektor, mint ahogy azt a korábbi példákba is láttuk. De erre a megoldás egyszerű, a <code>glEnable(GL_NORMALIZE);</code> függvény megkéri az OpenGL-t, hogy a világítás számolásakor normalizálja a normálokat.


<br/> <syntaxhighlight lang="c">
=== A textúrázás ===
GLuint tex[6];
</syntaxhighlight> <br/>


Kérjük meg az OpenGL-t, hogy találjon ki nekünk hat darab textúra nevet. Ezek után ő ezekről a nevekről tudni fogja, hogy azok textúra vonatkoznak. Ehhez a <code> glGenTextures(GLsizei n, GLuint *textures); </code> függvényt használhatjuk.
Eddigi ismereteink szerint, ha egy objektumba egy plusz színt akartunk vinni, akkor kénytelenek voltunk egy plusz pontot is hozzáadni, ami semmi plusz információt nem hordozott, csak más volt a színe. De cserébe lényegesen többet kellett számolni.  


<br/> <syntaxhighlight lang="c">
A plusz színek hozzáadásának hatékonyabb megoldására lehet használni a textúrázást. Ezt használva egy háromszögön belül minden egyes pixelnek különböző színe lehet, a háromszög csúcspontjaihoz rendelt színtől függetlenül. Viszont ha a megvilágítás csúcspontonként történik, akkor a megvilágítás hogyan tudja figyelembe venni ezeket az új színeket? A rövid válasz az, hogy sehogy, de mégis használhatunk megvilágítást és textúrázást egyszerre. Ehhez egy ügyes trükkre lesz szükségünk, de erről majd bővebben akkor, amikor már tudjuk, hogy hogyan kell textúrázni. Addig kapcsoljuk ki a megvilágítást.
glGenTextures(6, tex);   
</syntaxhighlight> <br/>


Fontos, hogy ilyenkor még a textúra nem jön létre. Az csak akkor történik meg, amikor először használni akarjuk (bindoljuk).
==== A textúrázás alapjai ====


==== A textúrák beállítása ====
Először egy egyszerű 6 db quadból álló kockán fogom megmutatni, hogy a textúrázás hogyan működik. A cél, hogy minden oldalra kiírjak egy számot, hogy azt az oldalt hányadikként rajzoltam ki. Ezt csak kódból fogom megoldani, nem használok hozzá külső programmal, pl. photoshoppal előállított képet, mert azt a házikhoz se lehet. Néhány szám esetleg fordítva fog állni, de ez engem nem zavar. Ilyen eredményt fogunk kapni:


Nyilván egy for ciklusban fogom beállítani őket, csak az 'i'-edik textúra beállítását mondom el.
{|
|-
| http://i.imgur.com/Bh5G5f8.png || http://i.imgur.com/0lSPkjh.png
|}


Ahhoz, hogy egy textúrán műveletet tudjunk végezni azt bindolni kell egy targethez. Képzeljünk el egy szobát, amibe vannak képkeretek. Az összes képkeret különböző alakú, mindegyikbe másféle képet tudunk rakni. De mindig csak éppen a képkeretben lévő képeket látjuk, csak azokkal tudunk dolgozni. Ezek a képkeretek pontosan ugyanúgy viselkednek mint az OpenGL-be a targetek. Két fontos target van, az egyik az egydimenziós texturáknak (GL_TEXTURE_1D), a másik a kétdimenziósoknak (GL_TEXTURE_2D). A házikban szinte mindig csak a kétdimenziósra lesz szükséged. A hasonlatban amikor egy képet berakunk a képkeretbe, az megegyezik az OpenGL-nél a bindolással. A bindolás után az összes textúra művelet arra a képre fog vonatkozni. Bindolni a <code>glBindTexture(GLenum target, GLuint texture);</code> függvénnyel tudunk.
Először is, mi is az a textúra? A textúra egy színeket tartalmazó tömb. OpenGL 1.1-be egy vagy két dimenziós lehet, és szabvány szerint kettőhatvány méretűnek kell lennie. Annak ellenére, hogy a textúra színeket tartalmaz, és a színeket az OpenGL floatként szereti kezelni, a textúrák esetében nem annyira szeretünk floatokat használni. Itt tényleg csak (0-1) tartománybeli LDR színekre vagyunk kíváncsiak, itt float helyett elég egy fix pontos szám is, pl. komponensenként egy byte. De gyakran mindhárom komponenst le tudjuk írni mindössze egy bájtban. A float textúrák sokkal több helyet foglalnak, külön megizzasztják a memóriát, ami már enélkül is szűk keresztmetszet, ráadásul ezt nagyjából feleslegesen teszik, a float értékkészletének nagy részét nem is használják ki.  


<br/> <syntaxhighlight lang="c"> 
De hogyan állítsuk elő a számokat? Vegyünk egy unsigned char tömböt és kézzel írjuk be az összes pixelre, hogy milyen színű? Majdnem, de ez ilyen formába használhatatlan lenne. Semmi vizuális visszacsatolásunk se lenne, hogy a textúra hogy néz ki. Én ehelyett egy karakter tömbbe (magyarul stringbe) fogok ascii-art számokat rajzolni. Például a '.' karakter jelentsen fehér színt, a '*' feketét, a '+' meg szürkét. Én 8*8-as textúrákat fogok csinálni minden egyes számnak. A számokat én így álmodtam meg (ér ezeknél szebbeket csinálni):
glBindTexture(GL_TEXTURE_2D, tex[t]);   
</syntaxhighlight> <br/>


Ezután fel kell töltenünk a textúrát a <code> glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels)</code> függvénnyel.
<br/> <syntaxhighlight lang="c">
"........"          "........"          "........"
Elősször nézzük meg a paramétereit:
"...**..."          "..***+.."          "..***+.."
* target - Ez nálad szinte mindig GL_TEXTURE_2D lesz.
"....*..."          "....+*.."          "....+*.."
* level - Ide 0-t írj. Az OpenGL megengedi, hogy egy textúrának különböző felbontású változatait is feltöltsed, ami sokat javíthat a rendelt kép minőségén. Az alap textúránál kisebb felbontású képeket mipmap-nek hívják. Az 'i'-edik mipmap mérete minden dimenzióban fele az 'i-1'- ediknek. Ide azt írhatod be, hogy éppen hányadik mipmap-et töltöd fel (a 0. mipmap az alaptextúra). A mipmap-ek használata nagyon hasznos tud lenni, de nagyon veszélyes is. Ha ez a feature be van kapcsolva, és nem töltöd fel az összes mipmap-et, vagy valahova rossz felbontásút töltesz fel, akkor a textúra definíció szerint "incomplete" marad, és az incomplete textúrák úgy viselkednek, mintha nem is lennének ott. És ezt nagyon nehéz debuggolni, ezért a tárgyból ajánlott a mipmapek használatának kerülése.
"....*..."          "....+*.."         "....+*.."
* internalFormat - Ez legyen GL_RGB vagy GL_RGBA. Itt azt kell megadnod, hogy az OpenGL milyen formátumba tárolja a képet. Például a tömörített formátumok nagyon hasznosak, de a házikhoz nem kell használnod őket. Hatékonysági okok miatt a komponensenként egy byteos GL_RGB textúra gyakran GL_RGBA formátumba tárolódik, mert az OpenGL csak 4 byteos egységeket tud hatékonyan írni / olvasni, de ezzel nekünk explicit nem kell foglalkozni, ezt majd a driver eldönti, hogy szerinte hogy jó.  
"....*..."          "...+*+.."          "...**+.."
* width / height - Egyértelmű.  
"....*..."          "..+*+..."          "....+*.."
* border - Ez mindenképpen legyen 0.  
"...***.."          "..****.."          "..***+.."
* format - Ez is legyen GL_RGB vagy GL_RGBA. Itt adhatod meg, hogy milyen formátumban töltöd fel a képet. Ez nem feltétlen egyezik meg az internalFormat-al, más formátúmú tárolást is lehet tőle kérni, mint ahogy mi adjuk neki a textúrákat.
"........",         "........",         "........",
* type - Ez legyen GL_UNSIGNED_BYTE. Ez azt adja meg, hogy milyen típussal írtad le a színeket.
* pixels - A textúra elejére mutató pointer. Fontos megjegyezni, hogy a tömbünket úgy értelmezi, hogy a sorok mérete 4 byte-al oszható. Ha nem olyan (pl. 2*2-es RGB textúrát használtunk, akkor a sorokat ki kell egészíteni úgy nevezett "padding"-el, hogy illeszkedjenek a szóhatárra.


A mi esetünkben: 
"........"          "........"          "........"
 
"....*..."          "..****.."          "........"
<br/> <syntaxhighlight lang="c">
"...+*..."          "..*....."          "..+**..."
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, texture_data[t]);
"..+**..."          "..***+.."          "..*....."
"..+.*..."          "....+*.."          "..***+.."
"..****.."          "....+*.."          "..*..*.."
"....*..."          "..***+.."          "..+**+.."
"........",         "........",         "........"
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


A textúrákkal kapcsolatban nagyon sok beállítási lehetőségünk van, amiket a <code> glTexParameteri(GLenum target, GLenum pname, GLint param); </code> függvénnyel tehetünk meg. Minket két fontos paraméter érdekel, a <code>GL_TEXTURE_MIN_FILTER</code>, és a <code>GL_TEXTURE_MAG_FILTER</code> és mindkettőnek a számunkra fontos lehetséges értékei: <code>GL_LINEAR</code> (ez a bilineáris szűrést bekapcsolja, amit a sugárkövetésnél is használtunk) és <code>GL_NEAREST</code> (ami a legközelebbi éréket választja). Mivel a GL_LINEAR hardwareből van megvalósítva, ezért gyakorlatilag semmivel se lassabb mint a GL_NEAREST, és általában szebb eredményt okoz (mint ahogy most is). Ami miatt azonban ezek fontosak, hogy a GL_TEXTURE_MIN_FILTER beállítása ennek a két értéknek az egyikére kikapcsolja a mipmap-ek használatát. Ennélkül a textúra úgy viselkedne, mintha nem is lenne ott.
Technikailag már ezek a stringek is lehetnének unsigned char tömbök. És három szín van összesen, amit egy byte-on is bőven le lehetne írni. Viszont én az egyszerűség kedvéért inkább maradok a komponensenként egy byte-nál, a 3 komponensre összesen egy byte helyett, és nem kezdek el bitműveletekkel játszani a GL_R3_G3_B2-höz.


A mi esetünkben:
Termesztésen ezekből a szín előállítása egyszerűen két egymásba ágyazott for ciklus. Pl:


<br/> <syntaxhighlight lang="c">
<br/> <syntaxhighlight lang="c">
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
GLubyte texture_data[6][64][3];
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
for(int t = 0; t < 6; t++) {
  for(int i = 0; i < 64; i++) {
    switch(ascii_textures[t][i]) {
      case '*':
        for(int j = 0; j < 3; j++) {
          texture_data[t][i][j] = 0;
        }
        break;
      case '+':
        for(int j = 0; j < 3; j++) {
          texture_data[t][i][j] = 127;
        }
        break;
      default:
        for(int j = 0; j < 3; j++) {
          texture_data[t][i][j] = 255;
        }
        break;
    }
  }
}
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


Megjegyzés: a textúrákat "unbindolni" szoktuk, miután használtuk őket. Ez azt jelenti, hogy a 0 nevű, default textúrát használjuk, ami úgy viselkedik mint a null pointer, a rajta végzett műveletek nem csinálnak semmit. Ez nagyobb programoknál azért ''nagyon'' hasznos, mert ha valaki más hülyeséget csinál, akkor az nem a mi erőforrásunkat rontja el.
Oké, megvan a textúra tartalma, és akkor most ezt hogyan adjuk oda az OpenGLnek?


==== A textúrák használata ====
Először is szükségünk van "egy textúra mutató pointerre/referenciára", egy névre, amivel hivatkozhatunk majd később a textúrára. Ez a referencia a textúrák esetében (és a modern OpenGLbe gyakorlatilag az összes objektum esetében) egy GLuint (unsigned int) lesz. A textúrázással kapcsolatos függvények dokumentációjába ez "a textúra neve"-ként lesz megemlítve.


Ha azt akarjuk, hogy target befolyásolja a rajolást akkor azt a targetet engedélyezni kell pl. a <code>glEnable(GL_TEXTURE_2D);</code> és miután használtuk, ki kell kapcsolni a <code>glDisable(GL_TEXTURE_2D);</code>. Természetesen azt a képet kell bindolnunk, amit éppen használni akarunk. De hova kerüljön a kép az objektumon?
Nekünk hat darab textúra kell, ezért definiáljuk egy 6 elemú GLuint tömböt:


Ennek a specifikálásához textúrakoordinátákat is meg kell adnunk a glBegin()-glEnd() blokkban. Két dimenziós textúrák esetén a textúrakoordináták (0-1) tartománybeli float számpárok, ahol a (0, 0) jelképezi a kép bal alsó sarkát, az (1, 1) pedig a jobb felső sarkot. Azért a (0-1) tartományban használjuk őket, és nem egész pixeleket adunk meg, mert így ha a textúrát átméretezzük, a régi textúrakoordináták továbbra is jók maradnak, hiszen az a teljes mérethez képest relatív értékeket ad meg. Továbbá így használhatunk bilineáris interpolációt. A textúrakoordinátákat két dimenziós textúrák esetén a <code>glTexCoord2f(GLfloat s, GLfloat t)</code> függvénnyel adhatjuk meg.
<br/> <syntaxhighlight lang="c">
GLuint tex[6];
</syntaxhighlight> <br/>


A kocka kirajzolásakor annyi változtatásra szükség van, hogy most minden oldal külön glBegin()-glEnd() blobbkba kerül, mert egy ilyen blokkon belül nem lehet textúrát váltani. Az én megvalósításomban:
Kérjük meg az OpenGL-t, hogy találjon ki nekünk hat darab textúra nevet. Ezek után ő ezekről a nevekről tudni fogja, hogy azok textúra vonatkoznak. Ehhez a <code> glGenTextures(GLsizei n, GLuint *textures); </code> függvényt használhatjuk.


<br/> <syntaxhighlight lang="c">
<br/> <syntaxhighlight lang="c">
void glQuad(const Vector& a, const Vector& b, const Vector& c, const Vector& d) {
glGenTextures(6, tex);  
  glTexCoord2f(0, 0); glVertex3f(a);
</syntaxhighlight> <br/>
  glTexCoord2f(1, 0); glVertex3f(b);
  glTexCoord2f(1, 1); glVertex3f(c);
  glTexCoord2f(0, 1); glVertex3f(d);
}


void drawCube(const Vector& size) {
Fontos, hogy ilyenkor még a textúra nem jön létre. Az csak akkor történik meg, amikor először használni akarjuk (bindoljuk).
    /*      (E)-----(A)
            /|      /|
            / |    / |
          (F)-----(B) |
          | (H)---|-(D)
          | /    | /
          |/      |/
          (G)-----(C)        */


    Vector s = size / 2;
==== A textúrák beállítása ====


    Vector A(+s.x, +s.y, -s.z), B(+s.x, +s.y, +s.z), C(+s.x, -s.y, +s.z), D(+s.x, -s.y, -s.z),
Nyilván egy for ciklusban fogom beállítani őket, csak az 'i'-edik textúra beállítását mondom el.
          E(-s.x, +s.y, -s.z), F(-s.x, +s.y, +s.z), G(-s.x, -s.y, +s.z), H(-s.x, -s.y, -s.z);


    Vector vertices[6][4] = {
Ahhoz, hogy egy textúrán műveletet tudjunk végezni azt bindolni kell egy targethez. Képzeljünk el egy szobát, amibe vannak képkeretek. Az összes képkeret különböző alakú, mindegyikbe másféle képet tudunk rakni. De mindig csak éppen a képkeretben lévő képeket látjuk, csak azokkal tudunk dolgozni. Ezek a képkeretek pontosan ugyanúgy viselkednek mint az OpenGL-be a targetek. Két fontos target van, az egyik az egydimenziós texturáknak (GL_TEXTURE_1D), a másik a kétdimenziósoknak (GL_TEXTURE_2D). A házikban szinte mindig csak a kétdimenziósra lesz szükséged. A hasonlatban amikor egy képet berakunk a képkeretbe, az megegyezik az OpenGL-nél a bindolással. A bindolás után az összes textúra művelet arra a képre fog vonatkozni. Bindolni a <code>glBindTexture(GLenum target, GLuint texture);</code> függvénnyel tudunk.
      {A, B, C, D}, {G, F, E, H}, {A, E, F, B},
      {D, C, G, H}, {B, F, G, C}, {E, A, D, H}
    };


    glEnable(GL_TEXTURE_2D);
<br/> <syntaxhighlight lang="c"> 
   
glBindTexture(GL_TEXTURE_2D, tex[t]);  
    for(int i = 0; i < 6; i++) {
      glBindTexture(GL_TEXTURE_2D, tex[i]); // Ezt semmiképpen se rakd a glBegin - glEnd blokk közé
 
      glBegin(GL_QUADS); {
        glQuad(vertices[i][0], vertices[i][1], vertices[i][2], vertices[i][3]);
      } glEnd();
    }
 
    glBindTexture(GL_TEXTURE_2D, 0);
    glDisable(GL_TEXTURE_2D);
}
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


A teljes forráskód: [[Média:Grafpp4_texturazas.cpp‎|Textúrázott kocka]]
Ezután fel kell töltenünk a textúrát a <code> glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels)</code> függvénnyel.
 
==== A textúrák és a megvilágítás ====
Elősször nézzük meg a paramétereit:
* target - Ez nálad szinte mindig GL_TEXTURE_2D lesz.
* level - Ide 0-t írj. Az OpenGL megengedi, hogy egy textúrának különböző felbontású változatait is feltöltsed, ami sokat javíthat a rendelt kép minőségén. Az alap textúránál kisebb felbontású képeket mipmap-nek hívják. Az 'i'-edik mipmap mérete minden dimenzióban fele az 'i-1'- ediknek. Ide azt írhatod be, hogy éppen hányadik mipmap-et töltöd fel (a 0. mipmap az alaptextúra). A mipmap-ek használata nagyon hasznos tud lenni, de nagyon veszélyes is. Ha ez a feature be van kapcsolva, és nem töltöd fel az összes mipmap-et, vagy valahova rossz felbontásút töltesz fel, akkor a textúra definíció szerint "incomplete" marad, és az incomplete textúrák úgy viselkednek, mintha nem is lennének ott. És ezt nagyon nehéz debuggolni, ezért a tárgyból ajánlott a mipmapek használatának kerülése.
* internalFormat - Ez legyen GL_RGB vagy GL_RGBA. Itt azt kell megadnod, hogy az OpenGL milyen formátumba tárolja a képet. Például a tömörített formátumok nagyon hasznosak, de a házikhoz nem kell használnod őket. Hatékonysági okok miatt a komponensenként egy byteos GL_RGB textúra gyakran GL_RGBA formátumba tárolódik, mert az OpenGL csak 4 byteos egységeket tud hatékonyan írni / olvasni, de ezzel nekünk explicit nem kell foglalkozni, ezt majd a driver eldönti, hogy szerinte hogy jó.
* width / height - Egyértelmű.
* border - Ez mindenképpen legyen 0.
* format - Ez is legyen GL_RGB vagy GL_RGBA. Itt adhatod meg, hogy milyen formátumban töltöd fel a képet. Ez nem feltétlen egyezik meg az internalFormat-al, más formátúmú tárolást is lehet tőle kérni, mint ahogy mi adjuk neki a textúrákat.
* type - Ez legyen GL_UNSIGNED_BYTE. Ez azt adja meg, hogy milyen típussal írtad le a színeket.
* pixels - A textúra elejére mutató pointer. Fontos megjegyezni, hogy a tömbünket úgy értelmezi, hogy a sorok mérete 4 byte-al oszható. Ha nem olyan (pl. 2*2-es RGB textúrát használtunk, akkor a sorokat ki kell egészíteni úgy nevezett "padding"-el, hogy illeszkedjenek a szóhatárra.
 
A mi esetünkben: 


A megvilágítást nem tudjuk a textúrák színeivel számolni. Valójában a rajzolási csővezetékben a textúrázás még időben is később történik, mint a megvilágítás számolása. Amit viszont tudunk tenni, hogy fehér színt állítunk be a megvilágítás számításakor, és az eredményt - hogy az adott felületi pont mennyire világos - megszorozzuk (moduláljuk) a konkrét színnel. Ezt a <code>glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);</code> függvénnyel lehet bekapcsolni, de alapértelmezetten is be van kapcsolva. Ha nem akarunk megvilágítást, csak a textúra színét akarjuk megjeleníteni akkor a GL_MODULATE-et lecserélhetjük GL_REPLACE-re. Ezzel a függvénnyel még rengeteg hasznos dolgot lehetne csinálni, de azok a házikhoz általában nincsenek megengedve.
<br/> <syntaxhighlight lang="c">
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, texture_data[t]);
</syntaxhighlight> <br/>
 
A textúrákkal kapcsolatban nagyon sok beállítási lehetőségünk van, amiket a <code> glTexParameteri(GLenum target, GLenum pname, GLint param); </code> függvénnyel tehetünk meg. Minket két fontos paraméter érdekel, a <code>GL_TEXTURE_MIN_FILTER</code>, és a <code>GL_TEXTURE_MAG_FILTER</code> és mindkettőnek a számunkra fontos lehetséges értékei: <code>GL_LINEAR</code> (ez a bilineáris szűrést bekapcsolja, amit a sugárkövetésnél is használtunk) és <code>GL_NEAREST</code> (ami a legközelebbi éréket választja). Mivel a GL_LINEAR hardwareből van megvalósítva, ezért gyakorlatilag semmivel se lassabb mint a GL_NEAREST, és általában szebb eredményt okoz (mint ahogy most is). Ami miatt azonban ezek fontosak, hogy a GL_TEXTURE_MIN_FILTER beállítása ennek a két értéknek az egyikére kikapcsolja a mipmap-ek használatát. Ennélkül a textúra úgy viselkedne, mintha nem is lenne ott.


Ne feledjük, hogy ahhoz, hogy a megvilágításnak szép eredménye legyen továbbra sem elég, ha a kocka oldali egyetlen quadból állnak. De a textúrázás bemutatásához önmagában az elég volt.
A mi esetünkben:


Példaprogram: [[Média:Graf4_megvilagitas_es_texturazas.cpp‎|Megvilágított, textúrázott kocka]]
<br/> <syntaxhighlight lang="c">
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
</syntaxhighlight> <br/>


{|
Megjegyzés: a textúrákat "unbindolni" szoktuk, miután használtuk őket. Ez azt jelenti, hogy a 0 nevű, default textúrát használjuk, ami úgy viselkedik mint a null pointer, a rajta végzett műveletek nem csinálnak semmit. Ez nagyobb programoknál azért ''nagyon'' hasznos, mert ha valaki más hülyeséget csinál, akkor az nem a mi erőforrásunkat rontja el.
|-
| http://i.imgur.com/xT1d1ur.png || http://i.imgur.com/XE7dZAl.png
|}


==== Nem kettőhatvány méretű textúrák ====
==== A textúrák használata ====


Gyakran felmerülhet az igény nem kettőhatvány méretű textúrára. Például az előző példák kockájából egy dobókockát akarunk csinálni. Egy ilyet:
Ha azt akarjuk, hogy target befolyásolja a rajolást akkor azt a targetet engedélyezni kell pl. a <code>glEnable(GL_TEXTURE_2D);</code> és miután használtuk, ki kell kapcsolni a <code>glDisable(GL_TEXTURE_2D);</code>. Természetesen azt a képet kell bindolnunk, amit éppen használni akarunk. De hova kerüljön a kép az objektumon?
 
Ennek a specifikálásához textúrakoordinátákat is meg kell adnunk a glBegin()-glEnd() blokkban. Két dimenziós textúrák esetén a textúrakoordináták (0-1) tartománybeli float számpárok, ahol a (0, 0) jelképezi a kép bal alsó sarkát, az (1, 1) pedig a jobb felső sarkot. Azért a (0-1) tartományban használjuk őket, és nem egész pixeleket adunk meg, mert így ha a textúrát átméretezzük, a régi textúrakoordináták továbbra is jók maradnak, hiszen az a teljes mérethez képest relatív értékeket ad meg. Továbbá így használhatunk bilineáris interpolációt. A textúrakoordinátákat két dimenziós textúrák esetén a <code>glTexCoord2f(GLfloat s, GLfloat t)</code> függvénnyel adhatjuk meg.
 
A kocka kirajzolásakor annyi változtatásra szükség van, hogy most minden oldal külön glBegin()-glEnd() blobbkba kerül, mert egy ilyen blokkon belül nem lehet textúrát váltani. Az én megvalósításomban:
 
<br/> <syntaxhighlight lang="c">
void glQuad(const Vector& a, const Vector& b, const Vector& c, const Vector& d) {
  glTexCoord2f(0, 0); glVertex3f(a);
  glTexCoord2f(1, 0); glVertex3f(b);
  glTexCoord2f(1, 1); glVertex3f(c);
  glTexCoord2f(0, 1); glVertex3f(d);
}
 
void drawCube(const Vector& size) {
    /*      (E)-----(A)
            /|      /|
            / |    / |
          (F)-----(B) |
          | (H)---|-(D)
          | /    | /
          |/      |/
          (G)-----(C)        */
 
    Vector s = size / 2;
 
    Vector A(+s.x, +s.y, -s.z), B(+s.x, +s.y, +s.z), C(+s.x, -s.y, +s.z), D(+s.x, -s.y, -s.z),
          E(-s.x, +s.y, -s.z), F(-s.x, +s.y, +s.z), G(-s.x, -s.y, +s.z), H(-s.x, -s.y, -s.z);
 
    Vector vertices[6][4] = {
      {A, B, C, D}, {G, F, E, H}, {A, E, F, B},
      {D, C, G, H}, {B, F, G, C}, {E, A, D, H}
    };
 
    glEnable(GL_TEXTURE_2D);
   
    for(int i = 0; i < 6; i++) {
      glBindTexture(GL_TEXTURE_2D, tex[i]); // Ezt semmiképpen se rakd a glBegin - glEnd blokk közé
 
      glBegin(GL_QUADS); {
        glQuad(vertices[i][0], vertices[i][1], vertices[i][2], vertices[i][3]);
      } glEnd();
    }
 
    glBindTexture(GL_TEXTURE_2D, 0);
    glDisable(GL_TEXTURE_2D);
}
</syntaxhighlight> <br/>
 
A teljes forráskód: [[Média:Grafpp4_texturazas.cpp‎|Textúrázott kocka]]
 
==== A textúrák és a megvilágítás ====
 
A megvilágítást nem tudjuk a textúrák színeivel számolni. Valójában a rajzolási csővezetékben a textúrázás még időben is később történik, mint a megvilágítás számolása. Amit viszont tudunk tenni, hogy fehér színt állítunk be a megvilágítás számításakor, és az eredményt - hogy az adott felületi pont mennyire világos - megszorozzuk (moduláljuk) a konkrét színnel. Ezt a <code>glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);</code> függvénnyel lehet bekapcsolni, de alapértelmezetten is be van kapcsolva. Ha nem akarunk megvilágítást, csak a textúra színét akarjuk megjeleníteni akkor a GL_MODULATE-et lecserélhetjük GL_REPLACE-re. Ezzel a függvénnyel még rengeteg hasznos dolgot lehetne csinálni, de azok a házikhoz általában nincsenek megengedve.
 
Ne feledjük, hogy ahhoz, hogy a megvilágításnak szép eredménye legyen továbbra sem elég, ha a kocka oldalai egyetlen quadból állnak. De a textúrázás bemutatásához önmagában az elég volt.
 
Példaprogram: [[Média:Graf4_megvilagitas_es_texturazas.cpp‎|Megvilágított, textúrázott kocka]]
 
{|
|-
| http://i.imgur.com/xT1d1ur.png || http://i.imgur.com/XE7dZAl.png
|}
 
==== Nem kettőhatvány méretű textúrák ====
 
Gyakran felmerülhet az igény nem kettőhatvány méretű textúrára. Például az előző példák kockájából egy dobókockát akarunk csinálni. Egy ilyet:


http://i.imgur.com/3benYFM.png
http://i.imgur.com/3benYFM.png


De ez a mintázat egy 7*7-es textúrát igényel, pl az öt így néz ki:
De ez a mintázat egy 7*7-es textúrát igényel, pl az öt így néz ki:
 
<br/> <syntaxhighlight lang="c">
"......."
".*...*."
"......."
"...*..."
"......."
".*...*."
"......."
</syntaxhighlight> <br/>
 
A hét köztudottan nem kettőhatvány. Ha megpróbáljuk ugyanúgy használni, mint ahogy a 8x8-as textúrával tettük, vajon működni fog?
 
Természetesen nem...
 
http://i.imgur.com/NsKm5AK.png
 
De ennek a problémának az eddigi ismereteink alapján nem szabadna meglepőnek lennie, még akkor se ha nem tudtuk, hogy csak kettőhatvány textúrákat lehet használni.
Természetesen itt azzal van a gond, hogy az OpenGL szóhatárra illeszkedő sorokat vár, viszont mi 7*3 = 21 byteos sorokat töltöttünk fel. Az OpenGL viszont úgy fogja értelmezni a textúránkat, mintha az 7*8-as lenne, aminek hatására jó pár szín elcsúszik, és egy kevés memóriaszemét is kerül a színek közé (az utolsó sorba). Ennek megoldására én három egyszerű megoldást tudok, és az egyszerű megoldásokon kívül egy olyat amit a beadón is lehet használni.
Az egyszerű megoldások:
* Írjuk be az onInitbe az alábbi sort: <code>glPixelStorei(GL_UNPACK_ALIGNMENT, 1);</code> - Ennek hatására az OpenGL egy bájtos határra ileszkedő sorokat vár.
* Minden sor végére rakjunk be egy plusz karaktert, és egy 7*8-as tömbre mutató pointer adjunk oda a glTexImage2D-nek. De fontos, hogy továbbra is azt mondjuk neki, hogy 7x7-es textúrát töltünk fel. Ez valószínűleg a legnehezebben emészthető megoldás, úgyhogy erről töltöttem fel kódot: [[Média:Grafpp4_7x8_dobokocka.cpp‎|Dobókocka 7x8-as textúrával]].
* RGB textúra helyett használjunk RGBA textúrát.
 
De várjunk csak, nem arról volt szó, hogy az OpenGL csak kettőhatvány méretű textúrákkal tud dolgozni? De.. ez szabvány szerint igaz... csak a mi gépünk nem tud róla... A 90-es években még problémát okozott a videókártyáknak a nem kettőhatvány méretű textúrák kezelése, de már több mint tíz éve tetszőleges méretű textúrával is ugyanolyan hatékonyan tud dolgozni az összes videókártya. Ez viszont sovány vigasz a beadón... ahol a nem kettőhatvány méretű textúrák nincsenek implementálva.
 
A beadón működő megoldás:
* Egészítsük ki 8x8-asra a textúrát. A szélére másoljuk be a legközelebbi valid szomszédokat, mert bilineáris szűréskor ezeknek az értékeknek is látjuk a hatását.
* Buheráljuk meg a textúrakoordinátákat, úgy, hogy 0-tól 7/8-ig menjenek. Ezzel persze elvesztjük azt az előnyt, hogy a textúrakoordináták függetlenek magának a textúrának a paramétereitől, és a textúra lecserélése a textúrakoordináták lecserélését is magával vonja.
* Ennek a megoldásnak a kódja: [[Média:Grafpp4_8x8_dobokocka.cpp‎|Dobókocka 8x8-as textúrával]]
 
{|
|-
| http://i.imgur.com/vGETS3t.png || http://i.imgur.com/3benYFM.png
|}
 
Persze a dobókocka egyeseknek jobban tetszhet GL_NEAREST szűréssel, a GL_LINEAR helyett. Ez teljesen ízlés kérdése, amúgy így néz ki bilineáris szűrés nélkül:
 
{|
|-
| http://i.imgur.com/N3uYYBc.jpg || http://i.imgur.com/Ai76YUE.jpg
|}
 
=== Egyszerű kamera ===
 
A végleges házikban a kamera általában valamilyen mozgatható objektumot követ. Ennek az implementálása nagyon egyszerű, minden rajzolás elején egységmátrixot töltünk a GL_MODELVIEW-ba, majd a gluLookAt(objektum pozíciója, objektum pozíciója + orientációja, felfele) függvényhívással megoldható.
 
Viszont ezzel nehézkes a debuggolás, nem lehet igazán jól körülnézni, és valós játékokban is ritka az ennyire egyszerű kamera.
 
Én egy egyszerű, szabadon-repülő kamera egy implementációjához adok ötletet, ami sokat segíthet a debuggoláshoz (sokszor csak egy nézetből nézve egy jelenetet nem lehet eldönteni, hogy az jó-e).
 
Kétféle input érdekel minket, a billentyűlenyomások (W,A,S,D), és az egér mozgatása (úgy, hogy közbe az egyik egérgomb le van nyomva).
 
A billentyűlenyomásokat nem kezelhetjük egyszerűen az onKeyboard-ban, ez akkor generál eseményeket, amikor egy karaktert begépelnénk, ami pl megtörténik először a billentyű lenyomásakor, aztán jelentős ideig (kb. 0.3 - 0.5 sec) nem generálódik újabb esemény, majd után másodpercenként kb. 10-20 karakterbeütés-t generál. Ez pl egy karakter irányításához teljesen használhatatlan, helyette inkább tároljuk el, hogy az egyes billentyűk lenyomott állapotban vannak-e.
 
<br/> <syntaxhighlight lang="c">
enum ControllKeys {W, A, S, D, keys_num};
bool keys_down[keys_num];
 
void onKeyboard(unsigned char key, int, int) {
  switch(key) {
    case 'w': case 'W':
      keys_down[W] = true;
      break;
    case 's': case 'S':
      keys_down[S] = true;
      break;
    case 'a': case 'A':
      keys_down[A] = true;
      break;
    case 'd': case 'D':
      keys_down[D] = true;
      break;
  }
}
 
void onKeyboardUp(unsigned char key, int, int) {
    switch(key) {
    case 'w': case 'W':
      keys_down[W] = false;
      break;
    case 's': case 'S':
      keys_down[S] = false;
      break;
    case 'a': case 'A':
      keys_down[A] = false;
      break;
    case 'd': case 'D':
      keys_down[D] = false;
      break;
  }
}
</syntaxhighlight> <br/>
 
Ahhoz, hogy ezt fel tudjuk használni, a kamerának tudnia kell, hogy hol van, merre néz, és milyen gyorsan tud mozogni.
 
<br/> <syntaxhighlight lang="c">
  Vector fwd, pos;
  const float speed;
 
  void updatePos(float dt) {
    Vector up = Vector(0, 1, 0), right = cross(fwd, up).normalize();
    up = cross(right, fwd).normalize();
 
    if(keys_down[W] && !keys_down[S]) {
      pos += fwd * speed * dt;
    } else if(keys_down[S] && !keys_down[W]) {
      pos -= fwd * speed * dt;
    }
 
    if(keys_down[D] && !keys_down[A]) {
      pos += right * speed * dt;
    } else if(keys_down[A] && !keys_down[D]) {
      pos -= right * speed * dt;
    }
  }
</syntaxhighlight> <br/>
 
Ennek a meghívását pl az onIdle-ben tehetjük meg az alábbi módon:
 
<br/> <syntaxhighlight lang="c">
void onIdle() {
  static float last_time = glutGet(GLUT_ELAPSED_TIME);
  float time = glutGet(GLUT_ELAPSED_TIME);
  float dt = (time - last_time) / 1000.0f;
  last_time = time;
 
  camera.updatePos(dt);
  glutPostRedisplay();
}
</syntaxhighlight> <br/>
 
Továbbá az egér mozgatást is kezelnünk kell.
 
<br/> <syntaxhighlight lang="c">
  const float mouse_speed;
 
  void updateDir(int dx, int dy) {
    Vector y_axis = Vector(0, 1, 0), right = cross(fwd, y_axis).normalize();
    Vector up = cross(right, fwd).normalize();
 
    // Ha teljesen felfele / lefele néznénk, akkor ne forduljon át a kamera
    float dot_up_fwd = dot(y_axis, fwd);
    if(dot_up_fwd > 0.95f && dy > 0) {
      dy = 0;
    }
    if(dot_up_fwd < -0.95f && dy < 0) {
      dy = 0;
    }
 
    // Módosítsuk az nézeti irányt
    fwd += mouse_speed * (right * dx + up * dy);
    fwd = fwd.normalize();
  }
</syntaxhighlight> <br/>
 
Ennek a meghívására pedig az onMouseMotion függvény alakalmas:
 
<br/> <syntaxhighlight lang="c">
int last_x, last_y;
void onMouse(int, int, int x, int y) {
  last_x = x;
  last_y = y;
}
 
void onMouseMotion(int x, int y) {
  camera.updateDir(x-last_x, last_y-y);
  last_x = x;
  last_y = y;
}
</syntaxhighlight> <br/>
 
A kamera beállítása ezek után már csak egyetlen függvényhívás:


<br/> <syntaxhighlight lang="c">
<br/> <syntaxhighlight lang="c">
"......."
gluLookAt(pos.x, pos.y, pos.z, pos.x+fwd.x, pos.y+fwd.y, pos.z+fwd.z, 0, 1, 0);
".*...*."
"......."
"...*..."
"......."
".*...*."
"......."
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


A hét köztudottan nem kettőhatvány. Ha megpróbáljuk ugyan úgy használni, mint ahogy a 8x8-as textúrával tettük, vajon működni fog?
Egy ilyesfajta kamera már a közepesen komplex jelenetek összeállításban is nagyon sokat tud segíteni.  
 
Természetesen nem...
 
http://i.imgur.com/NsKm5AK.png
 
De ennek a problémának az eddigi ismereteink alapján nem szabadna meglepőnek lennie, még akkor se ha nem tudtuk, hogy csak kettőhatvány textúrákat lehet használni.
Természetesen itt azzal van a gond, hogy az OpenGL szóhatárra illeszkedő sorokat vár, viszont mi 7*3 = 21 byteos sorokat töltöttünk fel. Az OpenGL viszont úgy fogja értelmezni a textúránkat, mintha az 7*8-as lenne, aminek hatására jó pár szín elcsúszik, és egy kevés memóriaszemét is kerül a színek közé (az utolsó sorba). Ennek megoldására én három egyszerű megoldást tudok, és az egyszerű megoldásokon kívül egy olyat amit a beadón is lehet használni.
Az egyszerű megoldások:
* Írjuk be az onInitbe az alábbi sort: <code>glPixelStorei(GL_UNPACK_ALIGNMENT, 1);</code> - Ennek hatására az OpenGL egy bájtos határra ileszkedő sorokat vár.
* Minden sor végére rakjunk be egy plusz karaktert, és egy 7*8-as tömbre mutató pointer adjunk oda a glTexImage2D-nek. De fontos, hogy továbbra is azt mondjuk neki, hogy 7x7-es textúrát töltünk fel. Ez valószínűleg a legnehezebben emészthető megoldás, úgyhogy erről töltöttem fel kódot: [[Média:Grafpp4_7x8_dobokocka.cpp‎|Dobókocka 7x8-as textúrával]].
* RGB textúra helyett használjunk RGBA textúrát.
 
De várjunk csak, nem arról volt szó, hogy az OpenGL csak kettőhatvány méretű textúrákkal tud dolgozni? De.. ez szabvány szerint igaz... csak a mi gépünk nem tud róla... A 90-es években még problémát okozott a videókártyáknak a nem kettőhatvány méretű textúrák kezelése, de már több mint tíz éve tetszőleges méretű textúrával is ugyan olyan hatékonyan tud dolgozni az összes videókártya. Ez viszont sovány vigasz a beadón... ahol a nem kettőhatvány méretű textúrák nincsenek implementálva.
 
A beadón működő megoldás:
* Egészítsük ki 8x8-asra a textúrát. A szélére másoljuk be a legközelebbi valid szomszédokat, mert bilineáris szűréskor ezeknek az értékeknek is látjuk a hatását.
* Buheráljuk meg a textúrakoordinátákat, úgy, hogy 0-tól 7/8-ig menjenek. Ezzel persze elvesztjük azt az előnyt, hogy a textúrakoordináták függetlenek magának a textúrának a paramétereitől, és a textúra lecserélése a textúrakoordináták lecserélését is magával vonja.
* Ennek a megoldásnak a kódja: [[Média:Grafpp4_8x8_dobokocka.cpp‎|Dobókocka 8x8-as textúrával]]
 
{|
|-
| http://i.imgur.com/vGETS3t.png || http://i.imgur.com/3benYFM.png
|}


== Az ötödik házihoz kellő elmélet ==
Az én implementációim: [[Média:Grafpp4_kamera.cpp‎|Kamera]]


A korábbi ötödik házikhoz lényegében nem kellett új elmélet. Azokhoz az első házi dinamikáját és a negyedik házi rajzolását kellett felhasználni, esetleg egy kis plusz fizikát vagy matekot (pl. inverz kinematika). Ezek viszont nem olyanok, amit valaki egyszer kitalált, és szinte esélyed sincs, hogy magadnak levezesd, mint pl. a Filmic tone map, vagy a Fresnel egyenletek. Ezeket viszonylag könnyedén magadnak is meg tudod oldani, és általában ez a kihívás még élvezetes is szokott lenne. Az ötödik házival kapcsolatban a levlistán keresztül biztos, hogy sok segítséget fogsz kapni, ha elakadsz, de én, a konkrét feladat ismerete nélkül csak annyit tudok mondani, hogy jó programozást!
[[File:Graftutorial_kamera_anim.gif]]


== Utóhang ==
== Utóhang ==
2 121. sor: 2 325. sor:


-----
-----
[https://wiki.sch.bme.hu/Szerkeszt%C5%91:Rohamcsiga RohamCsiga] - 2014.01.
[https://wiki.sch.bme.hu/Szerkeszt%C5%91:Rohamcsiga Csala Tamás] - 2014.01.




[[Category:Infoalap]]
[[Category:Infoalap]]