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

Rohamcsiga (vitalap | szerkesztései)
Rohamcsiga (vitalap | szerkesztései)
Elkezdtem megírni a textúrázás részt
1 605. sor: 1 605. sor:


Szerencsére ezt az OpenGL automatikusan megcsinálja helyettünk. A gond viszont ezzel ez, 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.
Szerencsére ezt az OpenGL automatikusan megcsinálja helyettünk. A gond viszont ezzel ez, 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.
Ennek a problémának a megoldására lehet használni a textúrázást. Ezt használni 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 azok hogy befolyásolják a textúrázást. Ehhez ez ü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/lVR38D3.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 OpenGL floatként szeretjük kezelni, nagyon ritkán szoktunk float textúrákat használni, és azokat se színek leírására. Itt tényleg csak (0-1) tarmotány beli LDR színekre vagyunk kíváncsiak, itt teljesen 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 égvilágon semmi előnyünk nem származik abból, floatként írjuk le a színeket. Hangsúlyozom, hogy ez csak a textúrákra igaz. A videókártya, amint egy értéket kiolvas a textúrából, azt azonnal floattá is kasztolja, hogy műveleteket tudjon végezni vele, külön hardware van erre, ez annyira gyakori művelet.
És 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 ilyenformába használhatatlan lenne. Semmi vizuális visszacsatolásunk nincs, 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:
<br/> <syntaxhighlight lang="c">
"........"          "........"          "........"          "........"          "........"          "........"
"...**..."          "..***+.."          "..***+.."          "....*..."          "..****.."          "...+**.."
"....*..."          "....+*.."          "....+*.."          "...+*..."          "..*....."          "..++...."
"....*..."          "....+*.."          "....+*.."          "..+**..."          "..***+.."          "..*....."
"....*..."          "...+*+.."          "...**+.."          "..+.*..."          "....+*.."          "..*+*+.."
"....*..."          "..+*+..."          "....+*.."          "..****.."          "....+*.."          "..*..*.."
"...***.."          "..****.."          "..***+.."          "....*..."          "..***+.."          "..+**+.."
"........",        "........",        "........",        "........",        "........",        "........"
</syntaxhighlight> <br/>
Technikailag már ezek a stringek is lehetnének unsigned char tömbök. És három szín van összesen, amit egy byte-on is bőven le lehetne írni. Viszont én az egyszerűség kedvéért inkább maradok a komponensenként egy bytenál, és nem kezdek el bitműveletekkel játszani a GL_R3_G3_B2-höz.
Termesztésen ezekből a szín előállítása egyszerűen két egymásba ágyazott for ciklus. Pl:
<br/> <syntaxhighlight lang="c">
GLubyte texture_data[6][64][3];
for(int t = 0; t < 6; t++) {
  for(int i = 0; i < 64; i++) {
    switch(ascii_textures[t][i]) {
      case '*':
        for(int j = 0; j < 3; j++) {
          texture_data[t][i][j] = 0;
        }
        break;
      case '+':
        for(int j = 0; j < 3; j++) {
          texture_data[t][i][j] = 127;
        }
        break;
      default:
        for(int j = 0; j < 3; j++) {
          texture_data[t][i][j] = 255;
        }
        break;
    }
  }
}
</syntaxhighlight> <br/>
Na, megvan a textúra tartalma, és akkor ezt hogyan adjuk oda az OpenGLnek?
Először is szükségünk van "egy textúra mutató pointerre/referenciára", egy névre, amivel hivatkozhatunk majd később a textúrára. Ez a név a textúrák esetében (és a modern OpenGLbe gyakorlatilag az összes objektum esetében) egy GLuint (unsigned int) lesz. A textúrázással kapcsolatos függvények dokumentációjába ez "a textúra neve"-ként lesz megemlítve.
Nekünk hat darab textúra kell, ezért definiáljuk egy 6 elemú GLuint tömböt:
<br/> <syntaxhighlight lang="c">
GLuint tex[6];
</syntaxhighlight> <br/>
Kérjük meg az OpenGL-t, hogy találjon ki nekünk hat darab textúra nevet. Ezek után ő ezekről a nevekről tudni fogja, hogy azok textúra vonatkoznak. Ehhez a <code> glGenTextures(GLsizei n, GLuint *textures); </code> függvényt használhatjuk.
<br/> <syntaxhighlight lang="c">
glGenTextures(6, tex);   
</syntaxhighlight> <br/>
Fontos, hogy ilyenkor még a textúra nem jön létre. Az csak akkor történik meg, amikor először használni akarjuk (bindoljuk).
==== A textúrák beállítása ====
Nyilván egy for ciklusban fogom beállítani őket, csak az 'i'-edik textúra beállítását mondom el.
Ahhoz, hogy egy textúrán műveletet tudjunk végezni azt bindolni kell egy targethez. Képzeljünk el egy szobát, amibe vannak képkeretek. Az összes képkeret különböző alakú, mindegyikbe másféle képet tudunk rakni. De mindig csak éppen a képkeretben lévő képeket látjuk, csak azokkal tudunk dolgozni. Ezek a képkeretek pontosan ugyanúgy viselkednek mint az OpenGL-be a targetek. Két fontos target van, az egyik az egydimenziós texturáknak (GL_TEXTURE_1D), a másik a kétdimenziósoknak (GL_TEXTURE_2D). A házikban szinte mindig csak a kétdimenziósra lesz szükséged. A hasonlatban amikor egy képet berakunk a képkeretbe, az megegyezik az OpenGL-nél a bindolással. A bindolás után az összes textúra művelet arra a képre fog vonatkozni. Bindolni a <code>glBindTexture(GLenum target, GLuint texture);</code> függvénnyel tudunk.
<br/> <syntaxhighlight lang="c"> 
glBindTexture(GL_TEXTURE_2D, tex[t]);   
</syntaxhighlight> <br/>
Ezután fel kell töltenünk a textúrát a <code> glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); </code>
Elősször nézzük meg a paramétereit:
* target - Ez nálad szinte mindig GL_TEXTURE_2D lesz.
* level - Ide 0-t írj. Az OpenGL megengedi, hogy egy textúrának különböző felbontású változatait is feltöltsed, ami sokat javíthat a rendelt kép minőségén. Az alap textúránál kisebb felbontású képeket mipmap-nek hívják. Az 'i'-edik mipmap mérete minden dimenzióban fele az 'i-1'- ediknek. Ide ezt írhatod be, hogy éppen hányadik mipmap-et töltöd fel (a 0. mipmap az alaptextúra). A mipmap-ek használata nagyon hasznos tud lenni, de nagyon veszélyes is. Ha ez a feature be van kapcsolva, és nem töltöd fel az összes mipmap-et, vagy valahova rossz felbontásút töltesz fel, akkor a textúra definíció szerint "incomplete" marad, és az incomplete textúrák úgy viselkednek, mintha nem is lennének ott. És ezt nagyon nehéz debuggolni, ezért a tárgyból ajánlott a mipmapek használatának kerülése.
* internalFormat - Ez legyen GL_RGB vagy GL_RGBA. Itt azt kell megadnod, hogy az OpenGL milyen formátumba tárolja a képet. Például a tömörített formátumok nagyon hasznos. Hatékonysági okok miatt a komponensenként egy byteos GL_RGB textúra gyakran GL_RGBA formátumba tárolódik, mert az OpenGL csak 4 byteos egységeket tud hatékonyan írni / olvasni, de ezzel nekünk nem kell foglalkozni.
* width / height - Egyértelmű.
* border - Ez mindenképpen legyen 0.
* format - Ez is legyen GL_RGB vagy GL_RGBA. Itt adhatod meg, hogy milyen formátumban töltöd fel a képet. Ez nem feltétlen egyezik meg az internalFormat-al, más formátúmú tárolást is lehet tőle kérni, mint ahogy mi adjuk neki a textúrákat.
* type - Ez legyen GL_UNSIGNED_BYTE. Ez azt adja meg, hogy milyen típussal írtad le a színeket.
* pixels - A textúra elejére mutató pointer. Fontos megjegyezni, hogy a tömbünket úgy értelmezi, hogy a sorok mérete 4 byte-al oszható. Ha nem olyan (pl. 2*2-es RGB textúrát használtunk, akkor a sorokat ki kell egészíteni úgy nevezett "padding"-el, hogy illeszkedjenek a szóhatárra.
A mi esetünkben: 
<br/> <syntaxhighlight lang="c">
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 8, 8, 0, GL_RGB, GL_UNSIGNED_BYTE, texture_data[t]);
</syntaxhighlight> <br/>
A textúrákkal kapcsolatban nagyon sok beállítási lehetőségünk van, amiket a <code> glTexParameteri(GLenum target, GLenum pname, GLint param); </code> függvénnyel tehetünk meg. Minket két fontos paraméter érdekel, a <code>GL_TEXTURE_MIN_FILTER</code>, és a <code>GL_TEXTURE_MAG_FILTER</code> és mindkettőnek a számunkra fontos lehetséges értékei: <code>GL_LINEAR</code> (ez a bilineáris szűrést bekapcsolja, amit a sugárkövetésnél is használtunk) és <code>GL_NEAREST</code> (ami a legközelebbi éréket választja). Mivel a GL_LINEAR hardwareből van megvalósítva, ezért gyakorlatilag semmivel se lassabb mint a GL_NEAREST, és általában szebb eredményt okoz (mint ahogy most is). Ami miatt azonban ezek fontosak, hogy a GL_TEXTURE_MIN_FILTER beállítása ennek a két értéknek az egyikére kikapcsolja a mipmap-ek használatát. Ennélkül a textúra úgy viselkedne, mintha nem is lenne ott.
A mi esetünkben:
<br/> <syntaxhighlight lang="c">
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
</syntaxhighlight> <br/>
Megjegyzés: a textúrákat "unbindolni" szoktuk, miután használjuk őket. Ez azt jelenti, hogy a 0 nevű, default textúrát használjuk, ami úgy viselkedik mint a null pointer, a rajta végzett műveletek nem csinálnak semmit. Ez nagyobb programoknál azért ''nagyon'' hasznos, mert ha valaki más hülyeséget csinál, akkor az nem a mi erőforrásunkat rontja el.


== Utóhang ==
== Utóhang ==