2010. március 16., kedd

Tao Start 06 - Textúrázás alapfokon (I.)

A legutóbbi OGL leckében a kockánk nagyon szépen lett kiszínezve, de mi van akkor, ha szeretnénk szöveget is rátenni? Például, hogy OpenGL. Gondolhatnánk, hogy megrajzoljuk poligononként, de ez elég durva munka lenne, hisz láttuk, hogy egy kocka megrajzolása is benne van két oldalban. A megoldás a textúrázás. Ezt úgy kell elképzelni (bár gondolom azért mindenki tudja, hogy mi is ez), hogy van egy képünk és annak bizonyos részét "ráhúzzuk" a poligonra.
Az előző kockarajzoló forrásunkat fogjuk kiegészíteni. Lecsonkíthatjuk úgy, hogy csak egy kocka legyen benne és az is csak forogjon a tengelye körül, de nem feltétlen szükséges. A kép felbontásának mindenképpen kettő hatványnak és N×N-esnek kell lennie! Majd meglátjuk később, hogy miért. Akkor most ebből textúrát kéne gyártani. Ehhez be kell vezetni egy „globális” változót:
private uint texId = 0;
Az texId egy bizonyos textúra azonosító, ami egy egész szám. Ez azért kell, mert amikor textúrát készítünk majd a képünkből akkor ez a videó kártya memóriájába kerül és ezen azonosító alapján lehet rá majd hivatkozni. Írjunk egy eljárást, ami megcsinálja a textúránkat, legyen a neve MakeTexture. Ez nagyjából így néz ki:
    private void MakeTexture()
    {
        Bitmap image = null;
        BitmapData data = null;

        try
        {
            image = new Bitmap("texture.bmp");
        }
        catch
        {
            Directory.SetCurrentDirectory(Application.StartupPath + @"\..\..\");
            image = new Bitmap("texture.bmp");
        }

        if (image != null)
        {
            Rectangle rect = new Rectangle(0, 0, image.Width, image.Height);
            data = image.LockBits(rect, ImageLockMode.ReadOnly,
            PixelFormat.Format24bppRgb);

            Gl.glEnable(Gl.GL_TEXTURE_2D);
            {
                Gl.glGenTextures(1, out texId);
                Gl.glBindTexture(Gl.GL_TEXTURE_2D, texId);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D,
                Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D,
                Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);
                Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGB,
                image.Width, image.Height, 0,
                Gl.GL_BGR, Gl.GL_UNSIGNED_BYTE, data.Scan0);
            }
            Gl.glDisable(Gl.GL_TEXTURE_2D);

            image.UnlockBits(data);
            image.Dispose();
        }
    }
Először létrehozunk egy Bitmap és egy BitmapData típusú változót. Ez előbbi fogja tartalmazni magát a textúrát, a konstruktor segítségével máris bele is töltjük a képet:
image = new Bitmap("texture.bmp");
A GDI+ a képek betöltésénél nagyon hasznos lesz a számunkra, hiszen számtalan képformátumot lekezel, és ennyiből áll csak a dolog. A gond csak annyi, hogy az image változóban tárolt adatot át is kell tudni adni az OpenGL-nek ami nagyon nem menedzselt. Erre nyújt segítséget a data változó. Ahhoz, hogy a képet közvetlenül elérhessük annak egészét, vagy egy részét zárolnunk kell, hogy más folyamat ne férhessen hozzá. Ehhez a osztály BitmapLockBits függvényét kell meghívnunk. Első paraméterként azt a területet kell megadnunk Rectangle típusban, melyet kezelni szeretnénk. Ez a terület kerül zárolásra. Második property-ben azt határozhatjuk meg, hogy milyen módon szeretnénk hozzáférni. Ehhez az ImageLockMode felsorolt típus elemei közül választhatunk. Végül a PixelFormat szintén felsorolt típus elemeiből választhatunk egyet, melyben a zárolt terület pixeleinek típusát adja meg. Például a Format32bppArgb választásával a kép minden pixeléhez 32 bit tartozik, melyben 8-8 bit fogja tárolni az átlátszóság mértékét, a piros, a zöld és a kék színösszetevő értékét. Jelen példában elegendő a 24 bit, amit a Format24bppRgb pixelformátum jelöl.
Ezután engedélyezzük a 2d-s textúrázást glEnable(GL_TEXTURE_2D); Ezt az eljárás végén illik kikapcsolni. A glGenTextures(1, out texId); eljárással generálunk a texId változónknak egy azonosítót, ami segítségével lehet majd hivatkozni a textúránkra. Itt az 1-es azt jelzi, hogy hány azonosítót generáljon, ugyanis az texId helyére lehetne egy array[1..n] of Gluint tömböt is írni és ekkor megadhatnánk n-t első paraméternek és akkor n db textúrát generálna, tehát feltöltené tömböt. A glBindTexture(GL_TEXTURE_2D, texId); aktiválja a 2. paraméterben megadott azonosítójú textúrát (texId változó), mint 2D-s textúrát (GL_TEXTURE_2D). Innentől az aktivált textúrára fognak vonatkozni a további textúra kezelő utasítások. A glTexParameteri paranccsal belőhetjük, hogy viselkedjen a textúra, ha össze kell nyomni, illetve szét kell húzni a pontokat. Ugyanis, a 128×128-as textúránk ritkán néz ki a képernyőn 128×128-asnak. A perspektivikus nézet miatt ugyanis, ha pl. a kocka tetejére húzzuk és elforgatjuk a kockát egy kissé x tengelyen, hogy látszódjon a teteje akkor a messzi részeken összenyomódik, míg a közeli részeken széthúzódik a textúra. Az első paraméterben meg kell adni, hogy milyen textúráról is van szó (GL_TEXTURE_2D), a második paramétere GL_TEXTURE_MAG_FILTER illetve GL_TEXTURE_MIN_FILTER lehet attól függően, hogy a nagyítás, vagy a kicsinyítés módját akarjuk beállítani. A harmadik paraméter lehet GL_LINEAR, vagy GL_NEAREST. HA GL_NEAREST-et adtunk meg akkor a textúránk "pixelesebb" lesz, ha viszont GL_LINEAR-t adunk, akkor úgy képez, hogy a pixelhez legközelebbi 2×2 pixeleket lineárisan interpolálja a rendszer, így simább lesz a textúra (elmosódik). Mi a GL_LINEART állítjuk be mindkettőre, de ki lehet próbálni (sőt javaslom is, hogy próbáljuk ki) a GL_NEAREST-et. Majd végül jön a lényeg, a textúra elkészítése az image-ból. Ez a glTexImage2D eljárás segítségével történik. Paraméterei:
  1. Textúra típusa (most GL_TEXTURE_2D)
  2. Akkor használjuk, ha a textúrát több felbontásban is tároljuk (majd később), egyébként 0 értéket kell adni
  3. Itt megadhatjuk, hogy a pixeleket hogyan jelenjenek meg, tehát a rendszer milyen adatokat használjon a megjelenítéshez. Itt, mivel RGB adatokat tároltunk célszerű GL_RGB -re állítani.
  4. A képünk szélessége (image.Width)
  5. A képünk magassága (image.Height)
  6. Keret vastagsága (általában nulla)
  7. A tárolás formátuma. Mi ugye RGB értékeket tároltunk, tehát ide GL_BGR-t kell megadni.
  8. Itt kell megadni, hogy a tömbben milyen adattípust használunk az előbb megadott értékek tárolására. Mi byte-ot használunk, tehát GL_UNSIGNED_BYTE -ot kell ide írni.
  9. Itt kell átadni a textúra tömbünk címét (bitmapdata.Scan0)
Végül az image által zárolt területet fel kell oldanunk. Ezt az UnlockBits függvény teszi meg. Paraméterként a LockBits által visszaadott BitmapData osztályt kell megadnunk, majd magát a képet is felszabadítjuk. És ezzel már kész is a textúra. Már csak annyi kell, hogy egyszer meghívni ezt a generálós eljárást. Például a MainForm konstruktorának elejére írjuk be, hogy MakeTexture(); és ezzel le is van generálva a textúra. Akkor most használni kellene. A DrawCube() eljárást kell módosítanunk, tehát a kockarajzolást.
A színbeállítást (glColor3f-eket) ki lehet szedni, hiszen most nem színezzük ki a kockánkat, hanem ehelyett textúrázzuk. A színek beállítása helyett minden vertexre be kell állítani az úgynevezett Textúra-Koordinátákat. Ezek a textúra koordináták [0,1] intervallumba eső számok. A textúra koordinátákat Vertexenként a glTexCoord2f függvénnyel tehetjük, aminek 2 paramétere van x és y, melyek értékei [0,1] tartományba kell esniük (persze nem mindig lesz ez igaz, de most így kell). Ez az x és y érték azt tartalmazza, hogy, ha a kép felbontása a [0,1] tartományra lenne összenyomva hová essen a Vertex a képen (x, y koordináta). Így meg lehet azt is oldani, hogy egy képen többféle textúrarészlet van és minden vertexet szépen ráigazítunk a megfelelő részre. Ez például olyankor jó, ha van mondjuk egy autó modellünk és ki szeretnénk textúrázni, akkor nem kell egy rakás képet megrajzolni elég oldal, felül, alul, elől és hátul nézetből megrajzolni a kocsi színezését egy képen, és a textúra koordinátáit beállítani a megfelelő módon. Elég a rizsából, lássuk a kódot:
 private void DrawCube()
 {
     Gl.glEnable(Gl.GL_TEXTURE_2D);
     Gl.glBindTexture(Gl.GL_TEXTURE_2D, texId);

     Gl.glBegin(Gl.GL_QUADS);
     {
         // A "piros" oldal

         Gl.glTexCoord2f(0, 0);
         Gl.glVertex3f(-0.5f, +0.5f, -0.5f);
         Gl.glTexCoord2f(1, 0);
         Gl.glVertex3f(+0.5f, +0.5f, -0.5f);
         Gl.glTexCoord2f(1, 1);
         Gl.glVertex3f(+0.5f, -0.5f, -0.5f);
         Gl.glTexCoord2f(0, 1);
         Gl.glVertex3f(-0.5f, -0.5f, -0.5f);

         // A "zöld" oldal

         Gl.glTexCoord2f(0, 0);
         Gl.glVertex3f(-0.5f, +0.5f, +0.5f);
         Gl.glTexCoord2f(1, 0);
         Gl.glVertex3f(+0.5f, +0.5f, +0.5f);
         Gl.glTexCoord2f(1, 1);
         Gl.glVertex3f(+0.5f, -0.5f, +0.5f);
         Gl.glTexCoord2f(0, 1);
         Gl.glVertex3f(-0.5f, -0.5f, +0.5f);

         // A "kék" oldal

         Gl.glTexCoord2f(0, 0);
         Gl.glVertex3f(-0.5f, +0.5f, +0.5f);
         Gl.glTexCoord2f(1, 0);
         Gl.glVertex3f(-0.5f, +0.5f, -0.5f);
         Gl.glTexCoord2f(1, 1);
         Gl.glVertex3f(-0.5f, -0.5f, -0.5f);
         Gl.glTexCoord2f(0, 1);
         Gl.glVertex3f(-0.5f, -0.5f, +0.5f);

         // A "lila" oldal}

         Gl.glTexCoord2f(0, 0);
         Gl.glVertex3f(+0.5f, +0.5f, +0.5f);
         Gl.glTexCoord2f(1, 0);
         Gl.glVertex3f(+0.5f, +0.5f, -0.5f);
         Gl.glTexCoord2f(1, 1);
         Gl.glVertex3f(+0.5f, -0.5f, -0.5f);
         Gl.glTexCoord2f(0, 1);
         Gl.glVertex3f(+0.5f, -0.5f, +0.5f);

         // A "sárga" oldal

         Gl.glTexCoord2f(0, 0);
         Gl.glVertex3f(-0.5f, +0.5f, +0.5f);
         Gl.glTexCoord2f(1, 0);
         Gl.glVertex3f(+0.5f, +0.5f, +0.5f);
         Gl.glTexCoord2f(1, 1);
         Gl.glVertex3f(+0.5f, +0.5f, -0.5f);
         Gl.glTexCoord2f(0, 1);
         Gl.glVertex3f(-0.5f, +0.5f, -0.5f);

         // A "cián" oldal

         Gl.glTexCoord2f(0, 0);
         Gl.glVertex3f(-0.5f, -0.5f, +0.5f);
         Gl.glTexCoord2f(1, 0);
         Gl.glVertex3f(+0.5f, -0.5f, +0.5f);
         Gl.glTexCoord2f(1, 1);
         Gl.glVertex3f(+0.5f, -0.5f, -0.5f);
         Gl.glTexCoord2f(0, 1);
         Gl.glVertex3f(-0.5f, -0.5f, -0.5f);
     }
     Gl.glEnd();

     Gl.glDisable(Gl.GL_TEXTURE_2D);
 }
Mi változott? Az elején engedélyezve van a 2D-s textúra használat, a végén meg ki van kapcsolva (glEnable(GL_TEXTURE_2D);). Ez mindig kell, ha textúrát szeretnénk használni. Aztán a már ismertetett glBindTexture-rel aktiváljuk az texId-ben tárolt azonosítóval rendelkező textúránkat. És mehet is a rajzolás. Ami nem változott, csak annyi a különbség, hogy a helyett írtuk be a glColor3fglTexCoord2f parancsokat. Most a textúra koordinátáinkat úgy adtuk meg, hogy minden oldalon az első vertex a bal felső (0,0), a második a jobb felső (1,0), harmadik a jobb alsó (1,1), negyedik meg a bal alsó része a textúrának (0,1). Remélem ez a textúrázás rész senkinek sem okozott gondot! Ha mégis akkor lehet kérdezni. Egyébként textúrázásnál van még egy csomó beállítási lehetőség, meg mipmapping, de ezeket egyenlőre úgysem használjuk, csak bonyolítana. Az eredmény:

Textúrázott kocka OGL-ben

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