2010. december 12., vasárnap

Tao Start 13 - Mip-mapping

Sokszor előfordul, hogy ha egy textúrázott objektumhoz közelebb megyünk a virtuális világban, akkor gyakran megjelenítési hibák tűnnek fel. Ezek a problémák abból fakadnak, hogy csak egy rögzített méretű textúrát használunk, amelyek a mozgás hatására az OpenGL az objektum méreteinek megfelelően próbál megjeleníteni. Például, ha egy falra egy kis textúrát teszünk fel, majd a falhoz nagyon közel megyünk, akkor a texeleket külön-külön felismerhetjük. Ha azonban a falhoz közeledve a rögzített méretű textúrát egyre nagyobb felbontásúra cserélnénk le, akkor a textúra kép véges felbontása is kevésbé lenne észrevehető. Ezt a problémát a Mip-mapping használatával oldhatjuk meg, amely a textúrát több felbontásban tárolja el. Két egymást követő textúra felbontásának aránya egy a kettőhöz.

A képpiramis
Az egymást követő képeket egy piramisként képzelhetjük el, amelyben a legnagyobb felbontású kép a piramis alján a legkisebb felbontású pedig a piramis tetején foglal helyet. A képpiramisok használatához az OpenGL-nek az összes kettő hatvány méretű textúrát át kell adni, a legnagyobb felbontásútól az 1×1-es méretűig. Tehát, ha a legnagyobb felbontású textúránk 16×16 texelből áll, akkor elérhetővé kell tenni a textúra 8×8-as, 4×4-es, 2×2-es és 1×1-es méretű változatait is.

A textúratár mip-map szervezése
A képpiramisok különböző szintjein lévő textúrákat a glTexImage2D() függvénnyel definiáljuk. A következő példa egy képpiramist ír le:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, MIPLevels[0]);
glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, MIPLevels[1]);
glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, MIPLevels[2]);
glTexImage2D(GL_TEXTURE_2D, 3, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, MIPLevels[3]);
glTexImage2D(GL_TEXTURE_2D, 4, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, MIPLevels[4]);
glTexImage2D(GL_TEXTURE_2D, 5, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, MIPLevels[5]);
glTexImage2D(GL_TEXTURE_2D, 6, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, MIPLevels[6]);
A MIPLevels[] tömb tartalmazza az egyes szintekhez tartozó képeket. A példában a képpiramis tetején egy 64x64-es kép áll, így a szükséges szintek száma 7. Hogy ezzel ne a programozónak kelljen az értékes idejét elvesztegetni, az OpenGL már tartalmaz egy megoldást ennek a megvalósítására. A lényege, hogy mikor a betöltött bitképből a textúrát generáljuk, az OpenGL elkészít belőle több változatot is, különféle részletességi szinttel és ezek közül fog válogatni a távolság függvényében. Ilyenkor a glTexImage2D() helyett a gluBuild2DMipmaps() függvényt kell használni, ami a GLU függvénykönyvtár része. A kisebb felbontású képeket a legnagyobból szűréssel hozza létre úgy, hogy veszi a 4 szomszédos texelt és átlagolja őket. A gluBuild2DMipmaps() függvény szoftveresen végzi el a fenti műveletet ezért ma már eléggé elavultnak tekinthető. A következő példában egy 64×64-es felbontású képből hozzuk létre a képpiramishoz szükséges textúrákat:
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, 64, 64, GL_RGBA, GL_UNSIGNED_BYTE, origImage);
Az origImage tartalmazza a legnagyobb felbontású képet, az összes szint ebből generálódik majd le. A Mip-mapping során újra előkerült a textúrafilterezés témája, amit ezúttal sem kerülhetünk meg. A textúrafilterezés lényege többek között, hogy megadhatjuk, kicsinyítésnél és nagyításnál hogy hogyan viselkedjenek a textúrák. Az OpenGL glTexParameteri() ill. glTexParameterf() függvényeivel tudunk textúrázási paramétereket beállítani, melyek közé a textúrafilterezés is tartozik. Számunkra most is igazán csak a GL_TEXTURE_MIN_FILTER és a GL_TEXTURE_MAX_FILTER paraméter a fontos. Az előbbi a textúra kicsinyítéséért, a másik a nagyításáért felel. Mindkettővel találkoztunk már az előző példák során. Most annyiban fog változni a használatuk, hogy a GL_TEXTURE_MIN_FILTER paramétert a GL_LINEAR helyett a GL_NEAREST_MIPMAP_LINEAR értékre fogjuk beállítani. Összesen hatféle textúrafilterezés van, amit választhatunk, ezek közül a GL_NEAREST a legrosszabb minőségű, de a leggyorsabb, míg a GL_LINEAR a legjobb minőségű. A Mip-mapping igyekszik valahol egyensúlyt találni a kettő között. Ennek is négy változata van, GL_xx_MIPMAP_xx alakban, ahol xx vagy LINEAR vagy NEAREST.

Hárdkódolás

Az elmélet után jöjjön most a gyakorlat. Szerencsére elég sokat tudunk már ahhoz, hogy viszonylag kevés kódolással elkészítsünk egy Mip-mapping demó alkalmazást. A gyakorlatban szoftveresen fogjuk legenerálni az egyes szinteket remélve azt, hogy sokat tanulunk belőle. A CodeSampler oldalán található egy nagyon személetes példa C++ nyelven. Ezt fogjuk mi is megvalósítani csak C# és Tao környezetben.
A példa lényege, hogy létrehozunk egy quad-okból álló hálót, ahol minden rácselemet egy-egy különálló textúra térkép burkol. Az alap textúra mérete 256x256 és fehér színű Az egyes Mip-map szintek viszont eltérő színnel rendelkeznek, így "szép" szivárványos átmenet rajzolódik ki a hálón (textúrafiltertől függően). A textúrák betöltését és a Mip-map szintek beállítását a Textura osztály végzi el. A leírtak valahogy így néznek ki:

A filterek hatása
A letölthető példaprogramban az F1 és F2 billentyűvel lehetséges váltani a filterek között, az ESC hatására az alkalmazás pedig bezáródik. A navigáció az egeret és a le-fel billentyűket használja. A szövegeket a GLUT könyvtár segítségével írjuk ki, így a freeglut.dll-re szükség van újból. A példa forrás letölthető innen:


A példaprogram a GLU segítségével szoftveresen képes legyártani az egyes Mip-map szinteket, valamint kézzel is beállíthatjuk azokat.

Hardveres Mip-map generálás

Eddig szoftveresen készítettük el a Mip-map szinteket. Természetesen, a grafikus kártya ebben is segít ma már, lássuk hát a lehetséges megoldásokat.

(1) GL_GENERATE_MIPMAP

Ez a régebbi módszer, amely még a SGI nevéhez fűződik. OpenGL 1.4 szükséges minimálisan a használatához.
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
Az OpenGL 3.0-ban a GL_GENERATE_MIPMAP felsorolást már elavultnak jelölték meg, így később a 3.1-es és újabb OpenGL állapotgépekből eltávolították.

(2) glGenerateMipmap()

Ez az FBO extension-nel együtt született. Elődjétől eltérően ez már függvény és így a működése is eltérő. Használatház OpenGL 3.0 szükséges minimálisa vagy a GL_ARB_framebuffer_object extension megléte. A használata rendkívül egyszerű:
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
glGenerateMipmap(GL_TEXTURE_2D);  //Generate mipmaps now!!!
A működése elve miatt fontos, hogy akkor kell meghívni, amikor a mipmap szinteket le akarjuk gyártani. Mindenképp szükséges a glTexImage után (mivel ekkor foglalja le a helyet is a textúra számára). Felhasználásukban is különbségek mutatkoznak. Míg az előbbit "statikus" textúrákhoz az utóbbit mindkettőhöz, de leginkább a dinamikus és főleg a render-to-texture textúrákhoz ajánlják. Bizonyos források szerint a GL_GENERATE_MIPMAP esetén minden egyes rajzolási hívás után legyártásra kerülnek a mipmap szintek, míg a glGenerateMipmap esetén csak akkor, amikor szeretnénk. Ezt úgy tehetjük meg, hogy glBindTexture után meghívjuk a glGenerateMipmap-t. A példákban GL_TEXTURE_2D szerepelt azonban a generálás működik GL_TEXTURE_3D és "GL_TEXTURE_CUBE_MAP" esetén is.
A leírtak gyakorlati megvalósítása házi feladat. A Tao wrapper csak OpenGL 2.0-át valósít meg így a glGenerateMipmap nem érthető el, helyette a glGenerateMipmapEXT található meg amit viszont nem biztos, hogy a videókártyánk drivere támogat. Egy próbát azért megér.

Felhasznált anyagok

A tutoriálhoz felhasználtam Merczel László OpenGL cikkét a CodexOnline-on, Sakura7 bejegyzéseit, valamint a CodeSampler oldalon található mintapéldát. Irodalmi forrásként Antal György, Csonka Ferenc, Szirmay Kalos László: Háromdimenziós grafika, animáció és játékfejlesztés c. könyvét használtam.

1 megjegyzés :