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

Rohamcsiga (vitalap | szerkesztései)
 
(36 közbenső módosítás, amit 5 másik szerkesztő végzett, nincs mutatva)
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.
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 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>.
* 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 2 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:
591. sor: 595. sor:
<br/> <syntaxhighlight lang="c">  
<br/> <syntaxhighlight lang="c">  
Vector pos_on_plane = Vector(
Vector pos_on_plane = Vector(
   (x + 0.5f - Screen::width/2) / (Screen::width/2),
   (x - Screen::width/2) / (Screen::width/2),
   (y + 0.5f - Screen::height/2) / (Screen::height/2),  
   (y - Screen::height/2) / (Screen::height/2),  
   0
   0
);
);
614. sor: 618. sor:


Megjegyzések az algoritmussal kapcsolatban:
Megjegyzések az algoritmussal kapcsolatban:
* 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) = 90 fok. Egy nem túl arányos rajz arról, hogy ez hogy jött ki:
* 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:


http://i.imgur.com/gC7f6l1.png
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, de az ernyő-kamera távolság is változtathatnám. Az arány amit akarunk az a tan(fov/2).
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.  
* 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.  


646. sor: 650. sor:
   void capturePixel(float x, float y) {
   void capturePixel(float x, float y) {
     Vector pos_on_plane = Vector(
     Vector pos_on_plane = Vector(
       (x - Screen::width/2) / (Screen::width/2),
       (x + 0.5f - Screen::width/2) / (Screen::width/2),
       (y - Screen::height/2) / (Screen::height/2),  
       (y + 0.5f - Screen::height/2) / (Screen::height/2),  
       0
       0
     );
     );
659. sor: 663. sor:
</syntaxhighlight> <br/>
</syntaxhighlight> <br/>


Megjegyzés: én általában nem ilyen kamerát szoktam használni, de ez ugyan azt az eredményt adja, mint amit a 4. meg 5. háziban fogunk kapni, a gluLookAt() függvény segítségével.
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.
739. 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.
846. 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:
894. 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 sugárkövetés eredménye(baloldalt), összehasonlítva azzal, amit az OpenGL tud (glutSolidCube, jobboldalt), 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:  
907. sor: 911. sor:
==== 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.
960. 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/16ohvtS.jpg <br/>
http://i.imgur.com/16ohvtS.jpg <br/>
970. 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
1 006. 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 030. 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 036. 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 090. 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 119. 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 146. 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 152. 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/uCHgf9g.jpg
http://i.imgur.com/uCHgf9g.jpg
1 193. sor: 1 197. sor:
http://i.imgur.com/GLBoT7h.jpg
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/M09ZnHA.jpg
http://i.imgur.com/M09ZnHA.jpg
1 214. 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/jwtHLsb.jpg
http://i.imgur.com/jwtHLsb.jpg
1 262. 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/vJKsR53.jpg
1 303. 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 328. 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 338. 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:
1 356. 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/BxeEztT.jpg
http://i.imgur.com/BxeEztT.jpg
1 391. 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 436. 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 465. 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 490. 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 535. 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 553. 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 617. 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 773. 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.


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:
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.


<br/> <syntaxhighlight lang="c">
Viszont egy vektor önmagában nem jelent semmit, amíg meg nem mondjuk, hogy melyik koordináta-rendszerben van értelmezve.  
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.
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.


http://i.imgur.com/dzjcMuZ.png
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.  


<br/>
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.


Viszont ha az első lapokat dobjuk el a <code>glCullFace(GL_FRONT);</code> függvénnyel, akkor ezt kapjuk:  
Például:


http://i.imgur.com/OsxWbsw.png
<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);
}


=== Színek ===
void onDisplay() {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


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.
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
 
  // ...
  setCamera();
 
  // rajzolás
}
</syntaxhighlight> <br/>


Az egyes színeket állító függvény a <code>glMaterialfv(GLenum face, GLenum pname, const GLfloat *params);</code>
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.


A leggyakoribb paraméterei:
http://i.imgur.com/iZJlFr9.gif
* 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>
* 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.  
* Ezt a GL_SHININESS állítására szoktuk használni.


=== Tesszelláció ===
http://i.imgur.com/cUPVzeT.gif


Avagy hogyan bontsuk fel primitívekre (tipikusan háromszögekre) az objektumainkat.
* 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.


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:
http://i.imgur.com/UZT2nMr.gif
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
=== Hátsólap eldobás ===


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.
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.


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.
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:
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" ú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ő:


http://upload.wikimedia.org/wikipedia/hu/2/22/Gomb-pol.jpg
<br/> <syntaxhighlight lang="c">
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/>


<br/>
Ugyanazt látjuk, mint a három sor nélkül, de fele annyi erőfeszítéssel.


* 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://i.imgur.com/dzjcMuZ.png
 
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:
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.
** GL_FRONT
* Az OpenGL-be a <code>glShadeModel(GL_SMOOTH);</code> függvénnyel lehet bekapcsolni, hogy figyelembe vegye az árnyaló normálokat.
** GL_BACK
* É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.
** GL_FRONT_AND_BACK
* A példaprogram: [[Média:Grafpp4_gomb_sima_arnyalassal.cpp|Gömb sima árnyalással]]
* A 'pname' értékei:
** GL_AMBIENT
** GL_DIFFUSE
** GL_AMBIENT_AND_DIFFUSE
** GL_SPECULAR
** GL_EMISSION


A <code>GL_FLAT</code> árnyékolás (baloldalt) összehasonlítása <code>GL_SMOOTH</code> árnyékolással (jobboldalt)
Illetve egy másik hasznos függvény a <code>glMaterialf(GLenum face, GLenum pname, GLfloat param)</code>
* Ezt a GL_SHININESS állítására szoktuk használni.


{|
=== Tesszelláció ===
|-
| http://i.imgur.com/9luiuQI.png || http://i.imgur.com/IWMFbU4.png
|}


=== A trafók vs az árnyaló normálok ===
Avagy hogyan bontsuk fel primitívekre (tipikusan háromszögekre) az objektumainkat.


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.
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".


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:
http://i.imgur.com/IBsec5K.png


http://i.imgur.com/yy8eRvZ.png
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.


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?
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).


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:
http://upload.wikimedia.org/wikipedia/hu/2/22/Gomb-pol.jpg
 
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/YGa1jJw.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/>


http://i.imgur.com/RG9NlDC.png
* A két gömb összehasonlítva:


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.
http://i.imgur.com/qNnAKse.png


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.
* 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.


=== A textúrázás ===
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.


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.  
http://i.imgur.com/KCJ9s0Q.jpg


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.
=== Az árnyaló normálok ===


==== A textúrázás alapjai ====
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]]


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:
A <code>GL_FLAT</code> árnyékolás (baloldalt) összehasonlítása <code>GL_SMOOTH</code> árnyékolással (jobboldalt)


{|
{|
|-
|-
| http://i.imgur.com/Bh5G5f8.png || http://i.imgur.com/0lSPkjh.png
| http://i.imgur.com/9luiuQI.png || http://i.imgur.com/IWMFbU4.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 teszik, a float értékkészletének nagy részét nem is használják ki.
=== A trafók vs 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):  
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.
 
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:
 
http://i.imgur.com/yy8eRvZ.png
 
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?
 
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:
 
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.
 
=== A textúrázás ===
 
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 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.
 
==== A textúrázás alapjai ====
 
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:
 
{|
|-
| 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) 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.
 
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):  


<br/> <syntaxhighlight lang="c">
<br/> <syntaxhighlight lang="c">
2 069. sor: 2 120. sor:
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.
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 oldali egyetlen quadból állnak. De a textúrázás bemutatásához önmagában az elég volt.
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]]
Példaprogram: [[Média:Graf4_megvilagitas_es_texturazas.cpp‎|Megvilágított, textúrázott kocka]]
2 096. sor: 2 147. sor:
</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?  
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...
Természetesen nem...
2 109. sor: 2 160. sor:
* RGB textúra helyett használjunk RGBA textúrát.
* 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.
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:
A beadón működő megoldás:
2 121. sor: 2 172. sor:
|}
|}


== Az ötödik házihoz kellő elmélet ==
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">
gluLookAt(pos.x, pos.y, pos.z, pos.x+fwd.x, pos.y+fwd.y, pos.z+fwd.z, 0, 1, 0);
</syntaxhighlight> <br/>
 
Egy ilyesfajta kamera már a közepesen komplex jelenetek összeállításban is nagyon sokat tud segíteni.
 
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 130. 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]]