Az OpenGL rutinkönyvtárai

Az előző cikkben leszögeztük, hogy C++-szal fogunk dolgozni a továbbiakban. Nos, aki már ismeri a C-t, tudja, hogy ez nem fog menni a megfelelő header és lib fájlok hiányában. Nagyban megkönnyíti a dolgunkat, hogy az OpenGL nem új keletű dolog, így a legtöbb fordítóhoz már mellékelik a fent említett komponenseket. Ha egyik vagy másik esetében mégsem lenne így, a http://www.opengl.org címen mindig elérhető belőlük a legújabb változat.

Röviden szólnék néhány szót ezekről a rutinkönyvtárakról és a hozzájuk tartozó header fájlokról. Az, amit mindig használni fogunk, az OPENGL32 és a hozzá tartozó GL.H nevű header fájl. Ez utóbbi általában a szabványos Include-könyvtár alatt egy GL könyvtárban van, így az #include <GL/gl.h> paranccsal kell beilleszteni a programunkba. (Linuxosok! Figyeljenek a kis és nagybetűk különbségére!)

Szintén sűrűn használt library a GLU32 (include: GL/glu.h), amely az OpenGL Utility Library rövidítése és arra szolgál, hogy extra matematikai megoldásokkal és adattípusokkal, függvényekkel segítse munkánkat. Az előbbi rutinkönyvtárhoz hasonlóan ez is standard része az OpenGL fejlesztői csomagjának.

A következő library már nem tartozik szervesen az OpenGL rutinkönyvtáraihoz, hanem egy különálló fejlesztés, viszont mi mégis használni fogjuk, mert rengeteg fejfájástól kíméli meg a programozót! Ez a library a GLUT (OpenGL Utility Toolkit). A fájlok pedig GLUT32 és GL/glut.h. Megjegyezném, hogy mivel ez nem standard, mint az előző kettő, nem mindegyik fordítóhoz mellékelik. (A CD mellékletre felkerül egy általam összeállított csomag, amely biztosítja, hogy a DevC++ használóinak is gond nélkül menjen a GLUT használata. Én ezzel dolgozom és tapasztaltam némi verzió-problémát, amikor megpróbáltam GLUT-ra épülő programot fordítani vele, ennek kijavítására szolgál a csomag.)

Nem tudom, ki mennyire van tisztában az ilyen külső rutinkönyvtárak használatával C-ben, ezért megjegyzem, hogy a linkernek senki se felejtse el beállítani, hogy a tárgykódhoz a fenti library-kat szerkessze hozzá, különben egyetlen példaprogram sem fog lefordulni! Ez a beállítás fordítónként más és más, ezért mindenkit megkérek, nézze meg annak dokumentációjában, hogyan kell!

Adattípusok

Ezzel most meg is volnánk, akár írhatnánk is az első programunkat, de még mindig van egy kis elméleti rész, amit át kell tekintenünk! Ez pedig nem más, mint az OpenGL adattípusai, amelyeket a függvények használnak és mi is használni fogunk. Az egyszerűség kedvéért az egyes típusokat a leginkább közel álló C típusokkal hasonlítjuk össze:

Adattípus

C Típus

OpenGL Típus

8 bites egész

signed char

GLbyte

16 bites egész

short

GLshort

32 bites egész

int, long

GLint, GLsizei

32 bites lebegőpontos

float

GLfloat, GLclampf

64 bites lebegőpontos

double

GLdouble, GLclampd

8 bites előjeltelen egész

unsigned char

GLubyte, GLboolean

16 bites előjeltelen egész

unsigned short

GLushort

32 bites előjeltelen egész

unsigned int, unsigned long

GLuint, GLenum, GLbitfield

 

Miután feltételezem, hogy C-ben nagyjából mindenki ismeri az adattípusokat, erre nem is szándékozom több időt vesztegetni.

Az első program

Hát igen! Bármily hihetetlen, idáig is eljutottunk! Álljon itt most egy rövid program, amely nem csinál mást, csak kirajzol egy fehér négyzetet a képernyőre! Utána pedig igyekszem minél érthetőbben elmagyarázni a kódot.

 

#define GLUT_DISABLE_ATEXIT_HACK

#include <windows.h>

 

#include <GL/gl.h>

#include <GL/glu.h>

#include <GL/glut.h>

 

void RenderScene(void)

{

      glClear(GL_COLOR_BUFFER_BIT); //(1)

      glClearColor(0.0f, 0.0f, 0.0f, 1.0f );    //(2)

      glColor3f(1.0f, 1.0f, 1.0f);              //(3)

      glBegin(GL_QUADS);                        //(4)

      glVertex3f(-0.5f, -0.5f, 0.0f);

      glVertex3f(-0.5f,  0.5f, 0.0f);

      glVertex3f( 0.5f,  0.5f, 0.0f);

      glVertex3f( 0.5f, -0.5f, 0.0f);

      glEnd();

      glutSwapBuffers();                        //(5)

}

 

int main(int argc, char* argv[])

{

      glutInit(&argc, argv);

      glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);   //(6)

      glutCreateWindow("Pelda01");                                      //(7)

      glutDisplayFunc(RenderScene);                                     //(8)

      glutMainLoop();

      return 0;

}

Hát, ez lenne az! Mindjárt el kell mondanom, hogy az első két sor azért kell, hogy a GLUT tudjon Windows környezetben is működni, az OpenGL-hez más köze nincsen. (Az 1. sor elég fordítófüggő dolog, MinGW-nél és CygWin-nél általában kell, különben a linker hibával kilép...)

Két függvényünk van: a main() és a RenderScene(). A GLUT működésének alapja, hogy inicializálja a képernyőt az OpenGL számára (6.), majd létrehoz egy ablakot, amibe rajzolni fogunk (7.).  Ha ez megvan, beállítunk neki egy függvényt, ami semmi mást nem csinál, csak letörli a viewportot (az ablaknak/képernyőnek azt a részét, ahová rajzolunk) és kirajzolja újra a képet. (8.) Ennek az animációnál van igazán szerepe, ahol közben néhány dolog megváltozik és más fog megjelenni az újrarajzolás után.

Valójában ez a folyamat (innentől úgy hívjuk: a renderelés) úgy történik, hogy két képpuffert használunk. Az egyiket nem látjuk, erre történik a rajzolás, a másik pedig éppen látható. Az előbbit hívjuk BackBuffernek. Ha a BackBuffer-ben befejeződött a rajzolás, a program megcseréli a két puffert (5.), így amit a BackBufferbe rajzoltunk, az válik láthatóvá.

A BackBuffer letörléséért az (1.) utasítás a felelős, a (2.) csupán annyit csinál, hogy a háttérszínt feketére állítja. Mivel ez az első utasítás, amely színeket kezel, megemlíteném, hogy a szineket 3 db 0 és 1 közötti float értékkel adjuk meg, egy RGB (vörös, zöld és kék színkomponensek) kombinációval. Értelemszerűen a (0,0,0) a fekete és az (1,1,1) a fehér. A negyedik, eddig nem említett paramétert alpha-nak hívjuk, de ezzel majd csak később foglalkozunk, addig maradjon 1.0f-re állítva ez az érték!

A (3.) utasítás legegyszerűbben fogalmazva azt a színt állítja be, amivel rajzolunk – ez itt most a fehér. Természetesen mindig a rajzolás előtt kell a színt állítani, utána meghívni a rajzolást végző függvényt.

A (4.) és egyben utolsó rész, amit tárgyalni szeretnék, egy példa a korábban említett primitive-ek használatára. A glBegin() függvény paramétere megadja, hogy milyen primitive kerül rögtön utána megadásra. Most nekünk bőven elég, ha ezt az egy primitive-et ismerjük, a következő cikkben úgy is sorra vesszük őket. A glBegin() lezáró párja a glEnd(), amelynek nincsen paramétere. A kettő között kell megadni sorban a létrehozandó primitive vertexeit. Erre a célra itt a Vertex3f()-et használtuk. A függvény 3 paramétere a vertex (x,y,z) koordinátáit jelenti.

Most, hogy láttunk egy OpenGL programot, remélhetőleg mindenki tisztában van annyira az alapokkal, hogy a következő részben továbbléphetünk az alap primitive-ek részletezésére. Aki még nincs, az nyugodtan próbálgassa a programban a koordinátákat, színértékeket a saját kedvére változtatni!

Ezúttal is várom kérdéseiteket, kommentárjaitokat e-mailben! Viszlát következő alkalommal!

Merczel László