„Számítógépes grafika házi feladat tutorial” változatai közötti eltérés
| 264. sor: | 264. sor: | ||
=== 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. | |||
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ó. | |||
Az OpenGL két mátrixot ad nekünk, amiket módosíthatunk, és amivel az összes kirajzolandó pontot mindig beszorozza helyettünk. Az egyik a GL_MODELVIEW, a másik a GL_PROJECTION. A GL_MODELVIEW-val mozgathatunk objektumokat egymáshoz képest, és itt tudjuk megadni, hogy hogyan kell transzformálni a világot, hogy az origóban lévő, -z irányba néző képzeletbeli kamera azt lássa, amit meg akarunk jeleníteni. A GL_PROJECTION pedig azt adja meg, hogy a kamerával hogyan kell fényképezni. | |||
Két dimenzióban a két mátrix különválasztása gyakorlatilag fölösleges, ennek csak 3D-be lesz szerepe, majd a megvilágításkor. Két dimenzióban kényelmesebb a GL_PROJECTION-ra bízni a kamera mozgatást is, és a GL_MODELVIEW-t pedig meghagyni csak az objektumok közötti transzformációk leírására. | |||
Projekció 2D-ben: a fényképezés módja nagyon egyszerű, egyszerűen eldobjuk a 'z' koordinátát, és az x-y pozíció alapján rajzolunk. Vagy legalábbis ezt csináltuk eddig, amikor a GL_PROJECTION egységmátrix volt, de az NDC koordináta-rendszerben nem volt kényelmes dolgozni. Itt viszont lehetőséget kapunk saját koordináta-rendszer megválasztására, ahol az egység lehet pl. 1 méter, és a kamera pedig mondjuk követhet egy karaktert egy játékban. | |||
Példaprogram: [[Média:Grafpp_sidescroller.cpp|Sidescroller]] | |||
<br/> <syntaxhighlight lang="c"> | <br/> <syntaxhighlight lang="c"> | ||
| 292. sor: | 296. sor: | ||
Az eredménye: | Az eredménye: | ||
http://i.imgur.com/S3m5Lmv.gif | http://i.imgur.com/S3m5Lmv.gif | ||
=== Transzformációk === | === Transzformációk === | ||
A GL_MODELVIEW egyik legfontosabb használata, hogy segítségével könnyebben tudjuk elhelyezni az objektumokat a világban. Például ha van egy összetett, mondjuk 10 000 háromszögből álló alakzatunk, akkor annak elforgatását manuálisan úgy tudnánk megoldani, hogy az alakzat összes pontján elvégzünk valami undorító trigonometrikus képletet. Egy másik lehetőség, hogy a GL_MODELVIEW segítségével az egész világot elforgatjuk az ellenkező irányba, kirajzoljuk az alakzatot normál állapotában, majd visszaforgatjuk a világot. Az utóbbi megoldás első ránézésre bonyolultabbnak tűnik, de mindössze 2 sor kód. | |||
A világ transzformálásához használható függvények: | |||
* <code> glTranslatef(GLfloat x, GLfloat y, GLfloat z); </code> | |||
* <code> glRotatef(GLfloat angle, 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 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. | |||
[[Média:Grafpp_transzf_sorrend.cpp|Példa a transzformációk sorrendjére]]: | |||
<br/> <syntaxhighlight lang="c"> | <br/> <syntaxhighlight lang="c"> | ||
glTranslatef(2.7f, -3.1f, 0.0f); | glTranslatef(2.7f, -3.1f, 0.0f); | ||
| 315. sor: | 322. sor: | ||
Ami a koordináta-rendszerrel történik: | Ami a koordináta-rendszerrel történik: | ||
http://i.imgur.com/gUqk4pi.gif | http://i.imgur.com/gUqk4pi.gif | ||
- Intuitív, pontosan az történik, mint amit a kódról első ránézésre hinnénk, hogy csinál, az origó a transzformáció után a (2.7, -3.1) pontba kerül, a nagyítás az x tengely mentén 2, az y tengely mentén 2.5. <br/> <br/> | - Intuitív, pontosan az történik, mint amit a kódról első ránézésre hinnénk, hogy csinál, az origó a transzformáció után a (2.7, -3.1) pontba kerül, a nagyítás az x tengely mentén 2, az y tengely mentén 2.5. <br/> <br/> | ||
[[Média:Grafpp_transzf_sorrend2.cpp|Egy másik lehetséges sorrend]]: | |||
<br/> <syntaxhighlight lang="c"> | <br/> <syntaxhighlight lang="c"> | ||
glScalef(2, 2.5f, 1); | glScalef(2, 2.5f, 1); | ||
| 328. sor: | 335. sor: | ||
Ami a koordinátarendszerrel történik: | Ami a koordinátarendszerrel történik: | ||
http://i.imgur.com/XQcsrHs.gif | http://i.imgur.com/XQcsrHs.gif | ||
- Egyáltalán nem intuitív, az origó a (7.817, 3.185) pontba kerül, és a két tengely nagyítása 2.43 és 2.084. Ezeknek az értékeknek semmi köze a kódban szereplő konstansokhoz!! <br/> <br/> | |||
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. | |||
=== Matrix stack === | === Matrix stack === | ||
Az OpenGL két függvényt ad amivel a matrix stack-et használhatjuk: | |||
* <code>glPushMatrix()</code>. Ez a jelenleg aktív mátrixot (<code>GL_PROJECTION</code> vagy <code>GL_MODELVIEW</code>) elmenti annak a stackjébe. | |||
* <code> glPopMatrix()</code>. Ez a jelenleg aktív mátrix stackjéből a legutóbb elmentett mátrixot visszaállítja. A következő <code>glPopMatrix()</code> mátrix az az előtt elmentett mátrixot állítja vissza. | |||
Megjegyzések: | |||
* A <code>GL_MODELVIEW</code> stack mélysége legalább 32, a <code>GL_PROJECTION</code> mátrixé pedig legalább 2. Ez azt jelenti, hogy lehet, hogy nálad mindkét érték tízszer ekkora, de ha hordozható kódot akarsz írni, ekkor ennél nagyobb számokat nem feltételezhetsz. | |||
* Overflow / underflow esetén a mátrix értéke meghatározatlan lesz. | |||
Például: | |||
<br/> <syntaxhighlight lang="c"> | <br/> <syntaxhighlight lang="c"> | ||
glMatrixMode(GL_MODELVIEW); | glMatrixMode(GL_MODELVIEW); | ||
| 360. sor: | 366. sor: | ||
rajzolas2(); // It a világ már nincs elforgatva. | rajzolas2(); // It a világ már nincs elforgatva. | ||
</syntaxhighlight> <br/> | </syntaxhighlight> <br/> | ||
A mátrix stack hierarchikusan felépülő testek rajzolását nagy mértékben megkönnyíti. Hierarchikus test pl: az emberi kéz. Ha a felkarod elforgatod a vállad körül, akkor azzal az alkarod, a csuklód és az ujjaid is elmozdulnak. Minden ízület transzformációja befolyásolja az összes csontot, ami "belőle nő ki", és az összes csontot, ami a belőle kinövő csontokból nő ki, rekurzívan. Ez természetesen egy fa struktúrát jelent, minden ízületnek van egy saját transzformációja, és változó számú gyereke. Ez leprogramozni nem lehetetlen, sőt nem is nehéz, de nem is kifejezetten izgalmas, viszont a mátrix stack segítségével ez nagyon egyszerűen megoldható. | |||
Egy emberi kéz kirajzolása pszeudokóddal, hengerekből, feltételezve, hogy origó középpontú, egység hosszú, x-el párhuzamos főtengelyű hengert tudunk rajzolni a "henger kirajzolása" utasítással: | |||
<pre> | <pre> | ||
glPushMatrix(); { | glPushMatrix(); { | ||
| 391. sor: | 397. sor: | ||
</pre> | </pre> | ||
Sajnos ezt 2D-be nem lehet jól megmutatni, ezért kivételesen az ehhez kapcsolódó példaprogram 3D-s lesz. Technikailag a 3D rajzolást rábízzuk a glut-ra. Kizárólag a <code> glutSolidCube(GLdouble size); </code> függvényt fogjuk használni a rajzoláshoz. Ez a függvény - nem meglepő módon - egy 'size' élhosszúságú kockát rajzol ki. | |||
Próbáld ki nyugodtan: [[Média:Grafpp_glutSolidCube.cpp|glutSolidCube]] | |||
<div style="text-align:left;margin:0px auto;"> | <div style="text-align:left;margin:0px auto;"> | ||
http://i.imgur.com/PA2A3eQ.png | http://i.imgur.com/PA2A3eQ.png | ||
</div><br/> | </div><br/> | ||
Ezt felhasználva a példaprogram: [[Média:Grafpp_robot_kar.cpp|Robot kar]] | |||
http://i.imgur.com/tpAuxBa.gif | |||
A program irányítása: | |||
* 'q' - Ujjak szétnyitása, 'a' - Ujjak összezárása | |||
* 'w' - Alkar felemelése, 's' - Alkar lehajtása | |||
* 'e' - Felkar felemelése, 'd' - Felkar lehajtása | |||
* 'r' - Az alap forgatása jobbra, 'f' - Az alap forgatása balra | |||
A releváns kód, ebből a programból: | |||
<br/> <syntaxhighlight lang="c"> | <br/> <syntaxhighlight lang="c"> | ||
| 522. sor: | 532. sor: | ||
</syntaxhighlight> <br/> | </syntaxhighlight> <br/> | ||
Az | === 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. | |||
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ék egyik legfontosabb tulajdonsága a folytonosság mértéke, vagyis, hogy mekkora az a legnagyobb szám (n), amennyiedik deriváltja még folytonos. Jelölése: c(n). Például c0 folytonos görbe az, aminek nincs szakadása. Ha egy valós útvonal nem c0 folytonos, akkor azt jelenti, hogy valahol teleportálnunk is kell. Tekintve, hogy 3 kocsmát is érinteni akarunk, ez nem tűnik lehetetlennek :) | |||
A valós tárgyak mozgása legalább c2 folytonos (azaz a gyorsulás folytonosan változik). Ezt a agyunk megszokta, és a nem c2 folytonos mozgás nem tűnik valóságosnak. Ebből fakadóan az olyan egyszerűbb függvények, mint hogy a kontrollpontokat egyenes vonalakkal összekötjük, nem eredményeznek hihető mozgást. | |||
A tipikus görbék amiket a grafikában használni szoktunk: | |||
* Bézier-görbe. Ezt egyszerű implementálni, de a megfelelő kontrollpontok megadása nehézkes. | |||
* B-Spline görbe család (több görbéből összetett görbék). Ezek lokálisan vezérelhetőek, nagyon kényelmes velük dolgozni, viszont az implementálásuk általában bonyolult. Ebből a görbecsaládból például a Kochanek–Bartels görbe nagyon elterjedt. Viszont sok problémához túl bonyolult implementálni, ezért létezik néhány leegyszerűsített változata is. Gyakori implementációja a közelítőleg c2 folytonos Catmull-Rom görbe (Ezt akkor kapjuk, ha a Kochanek–Bartels összes paraméterét nullának választjuk). | |||
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]] | |||
http://i.imgur.com/C1iKaHx.gif | http://i.imgur.com/C1iKaHx.gif | ||
== A harmadik házihoz szükséges elmélet == | == A harmadik házihoz szükséges elmélet == | ||