2010. február 20., szombat

Tao Start 05 - Test felépítése poligonokból

Itt az ideje, hogy végre készítsünk egy igazán 3D programot. Az előző OGL leckében szereplő példát fogjuk kiegészíteni, mert abban már benne van a vetítés. A Paint() eljárásban a glPushMatrix(); és glPopMatrix(); közötti részt teljesen ki is törölhetjük. Hogy átláthatóbb legyen a példánk csináljunk egy kockarajzoló eljárást. A neve legyen mondjuk DrawCube paramétere viszont egyáltalán ne legyen. Hogy miért, azt a lecke végére mindenki megtudja.
Akkor essünk is neki a DrawCube eljárásnak. Ha logikusan belegondolunk, annyit kell csak csinálnunk, mint ahogyan megrajzoltuk az előző leckében azt a téglalapot (ez lesz a kockánk egy oldala), csak hat ilyen oldalt kell rajzolni más pozíciókban (esetleg más színnel). Akkor egy glBegin(GL_QUADS) ... glEnd; közé nyomhatjuk is az oldalakat a megtanult módon. A legutóbb épp jól csináltuk, hisz, ha megnézitek egység hosszúak voltak az oldalélek, nekünk most pont ez kell. Tehát egy origó középpontú egységnyi kockára van szükségünk:
 private void DrawCube()
 {
     Gl.glBegin(Gl.GL_QUADS);
     {
         //A "piros" oldal

         Gl.glColor3f(1, 0, 0);
         Gl.glVertex3d(-0.5, +0.5, -0.5);
         Gl.glColor3f(0.75f, 0f, 0f);
         Gl.glVertex3d(+0.5, +0.5, -0.5);
         Gl.glColor3f(0.5f, 0f, 0f);
         Gl.glVertex3d(+0.5, -0.5, -0.5);
         Gl.glColor3f(0.25f, 0f, 0f);
         Gl.glVertex3d(-0.5, -0.5, -0.5);
  
         // A "zöld" oldal

         Gl.glColor3f(0, 1, 0);
         Gl.glVertex3d(-0.5, +0.5, +0.5);
         Gl.glColor3f(0f, 0.75f, 0f);
         Gl.glVertex3d(+0.5, +0.5, +0.5);
         Gl.glColor3f(0f, 0.5f, 0f);
         Gl.glVertex3d(+0.5, -0.5, +0.5);
         Gl.glColor3f(0f, 0.25f, 0f);
         Gl.glVertex3d(-0.5, -0.5, +0.5);

         // A "kék" oldal

         Gl.glColor3f(0, 0, 1);
         Gl.glVertex3d(-0.5, +0.5, +0.5);
         Gl.glColor3f(0f, 0f, 0.75f);
         Gl.glVertex3d(-0.5, +0.5, -0.5);
         Gl.glColor3f(0f, 0f, 0.5f);
         Gl.glVertex3d(-0.5, -0.5, -0.5);
         Gl.glColor3f(0f, 0f, 0.25f);
         Gl.glVertex3d(-0.5, -0.5, +0.5);

         // A "lila" oldal

         Gl.glColor3f(1, 0, 1);
         Gl.glVertex3d(+0.5, +0.5, +0.5);
         Gl.glColor3f(0.75f, 0f, 0.75f);
         Gl.glVertex3d(+0.5, +0.5, -0.5);
         Gl.glColor3f(0.5f, 0f, 0.5f);
         Gl.glVertex3d(+0.5, -0.5, -0.5);
         Gl.glColor3f(0.25f, 0f, 0.25f);
         Gl.glVertex3d(+0.5, -0.5, +0.5);

         // A "sárga" oldal
  
         Gl.glColor3f(1, 1, 0);
         Gl.glVertex3d(-0.5, +0.5, +0.5);
         Gl.glColor3f(0.75f, 0.75f, 0f);
         Gl.glVertex3d(+0.5, +0.5, +0.5);
         Gl.glColor3f(0.5f, 0.5f, 0f);
         Gl.glVertex3d(+0.5f, +0.5f, -0.5f);
         Gl.glColor3f(0.25f, 0.25f, 0f);
         Gl.glVertex3d(-0.5, +0.5, -0.5);

         // A "cián" oldal

         Gl.glColor3f(0, 1, 1);
         Gl.glVertex3d(-0.5, -0.5, 0.5);
         Gl.glColor3f(0f, 0.75f, 0.75f);
         Gl.glVertex3d(+0.5, -0.5, 0.5);
         Gl.glColor3f(0f, 0.5f, 0.5f);
         Gl.glVertex3d(+0.5, -0.5, -0.5);
         Gl.glColor3f(0f, 0.25f, 0.25f);
         Gl.glVertex3d(-0.5, -0.5, -0.5);
     }
     Gl.glEnd();
 }
Kissé hosszú. Meg lehetett volna csinálni for ciklussal, de így egyértelműbb. Az oldalakat különböző színekkel rajzoljuk ki, hogy látszódjon, hogy melyik-melyik. Bár úgy tűnik, mintha árnyalva lenne valójában nincs, csak ügyesen adtuk meg a színátmeneteket (egyre sötétedik). Az eljárás meghívása a Paint eljárásban történik a szokásos glPushMatrix(); és glPopMatrix(); között. A Paint metódusban a DrawCube hívás elé tegyük hát be a glRotatef(alfa,1,1,1);-et! Itt azért adtunk meg minden tengelyre 1-et, mert így egyszerre az összes tengely mentén forgatja az OGL, így gyakorlatilag a testátlója mentén fogja forgatni, vagyis minden oldala elő fog jönni egyszer. Akkor futtassuk! Hááát, kissé furcsa... Mért is néz ki így?
Egyértelmű a válasz, mert így rajzoltuk ki. Olyan sorban rajzolja az oldallapokat, amilyen sorrendben az eljárásunkban van, így amit a végén rajzoltunk az mindig látszani fog, az utolsó előttit már takarhatja az utolsó...stb. az elsőt meg minden takarja. Most akkor mi legyen...rendezzük ezeket sorba? Az elég lassú lenne, meg bizonyos extrém szituációkban (pl: egymást keresztező poligonok) nem vezetne eredményre. Természetesen az OpenGL biztosít a takaráshelyes ábrázolásra egy nagyon egyszerűen használható módszert. Ez pedig a mélység teszt. A lényege, hogy van egy úgynevezett mélység-buffer (DEPTH_BUFFER), amely akkora, mint az ablakunk (pontosabban a ViewPort-unk) és a megfelelő pixelhez tartozó mélység értéket tartalmazza és van egy frame-buffer (COLOR_BUFFER) ami szintén ekkora, csak ez a színeket tartalmazza. A buffernek minden pixele szerint indít egy-egy sugarat és elmetszi vele a poligonjainkat. Amikor metszett egy poligont megnézi, hogy a bufferben ott milyen mélység érték van. Ha azt veszi észre, hogy az az érték kisebb akkor felülírja a most metszett poligon mélységére és a frame-bufferben átírja az aktuális pixelhez tartozó színt a poligon színére (a metszés helyén lévő színre). Ha szépen végigvizsgálta az algoritmus minden pixelre-minden poligonra akkor a frame-bufferben a kívánt képet kapjuk. Nos hogyan használjuk mi ezt? Egyszerű, az OpenGL automatikusan használja, ha engedélyezve van (alapból semmi sincs engedélyezve). Az engedélyezés a glEnable(); paranccsal történik, melynek paraméterben meg kell adni az engedélyezni kívánt dolgot. Ez a paraméter most nekünk a GL_DEPTH_TEST lesz. Kikapcsolni a glDisable(); eljárással lehet, ami a paraméterében megadott dolgot kapcsolja ki. Ennek a DEPTH_TEST cuccnak még lehet egy csomó tulajdonságát módosítani, de nekünk most jó az alapértelmezés. Írjuk is be a Form konstruktorába hogy: glEnable(GL_DEPTH_TEST);
  public MainForm()
  {
      InitializeComponent();
      {
          this.ClientSize = new Size(800, 600);
          this.Text = "TaoGL - Transformations";
          this.glControl.InitializeContexts();
          this.Focus();
      }

      // A mélység teszt engedélyezése
      Gl.glEnable(Gl.GL_DEPTH_TEST);
      Reshape();
  }
És most futtassuk le a proginkat! Na, így már mindjárt jobban fest! Akkor most jön a másik dolog, ami miatt egység méretű kockát csináltunk. Ugye mi van, ha mi nagyobbat szeretnénk? A megoldás a harmadik féle pont transzformáció (egyben utolsó, bizony összesen 3 pont-transzformáció van az OpenGL-ben, de ezekkel bármit meg lehet oldani). Ez pedig a skálázás. A parancs: glScaleF(); , ahol 3 paraméter van ezek rendre: x mentén, y mentén és z mentén kívánt méretezés. Ha egy paraméter [0,1] tartományban van akkor kicsinyítés történt (pl: glScaleF(0.5,0.5,0.5); parancs minden irányban összenyomja felére a világunk objektumainak vertex koordinátáit.) Ha 1-nél nagyobb, akkor nagyítás történik, ha negatív, akkor a paraméternek megfelelő tengelyre tükrözés történik. Próbáljuk is ki! Csináljunk a kockánkból dupla akkorát! A forgatásunk után írjuk be: glScalef(2,2,2);. Ugye milyen egyszerű...így nem kell a rajzoló eljárásainkat paraméterezni. Akkor most tengely körüli forgatás előtt toljuk ki a kockánkat, és forgassuk meg, így keringeni is fog az origó körül és forogni is fog a tengelye körül. Ez valahogy így néz ki:
Gl.glRotatef(alfa, 0, 1, 0);
Gl.glTranslatef(5, 0, 0);
Gl.glRotatef(alfa, 1, 1, 1);
Gl.glScalef(2, 2, 2);
DrawCube;
Hát igen...itt köszön vissza az előző lecke vége. Mondtam, hogy nagyon fontos. Na jó, akkor egy kis ismétlés. Rakjunk be még egy kockát, amit skálázzunk úgy, hogy téglatest legyen, mondjuk x tengely mentén nyújtsuk meg jobban, és ő ne origó körül keringjen, hanem mondjuk az első kockánk körül. Ekkor ugye, ahogy volt az előző leckében minden rajzolást rakjunk külön PushMatrix-PopMatrix-ba, hogy ne hajtódjon végre mindkettőn minden. De azt megfigyelhetjük, hogy van, amit mindkettőn végre kell hajtani. Ugyanis az első kockát kiskálázzuk, megforgatjuk a tengelye körül - ez lesz egy Push-Pop párban, majd a 2. kockát kiskálázzuk, megforgatjuk a tengelye körül, eltoljuk, hogy az első kockához képest (ami az origóban van egyenlőre) távol legyen, majd megforgatjuk körülötte, így fog az origó (ahol most az első kocka van) körül keringeni, na ezek vannak a másik Push-Pop párban. Majd ezt, ami így kialakult ki kell tolni és meg kell forgatni az origó körül. Valahogy így:
    private void glControl_Paint(object sender, PaintEventArgs e)
    {
        Gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

        Gl.glPushMatrix();
        {
            // Ide jöhetnek a rajzoló utasítások
            Gl.glRotatef(alfa, 0, 1, 0);
            Gl.glTranslatef(10, 0, 0);

            // Az első kocka
            Gl.glPushMatrix();
            {
                Gl.glRotatef(alfa, 1, 1, 1);
                Gl.glScalef(2, 2, 2);
                DrawCube();
            }
            Gl.glPopMatrix();

            // A második kocka (téglatest)
            Gl.glPushMatrix();
            {
                // Az első kocka körüli keringés biztosítása
                Gl.glRotatef(alfa, 0, 1, 0);
                // Ez lesz a távolság ami az első kockától lesz
                Gl.glTranslatef(4, 0, 0);
                // Forgatás saját tengelye körül, csak "y" tengely
                Gl.glRotatef(alfa, 0, 1, 0);
                Gl.glScalef(3, 2, 2);
                DrawCube();
            }
            Gl.glPopMatrix();
        }
        Gl.glPopMatrix();

        Gl.glFlush();
    }
Remélem érthető volt ez a lecke. Ha nem, akkor mindenképp újra el kell gondolkodni rajta mert ez mindennek az alapja az OGL-ben.

Kocka és téglatest, ez a mai eredmény
Most már tudunk a kockák transzformálásával kísérletezni, amit mindenkinek ajánlok, hogy tegyen is meg! 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