2010. július 17., szombat

Tao Start 10 - Árnyalási alapok

Elérkeztünk egy nagyon fontos állomáshoz, ami az OpenGL-t és a számítógépes grafikát illeti. Ugyanis elkezdünk foglalkozni a megvilágítás (lighting) kérdésével, hogy végre tetszetős ábrákat varázsoljunk a képernyőre. Az OpenGL természetesen itt is a segítségünkre lesz. Már most meg kell jegyeznünk, hogy az árnyalás/megvilágítás és az árnyékolás között óriási különbség van. Árnyalásnál minden felületet (face-t) külön kell vizsgálni, tehát az egymás közti viszonyokat nem kell nézni, csak a fény és a poligon pillanatnyi állapota számít. Az árnyékolás egy sokkal összetettebb dolog, hisz ott elvileg a poligonok is árnyékolhatják egymást. Ezt az OpenGL alapból nem is támogatja, tehát, ha árnyékolni szeretnétek, akkor egyértelműen trükközni kell. De most egyelőre csak az árnyalással foglalkozzunk, vagyis pontosabban a megvilágítással.

Alapok

Az OpenGL mielőtt bármit is megjelenítene a képernyőn, minden pixel színét kiszámítja, és a kiszámolt színértékeket a képpufferben eltárolja. A felmerülő számítások egy része attól függ, milyen megvilágítást alkalmazunk, és hogy az objektumok ezen megvilágítás fényét hogyan tükrözik, illetve nyelik el. Az OpenGL-ben természetesen lehetőség van a fényforrások és az objektumok tulajdonságainak manipulálására is. Az OpenGL által kezelt világítási modellben a fényt több, egyenként ki-, illetve bekapcsolható fényforrás határozza meg. Mindegy milyen videokártyánk van, az OpenGL-nek mindenképp minimum 8 db fényforrást kell tudni lekezelni. Ha a kártya hardveresen nem tud ennyit, akkor szoftveresen fogja megoldani, éppen ezért nem célszerű sok fényforrást használni annál is inkább, hiszen eléggé belassítja a program futását. Persze a mai gépeken ez már nem látszik különösebben, és a mostani videokártyák simán lekezelnek 8 db fényforrást. Általában elég csak egy fényt használni, ugyanis a fényszóró, utcai lámpa stb. dolgokat textúrával szokás megoldani, tehát „fénynek” is bitmapet raknak be (bármilyen meglepő, az újabb játékokban is így van), csak ügyesen kell ezt csinálni.
A fényforrásoknak csak akkor van hatása, ha vannak olyan felületek, melyek a fényt visszatükrözik, vagy elnyelik. Egy anyagnak lehet saját fénye is, a bejövő fényt szétszórhatja minden irányban, és tükrözheti a bejövő fény egy részét egy bizonyos irányba akár egy tükör vagy más tükröződő felszín. Ezért mielőtt fényeket tennénk be a modelltérbe, tisztáznunk kell, hogy milyen tulajdonságai is vannak egy fényforrásnak. Nyilván van helyük: ez lehet fix, de lehet a végtelenben is, és ami nagyon fontos, hogy van színük, ami három független komponensből áll: ambient, diffúz és spekuláris.
  1. Környezeti fény (Ambient light) - Ebben a modellben az objektumok egyenletesen, minden irányból kapnak fényt. Hatása a nappali fényviszonyoknak felel meg erősen felhős égbolt esetén. A számítógépes grafikában azért van rá szükség, hogy a felhasználó az ábrázolt jelenet összes objektumának a megvilágítását szabályozhassa. Ebben a modellben nincs fényforrás, az objektumok "saját" fényüket bocsájtják ki. Ez megfelel annak, hogy a jelenetet egy irányfüggetlen, szórt fény világítja meg.
  2. Szórt fény (Diffuse light) - Ennek már van iránya, de minden irányban azonos mértékben verődik vissza. Ez olyan, mint mikor egy matt felületű valamit világítunk meg (például egy fagolyót). Ilyenkor mindegy merre járjuk körbe a fagolyót sehol nem lesz élesebb a fény, mindig ugyanúgy a fény felőli része lesz élénkebb.
  3. Fényvisszaverődés fényes és csillogó felületekről (Specular light) - A sima felületekre általában az a jellemző, hogy rajtuk fényes foltokat (specular highlight) is látunk, melyek helye nézőpontunkkal együtt változik. Ezek a felületek bizonyos irányokban visszatükrözik a fényforrásokat. Ekkor a matt felületekre jellemző diffúz és a tökéletesen (ideálisan) tükröző felületekre jellemző visszaverődés közti átmeneti esetet kell modelleznünk. Az alábbi ábra szemlélteti a különböző fények hatásait:
Fények az OpenGL-ben
Nézzük akkor a fényforrások beállításának gyakorlati részét. Egy fényforrás tulajdonságait a glLightfv paranccsal adhatjuk meg, 3 paramétere van. Az első a fényforrás azonosító száma ez 0-től kezdődik, és a már említettek alapján minimum 8-ig vehet fel értékeket. A második, hogy a fényforrásnak milyen tulajdonságát szeretnénk megadni a harmadik pedig maga az érték, amit be szeretnénk állítani. Ha a fényforrás helyét szeretnénk beállítani a második paraméternek a GL_LIGHT_POSITION értéket kell beírni. Ha a fény színét szeretnénk állítani akkor külön kell a már említett három típusú fényfajta színét beállítani. Ezek: GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR. Alapértelmezés szerint ezek a színek valamilyen szürke árnyalatra vannak belőve (fehér), ha más színű fényt adunk meg a kapott képen a színek csúnyán megváltozhatnak, szóval érdemes csak a fény helyét beállítani, a színét nem (elsőre ez a javasolt megoldás). Aztán ha mindez megvan, be kell kapcsolni magát a megvilágítást: glEnable(GL_LIGHTING);
valamint a használt fényünket glEnable(GL_LIGHTN); ahol N a fényforrás azonosítószáma. Ezek után már meg lehet adni akár minden vertexre a fényvisszaverési tulajdonságokat, vagyis az anyagtulajdonságokat. De mik is azok az anyagok?

Anyag színek

Az OpenGL aszerint a megközelítés szerint dolgozik, miszerint egy anyag színe a bejövő fényben szereplő vörös, zöld és kék fény arányától függ. Például egy tökéletesen piros labda minden bejövő piros fényt visszatükröz, és minden zöld és kék fényt elnyel, amely eltalálja őt. Ha a labdát fehér fényben nézzük (a fényt egyenlő vörös, zöld és kék komponensekből kikeverve), akkor egy piros labdát látunk; ha azonban tiszta zöld fénnyel világítjuk meg a labdát, az feketének tűnik (a zöld szín teljesen elnyelődik, és nincs visszatükröződő fény). Akárcsak a fénynek, az anyagoknak is különböző ambient, diffúz és spekuláris színei vannak, amelyek meghatározzák az anyag ambient, diffúz és spekuláris tükröződését. Egy anyag ambient tükröződése összefüggésben van a bejövő fény ambient komponensével, a diffúz tükröződése a bejövő fény diffúz komponensével. Az ambient és diffúz tükröződések határozzák meg az anyag színét, ezek többnyire hasonlóak vagy megegyezőek. A spekuláris tükröződés általában fehér vagy szürke.
Az ambient, diffúz és spekuláris színek mellett, az anyagoknak emisszív színei is vannak, amelyek a világító objektumok fényét szimulálják, azaz egy anyag emisszív színe az anyag saját fényének a színe. Az OpenGL megvilágítási modellben egy anyag emisszív színe intenzitást ad az objektumnak, de nincsenek rá hatással a fényforrások, és a világító objektum sincs hatással a többi objektumra.
Az elmélet után következzen a gyakorlat, hozzunk létre anyagokat. Az anyagparaméterek a glMaterialfv eljárással adhatóak meg, ennek szintén 3 paramétere van. Az első, hogy a lap melyik oldalára szeretnénk beállítani az adott tulajdonságot. Ez azért jó, mert így egy test belseje más tulajdonsággal rendelkezhet, mint a külseje. Ennek értékei: GL_FRONT poligon elülső része GL_BACK poligon hátsó része GL_FRONT_AND_BACK mindkét oldalára egyaránt vonatkozzon. Többnyire a GL_FRONT_AND_BACK-et érdemes használni, hogy ne legyenek poligonmegadásból következő árnyalási problémák. A második paraméterrel állíthatjuk be, hogy milyen tulajdonságra vonatkozzon a változtatás. Ez lehet: GL_AMBIENT, GL_SPECULAR, GL_DIFFUSE, GL_EMISSION illetve GL_SHININESS. Az ambient, specular, diffuse-al azt adhatjuk meg, hogy az anyag milyen mértékben veri vissza ezeket a fényeket. A GL_EMISSION-nal az objektum által kibocsátott fényt adhatjuk meg. A GL_SHININESS pedig az anyag ragyogási kitevője, amely a specular tulajdonság által okozott fényes foltokra van hatással. Akkor nézzünk pár konkrétumot:

float[] matShininess = { 100f };
float[] matSpecular = { 1.0f, 1.0f, 1.0f, 1.0f };
float[] lgtPosition = { 1.0f, 1.0f, 1.0f, 0.0f };

Amint látható a különböző fényfajta tulajdonságok egy-egy 4 elemű tömbben vannak. Ezek rendre az r, g, b és alfa értékek. Az alfát mindig érdemes 1-re állítani. A pozíció szintén 4 elemű tömb. Ezek rendre az x, y, z és w értékek. Ha a w-t 0-ra állítjuk a fény a végtelenben lesz, ez azt jelenti, hogy a fénysugarak párhuzamosak. Körülbelül ez jellemző a napsugarakra. Egyébként állítsuk a w-t 1-re. A shininess pedig egy egyszerű szám, ez a [0..128] intervallumban lehet. Egy elképzelhető setup eljárás a fény beállítására a Tao-ban:

Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_SPECULAR, matSpecular); Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_SHININESS, matShininess); Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, lgtPosition);
Gl.glEnable(Gl.GL_LIGHT0);
Gl.glEnable(Gl.GL_LIGHTING);

Amint látszik a fényforrásunknak csak a pozícióját állítottuk, ami a negyedik koordináta 0 volta miatt a végtelenbe került, tehát gyakorlatilag csak iránya van. A fénytulajdonságoknak meg beállítottuk értelemszerűen a konstans tömböket. Most már csak szükség van egy objektumra, amin kipróbáljuk a tanultakat. Sem a háromszög, sem a kocka nem túl jó alany erre, hiszen a fények változását legjobban a görbült sima felületek adják vissza. Sajnos az OpenGL alapból nem tartalmaz olyan eljárásokat, amelyek magasszintű geometriai objektumok rajzolását teszik lehetővé. Ezeket az eljárásokat a GLUT tartalmazza. (emlékeztetőül: a GLUT (Graphics Library Utility Toolkit) az OpenGL egy kibővítése, amely tartalmazza az ablakok létrehozásához és kezeléséhez szükséges függvényeket, eljárásokat). A GLUT segítségével lehetővé válik egyszerű parancsokkal kocka, gömb, kúp, stb. rajzolása.

Kocka
rajzolása a

void glutSolidCube(GLdouble size);
void glutWireCube(GLdouble size);

eljárásokkal tudunk rajzolni. A size adja meg a kocka éleinek hosszát. A glutSolidCube egy kitöltött oldalakkal rendelkező kockát rajzol, a glutWireCube pedig csak a kocka éleit rajzolja meg. A kocka középpontja az origóban lesz.

Gömb rajzolására a

void glutSolidSphere(GLdouble radius , GLint slices , GLint stacks);
void glutWireSphere(GLdouble radius , GLint slices , GLint stacks);

eljárásokkal van lehetőség. A radius a gömb sugara, slices a z tengely körüli beosztások száma (mint a földrajzi hosszúság), stacks a z tengely menti beosztások száma (mint a földrajzi szélesség). A középpont itt is az origóban lesz.

Kúpot
a

void glutSolidCone(GLdouble base, GLdouble height, GLint slices, GLint stacks);
void glutWireCone(GLdouble base, GLdouble height, GLint slices, GLint stacks);

eljárásokkal tudunk rajzolni. A base a kúp alapjának sugara, height a kúp magassága, slices adja meg a z tengely körüli beosztások számát, stacks pedig a z tengely menti beosztások számát jelenti. A kúp alapja z = 0-nál helyezkedik el, teteje pedig z = height-nél.

Tóruszt
a

void glutSolidTorus(GLdouble innerRadius, GLdouble outerRadius GLint nsides GLint rings);
void glutWireTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings);

eljárásokkal tudunk rajzolni. Az innerRadius a tórusz belső sugara, outerRadius a tórusz külső sugara, nsides adja meg a radiális részek oldalainak számát, rings pedig a tórusz radiális beosztásainak számát. A tórusz középpontja koordináta-rendszer középpontjában lesz.
A fényességet az OpenGL minden vertexhez külön számolja. Ehhez a legegyszerűbb árnyalás esetén tudnia kell a vertex pozícióját, a fényforrás pozícióját és még egy infót, a vertexhez tartozó normálvektort. Ez egy olyan vektor, amely a vertexen keresztül kifele mutat a testből. Ilyen vektort síkra is lehet állítani, sőt ott egyértelmű is. A fényesség intenzitását úgy számoljuk, hogy a normálvektor (normális) és a fény iránya által bezárt szöget vesszük. Ennek a koszinusza lesz maga a fényesség intenzitása az adott lapon. Mivel a koszinusz [-1,+1] tartományban van értelmezve, ezért ha a koszinusz [-1,0] közt van akkor a poligon hátsó oldalát éri a fény, ha [0,1] közt van akkor az elülső oldalát. A poligon színét már csak fel kell szorozni ezzel a koszinusz alfával és kész is az árnyalás. Persze OpenGL-ben nem oldallaphoz, hanem vertexhez rendelünk normálvektort. Ez viszont már nem egyértelmű, hiszen pontra nem lehet merőlegest állítani, ezért nem is lehet számítani, tehát nekünk kell „kézzel” beállítani minden egyes vertexre a normálvektort. Alapesetben a vertex normálvektora a testből a vertexen keresztül kifelé mutató vektor.

A drótváz, a flat és a smooth ábrázolás
A létrehozott gömbünk esetében a Glut függvény nem csak a gömböt alkotó vertexeket, hanem az ahhoz tartozó normálvektorokat is kiszámolja szerencsére. Természetesen később nekünk is tudnunk kell majd normálvektorokat számolnunk, de egyenlőre most nem ez a legfontosabb. Az árnyalási modellt a void glShadeModel(GLenum mode); paranccsal állíthatjuk be. A mode a GL_FLAT illetve GL_SMOOTH valamelyike lehet.

A fények és anyagok RGB értékei

A specifikált fények szín komponensei némiképp különböznek az anyagokétól. Egy fény esetén, az értékek a teljes intenzitás százalékos kifejezései minden színre. Ha a fény színének mind az R, G és B komponense 1.0, akkor a fény a legfehérebb fehér. Ha az értékek mindegyike 0.5, a fény még mindig fehér, de csak fél intenzitással, így az szürkének tűnik.
Az anyagok számára, az értékek a színek tükröződési arányait specifikálják. pl. ha R=1.0, G=0.5 és B=0.0, akkor az anyag minden bejövő vörös fényt és a zöld fény 50%-át veri vissza, viszont minden kék fényt elnyel. Más szavakkal, ha egy fényforrás szín komponensei (LR, LG, LB) egy anyag megfelelő fény komponensei pedig (MR, MG, MB), akkor minden más tükröződési effekttől eltekintve a szemünkbe érkező fény: (LR*MR, LG*MG, LB*MB).

Összefoglalás

A következő lépések kellenek ahhoz, hogy a képünkhöz világítást adjunk:
  1. Minden objektum minden vertexéhez specifikálnunk kell normál vektort. Ezen normál vektorok határozzák meg az objektum irányát a fényforráshoz viszonyítva.
  2. Egy vagy több fényforrás létrehozása, kiválasztása és pozicionálása.
  3. Egy megvilágítási modell létrehozása és kiválasztása.
  4. Az objektumok anyagi tulajdonságainak definiálása.
Ezzel el is érkeztünk a mai anyag végére. Leraktuk az alapokat ahhoz, hogy végre valóban látványos dolgokat hajtsunk végre az OpenGL-ben. Ha most elsőre nem volt minden világos az csakis a szerző hibája, de előbb-utóbb biztosan le fog esni.

Gömb és tórusz árnyalása
A letölthető példaprogram a fent leírtakat mutatja be, remélhetőleg a kedves olvasó képes lesz továbbfejleszteni az alkalmazást a saját ötleteinek megfelelően. A példa teljes forrása itt tölthető le:


A fenti tutorial PowR által írt a http://free-pascal.extra.hu/ oldalon megtalálható OGL tutorial alapján készült el. Köszönet érte PowR-nek, valamint Kuba Attila - OGL Programozása jegyzetének!

0 megjegyzés :

Megjegyzés küldése