2010. augusztus 28., szombat

Tao Start 12 - Alpha Texture Blending

Az OpenGL univerzumban legutóbb a fények és az anyagok témakörével ismerkedtünk meg. Most viszont egy kicsit visszatérünk a textúrázás témájához mivel még rengeteg látványos dolog vár ránk ezen a területen is. Jelen írás témája az Alpha Texture Blending.
Lényege, hogy textúrázott felületet tudunk áttetszővé varázsolni! Olyasmire kell itt gondolni, mint egy katedrális mozaiküvege, vagy egy maszatos, kormos, összekarcolt, esetleg repedezett üveg. Jó néhány játékban találkozott már vele mindenki. Látványos effektek állíthatóak elő a használatával, és a megvalósítása szinte alig kerül plusz kódba. Akkor valósítsuk is ezt meg...
Természetesen először is szükségünk van egy textúrára. Méghozzá egy olyan textúrára lesz szükségünk, ami az RGB komponensek mellett rendelkezik alpha komponenssel is. Az RGBA színkomponensek közül az alpha (A) az áttetszőség modellezéséhez használható. Ezzel ugyanis azt írhatjuk elő, hogy az új fragmentum színe milyen mértékben vegyüljön a pixel jelenlegi színével. Az ilyen tulajdonsággal rendelkező textúrákat általában TGA (Truevision File Format) képformátumban szokták eltárolni. A Targa (*.TGA) formátum széles körben használt, főleg PC-s környezetben. Különleges szolgáltatásai – alfa-csatorna, gamma-érték, felületinformációk – közkedveltté tették a multimédia- és a programfejlesztők körében. Gyakorta használt formátum a 3D modellezésben és animáció-készítésben. Ez mind rendben is van, de sajnos a TGA formátumot a GDI+ nem támogatja ezért e képek betöltését magunknak kell megírnunk. Szerencsére elérhető egy komplex függvénykönyvtár amit .NET Targa Image Reader néven találhatunk meg a CodeProject.com oldalon. Ajánlom mindenkinek a figyelmébe és persze azonnal töltsük is le innen. Használata pedig rendkívül egyszerű. Adjuk a referenciákhoz hozzá a TargaImage.dll szerelvényt, majd egy TGA kép betöltése a következő:

Bitmap image = Paloma.TargaImage.LoadTargaImage(fileName)

Mivel most RGBA formátumú képet használunk, ezért nem elegendő a 24 bit, 32 bitre kell váltanunk. A TGA textúrát betöltő metódus a következő:
     private void LoadTargaTexture(string fileName)
     {
         Bitmap image = null;
         BitmapData data = null;

         try
         {
             image = Paloma.TargaImage.LoadTargaImage(fileName);
         }
         catch
         {
             Directory.SetCurrentDirectory(Application.StartupPath + @"\..\..\");
             image = Paloma.TargaImage.LoadTargaImage(fileName);
         }

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

             Gl.glEnable(Gl.GL_TEXTURE_2D);
             {
                 Gl.glGenTextures(1, out textureID);
                 Gl.glBindTexture(Gl.GL_TEXTURE_2D, textureID);
                 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_RGBA, image.Width, image.Height,
                     0, Gl.GL_BGRA, Gl.GL_UNSIGNED_BYTE, data.Scan0);
             }
             Gl.glDisable(Gl.GL_TEXTURE_2D);

             image.UnlockBits(data);
             image.Dispose();
         }
     }
Most, hogy előállítottunk egy megfelelő textúrát már csak fel kell azt használni. A példaprogramunkba egy kockát fogunk textúrázni és megjeleníteni a blending segítségével. Ahhoz, hogy átlátszóságot használhassunk az OpenGL-ben, a következő két utasítást kell elhelyezni a kódban:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Ez a két utasítás gondoskodik arról, hogy bekapcsolja a blending algoritmust az OpenGL-ben. A blending kezel minden átlátszósággal kapcsolatost számítást, nélküle csupán egy tömör poligon lenne a képernyőn. A második utasítás pontosan azt jelöli ki, hogy a forrás (amit blendelünk – átlátszóvá teszünk) alpha-csatornáját használja annak eldöntésére, hol mennyire átlátszó a poligon. Amennyiben valakit az átlátszóság matematikája érdekel, hasznos és viszonylag rövid bevezetőt talál a http://nehe.gamedev.net web-oldalon.
Tehát lépésenként a teendőink:
  1. A depth testing kikapcsolása. A depth testing lényege, hogy eldőljön, két primitív közül, melyik van közelebb a nézőponthoz. A távolabbit – részben legalábbis – nem jelenítjük meg, ha a közelebbi takarja. Mivel itt minden primitívnek láthatónak kell lennie, ezért ez a tesztelés felesleges és kellemetlen hatásokat hozhat. Tehát, cseréljük ki a glEnable(GL_DEPTH_TEST); sort a következőre: glDisable(GL_DEPTH_TEST);
  2. A textúrázás és a blendelés bekapcsolása.
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Átlátszó objektumok esetében könnyedén beláthatjuk, hogy más lesz az eredmény, ha egymás képét átfedő poligonokat a nézőponthoz képesti távolságuk szerint hátulról előre, vagy elölről hátulra haladva jelenítjük meg. Ezt a problémát általánosan csak úgy lehet megoldani, hogy kirajzoljuk a teljes jelenetet az átlátszó elemek nélkül bekapcsolt Z-pufferrel, majd ezután az átlátszók elemeket rajzoljuk ki a Z-tengely mentén a nézőponthoz képest hátulról előre haladva, kikapcsolt Z-puffer mellett.
Természetesen egy kocka esetében van egy apró trükk, amivel ez a rendezés elkerülhető. A trükk lényege, hogy a kockánkat kétszer rajzoljuk ki. Első menetben csak a hátsó lapokat rajzoljuk, pont azokat, amiket a glCullFace-el általában elszoktunk távolítani, majd a második menetben csak az elülső lapokat rajzoljuk a hátsó lapok elhagyása mellet. Így könnyen elérhetjük, hogy helyes sorrendben rajzolódjanak ki az elemek. A fentieket megvalósító Render() metódus:
     private void Render()
     {
         // A képernyő és a mélység puffer ürítése
         Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

         // A modellezési transzformáció
         Gl.glMatrixMode(Gl.GL_MODELVIEW);
         {
             Gl.glLoadIdentity();
             Gl.glTranslatef(0.0f, 0.0f, fDistance);
             Gl.glRotatef(-spinViewXY[1], 1.0f, 0.0f, 0.0f);
             Gl.glRotatef(-spinViewXY[0], 0.0f, 1.0f, 0.0f);
         }

         if (gBlending == true)
         {
             Gl.glDisable(Gl.GL_DEPTH_TEST);

             Gl.glEnable(Gl.GL_BLEND);
             Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);
             Gl.glBindTexture(Gl.GL_TEXTURE_2D, textureID);

             if (gSortUsingCullModeTrick == true)
             {
                 // Első menet

                 // A poligon eldobás engedélyezése
                 Gl.glEnable(Gl.GL_CULL_FACE);

                 // Csak a hátsó lapokat fogjuk kirajzolni, mivel az első lapok eldobásra kerülnek
                 Gl.glCullFace(Gl.GL_FRONT);

                 //Kirajzoljuk a kockát
                 Gl.glInterleavedArrays(Gl.GL_T2F_V3F, 0, cube);
                 Gl.glDrawArrays(Gl.GL_QUADS, 0, 24);

                 // Második menet, csak most a hátsó lapokat dobjuk el
                 Gl.glCullFace(Gl.GL_BACK);

                 Gl.glInterleavedArrays(Gl.GL_T2F_V3F, 0, cube);
                 Gl.glDrawArrays(Gl.GL_QUADS, 0, 24);

                 // A poligon eldobás letiltása
                 Gl.glDisable(Gl.GL_CULL_FACE);
             }
             else
             {
                 Gl.glInterleavedArrays(Gl.GL_T2F_V3F, 0, cube);
                 Gl.glDrawArrays(Gl.GL_QUADS, 0, 24);
             }
         }
         else
         {
             // A kocka rajzolása blend nélkül

             Gl.glDisable(Gl.GL_BLEND);
             Gl.glEnable(Gl.GL_DEPTH_TEST);
             Gl.glBindTexture(Gl.GL_TEXTURE_2D, textureID);
             Gl.glInterleavedArrays(Gl.GL_T2F_V3F, 0, cube);
             Gl.glDrawArrays(Gl.GL_QUADS, 0, 24);
         }

         //A grafikus csővezeték ürítése
         Gl.glFlush();
     }
Hátránya a módszernek az, hogy csak konvex, egymást nem átfedő poligonok esetében használható, valamint minden testet kétszer kell lerenderelni, igaz itt mind a két rajzolásra szükség is van. A példaprogram eredménye itt látható:


A programban az egér bal gombjának lenyomása mellett lehet forgatni a színteret, a fel és a le billentyű segítségével pedig közeledni és távolodni tudunk a kockától. Az „s” gombbal ki- és bekapcsolhatjuk a lapok rendezését, míg a „b” billentyűvel az átlátszóság kapcsolható ki/be. A program teljes forrása leölhető innen:


A tutoriálhoz felhasználtam Merczel László OpenGL cikkét a CodexOnline-on, valamint a CodeSampler oldalon található mintapéldát.

0 megjegyzés :

Megjegyzés küldése