2010. május 30., vasárnap

Tao Start 09 - GLUT a gyakorlatban

Elég régen írtam már OpenGL-el kapcsolatos bejegyzést, pedig megígértem, hogy a GLUT elméleti ismertetése után annak gyakorlati megvalósítását is megnézzük menedzselt környezetben. Most ezen elmaradásomat fogom pótolni.

Tudjuk már mi az a GLUT, vegyük hát használatba. Hozzunk létre egy sima konzolos projektet (console application) a VS segítségével, Form-ra most nem lesz szükség, mivel itt van helyette a GLUT. A Tao-ban a következő névtereket kell hozzáadni a projekthez:
using Tao.FreeGlut;
using Tao.OpenGl;
A referenciákhoz fel kell venni a Tao.FreeGlut, a Tao.OpenGL és a Tao.platform.Windows dll-eket. Sajnos viszont ennyi még nem elég mert szükségünk van egy natív dll-re is ami magát a FreeGlut-ot valósítja meg. Ezt a következő címről tudjuk letölteni: linkA letöltött dll 32 bites, amit másoljunk abba a mappába ahova majd a futtatható állomány kerül. Most hozzuk létre az InitGLUT metódust:
       static private void InitGLUT()
       {
           Glut.glutInit();

           // Duplán pufferelt, RGBA szín módú ablak
           Glut.glutInitDisplayMode(Glut.GLUT_DOUBLE | Glut.GLUT_RGBA);

           // Az ablak mérete
           Glut.glutInitWindowSize(800, 600);

           // Az ablak pozíciója
           Glut.glutInitWindowPosition(100, 100);

           // Az ablak neve
           Glut.glutCreateWindow("TaoGL - Glut Window");

           InitBitmap();

           // A képernyő kezelése
           Glut.glutDisplayFunc(new Glut.DisplayCallback(Display));

           // Az ablak átméretezés lekezelés
           Glut.glutReshapeFunc(new Glut.ReshapeCallback(Reshape));

           Glut.glutMotionFunc(new Glut.MotionCallback(Motion));

           // Az egér kezelése
           Glut.glutMouseFunc(new Glut.MouseCallback(Mouse));

           // A billentyűzet kezelése
           Glut.glutKeyboardFunc(new Glut.KeyboardCallback(Keyboard));

           // Belépés az esemény hurokba
           Glut.glutMainLoop();
       }
A metódus a kommentek segítségével könnyedén értelmezhető, az I/O eseményeket delegáltakkal valósítjuk meg pl.: new Glut.KeyboardCallback(Keyboard) aminek át kell adni a megvalósítást (Keyboard metódus), lásd később.
Az ablakra persze rajzolni is kell valamit, ez pedig most nem lesz más mint egy bitmap! Persze még nem fájlból töltjük be, de az alapok megértéséhez nagyon jó lesz:
       static private void InitBitmap()
       {
           // Create a pretty color ramp
           for (int j = 0; j < 256; j++)
           {
               for (int i = 0; i < 256; i++)
               {
                   bitmap[(256 * j + i) * 3 + 0] = (byte)(255 - i * j / 255);
                   bitmap[(256 * j + i) * 3 + 1] = (byte)i;
                   bitmap[(256 * j + i) * 3 + 2] = (byte)j;
               }
           }
       }
Az InitBitmap() metódusban a bitmap tömböt feltöltjük a kép RGB komponenseivel. Most következzen a rajzolás, amit a Display() metódus valósít meg:
      static private void Display()
      {
          // A képernyő törlése
          Gl.glClear(Gl.GL_COLOR_BUFFER_BIT);

          Gl.glRasterPos2i(0, 0);
          Gl.glBitmap(0, 0, 0, 0, rasterX, rasterY, null);
          Gl.glDrawPixels(256, 256, Gl.GL_RGB, Gl.GL_UNSIGNED_BYTE, bitmap);

          Glut.glutSwapBuffers();
      }
Három újdonság következik, amik bővebb magyarázatot igényelnek. OpenGL-ben kétféle raszteres objektum rajzolható: bittérkép és kép. OpenGL-ben a bittérkép pixelenként egyetlen bitben tárol információt (van vagy nincs képpont) és a rendszer maszkként kezeli ezt, a kép pedig pixelenként tárolja pl. az RGBA értékeket és nem maszkként kezeli a rendszer. Az OpenGL a bittérképeket és képeket mindig az aktuális raszterpozíciótól kezdődően rajzolja meg úgy, hogy a kurrens raszterpozíció lesz a bittérkép vagy kép bal alsó sarka. A kurrens raszterpozició (Xc, Yc) a

void glRasterPos{2 3 4}{s i f d}{# v}(T x, T y, T z, T w);

paranccsal adható meg. Értékét a

glGetFloatv(GL_CURRENT_RASTER_POSITION)

függvénnyel kérdezhetjük le. Bittérképek rajzolására a

void glBitmap(GLsizei width, GLsizei height,
GLfloat X0,
GLfloat Y0, GLfloat Xi, GLfloat Yi, const GLubyte *bitmap);

parancsot használjuk. A *bitmap a bittérkép címe, a width és a height a bittérkép pixelekben mért szélessége és magassága. Az (X0, Y0) párral a bittérkép bal alsó sarkának az eltolását adhatjuk meg, a raszterizálás után a rendszer a kurrens raszterpozíciót (Xi, Yi)-vel tolja el. A képek kirajzolása a

void glDrawPixels(GLsizei width, GLsizei height,
GLenum format, GLenum type, const GLvoid* pixels);

parancs segítségével történik, ahol format határozza meg, hogy hogyan kell értelmezni az egyes pixeleket, type a pixelek méretét és tárolási módját írja le, pixels pedig a kép tömbjére mutató pointer. A Reshape metódusban semmi olyan dolog nincs amiről már ne esett volna szó, merőleges vetítést alkalmazunk 2D-ben:
      static private void Reshape(int width, int height)
      {
          Gl.glViewport(0, 0, width, height);
          Gl.glMatrixMode(Gl.GL_PROJECTION);
          Gl.glLoadIdentity();
          Gl.glOrtho(0, width, 0, height, -1, 1);
          Gl.glMatrixMode(Gl.GL_MODELVIEW);
          Gl.glLoadIdentity();
      }
A Motion metódusban a bitmap-ünk koordinátáját frissítjük az egérkoordináták segítségével:
      static private void Motion(int x, int y)
      {
          y = Glut.glutGet(Glut.GLUT_WINDOW_HEIGHT) - y;
          rasterX = x - oldRasterX;
          rasterY = y - oldRasterY;
          Glut.glutPostRedisplay();
      }
A Mouse és a Keyboard metódusok az alapvető I/O műveletek megvalósítására mutatnak példát:
      static private void Mouse(int button, int state, int x, int y)
      {
          y = Glut.glutGet(Glut.GLUT_WINDOW_HEIGHT) - y;
          oldRasterX = x - rasterX;
          oldRasterY = y - rasterY;
      }

      static private void Keyboard(byte key, int x, int y)
      {
          switch (key)
          {
              case 27:
                  {
                      // Az ESC billentyű hatására kilépünk az alkalmazásból
                      Environment.Exit(0);
                  }
                  break;
              default:
                  break;
          }
      }
Legvégül pedig meg kell hívnunk még az InitGLUT metódust a Main metódusban:
      static void Main(string[] args)
      {
          InitGLUT();
      }
Ha most elsőre nem volt minden világos az csakis a szerző hibája, de előbb-utóbb biztosan le fog esni. A példa teljes forrása itt tölthető le:


A lényeget felejtettem csak el, hogy mit is csinál maga a program. Nos, egy 256x256 méretű színes négyzetet rajzolunk ki, amit az egér gombok lenyomása mellett interaktívan mozgathatunk a képernyőn. Ha meguntuk a játékot az ESC billentyűvel kilépünk az alkalmazásból.

2 megjegyzés :

  1. Adta a kis tutorialod! Csak igy tovabb.

    VálaszTörlés
  2. Kedves Lecso köszönöm a biztatást. Folytatom amíg bírom, ha van ötleted miről lehetne még szó, akkor kérlek oszd meg velem itt.
    Üdv

    VálaszTörlés