OpenGL alapok

Ajánlott irodalom

FreeGLUT

Első OpenGL programunk

1. Új projekt létrehozása

Visual Studio-ban:

  1. File/New Project...
  2. Visual C++/Win32 - Win32 Console Application
  3. Application Settings - Empty Project

2. Új cpp file hozzáadása

  1. Project/Add New Item...
  2. Visual C++/Code - C++ File
  3. Ízlés szerinti file-név

3. Egy minimál OpenGL program:

#include <GL/glut.h>

void display(void) {}

int main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutCreateWindow("My First OpenGL App");
  glutDisplayFunc(display);
  glutMainLoop();
  return 0;
}

Első lépés a GLUT (most FreeGLUT) inicializálása.

A glutCreateWindow hozza létre az ablakot a megadott névvel.

A glutDisplayFunc-vel adhatjuk meg az eseménykezelőt, amit a GLUT az ablak újrarajzolásához fog meghívni. Ez lesz, az egyelőre üres, display függvényünk feladata. (Ez hasonló a DXUT-s onFrameRender-hez, de ez csak akkor hívódik meg, ha az ablakot újra kell rajzolni.)

A glutMainLoop elindítja a program üzenetkezelő ciklusát, és addig nem tér vissza (azaz nem ér véget a glutMainLoop futása), amíg a programot le nem állítjuk.

i Ha a program nem fordul

fatal error C1083: Cannot open include file: 'GL/glut.h': No such file or directory

Könyvtár beállítások:

  1. Tools/Options...
  2. Projects and Solutions/VC++ Directories
  3. Show directories for: Include files - FreeGLUT include könyvár hozzáadása
  4. Show directories for: Library files - FreeGLUT lib könyvár hozzáadása

i Ha a program nem fut

freeglut.dll nem található

A program által megtalálható helyre kell rakni a freeglut.dll-t. Pl. Windows\System32, vagy a projekt könyvtára.

4. Tegyük kicsit érdekesebbé a programot!

void display(void)
{
  glClear(GL_COLOR_BUFFER_BIT);
  glBegin(GL_TRIANGLES);
    glColor3f(0.0, 0.0, 1.0);
    glVertex2i(0, 0);
    glColor3f(0.0, 1.0, 0.0);
    glVertex2i(1, 1);
    glColor3f(1.0, 0.0, 0.0);
    glVertex2i(1, -1);
  glEnd();
  glFlush();
}

A glClear törli a paraméterként megadott buffereket: GL_COLOR_BUFFER_BIT a képernyő, GL_DEPTH_BUFFER_BIT a mélység-buffer (később lesz).

(A törlési szín, gyakorlatilag a háttér színe, a glClearColor-ral adható meg.)

A glBegin hívás jelzi a rajzolás kezdetét, amit majd a glEnd zár. Paraméterként a rajzolni kívánt primitívek típusát kell megadni. Ez lehet GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIP vagy GL_POLYGON. (V.ö.: DirectX primitívek)

A glColor az aktuális színt állítja be. Ez, a legtöbb OpenGL beállításhoz hasonlóan, a függvényhívástól lesz érvényben, egészen addig, amíg más színre át nem állítjuk.

A csúcspontok koordinátákat a glVertex függvénnyel kell megadni.

Minden glVertex hívás glBegin/glEnd pár között kell szerepeljen!

A glFlush hívás kikényszeríti a megelőző OpenGL hívások végrehajtását - többek között a rajzolást.

* OpenGL parancsok szerkezete

Minden parancs a gl, minden konstans pedig GL_ előtaggal kezdődik. Egyes parancsok különböző típusú és számú paramétert fogadhatnak. A parancs nevének vége jelzi a típust, és az azt megelőző szám a paraméterek számát. Pl.: glColor3f három lebegőpontos értéket vár a szín megadásához.

Az egyes típus jelző betűk jelentései:

b 8-bit integer signed char GLbyte
s 16-bit integer short GLshort
i 32-bit integer long GLint, GLsizei
f 32-bit floating-point float GLfloat, GLclampf
d 64-bit floating-point double GLdouble, GLclampd
ub 8-bit unsigned integer unsigned char GLubyte, GLboolean
us 16-bit unsigned integer unsigned short GLushort
ui 32-bit unsigned integer unsigned long GLuint, GLenum, GLbitfield

3D-zés OpenGL-lel

1. Írjunk egy függvényt, ami kirajzolja a koordináta tengelyeket! A tengelyek induljanak az origóból, és legyenek egység hosszúak! Az X, Y, Z tengelynek a színe legyen rendre piros, zöld, kék!

Ezt hívjuk is meg a display függvényben!

void axes()
{
    glBegin(GL_LINES);
        glColor3f(1, 0, 0);
        glVertex3f(0, 0, 0);
        glVertex3f(1, 0, 0);

        glColor3f(0, 1, 0);
        glVertex3f(0, 0, 0);
        glVertex3f(0, 1, 0);

        glColor3f(0, 0, 1);
        glVertex3f(0, 0, 0);
        glVertex3f(0, 0, 1);
    glEnd();
}

void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    axes();
    glFlush();
}

2. Így még nem látszik semmi 3D-ben. Forgassuk meg nézetet!

Adjuk meg a GLUT-nak, hogy folyamatosan hívja a rajzoló függvényünket!

int main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutCreateWindow("My First OpenGL App");
    glutDisplayFunc(display);
    glutIdleFunc(display); // <--
    glutMainLoop();
    return 0;
}

Most a kép folyamatosan villog és "zizeg". Ennek az oka, hogy ugyan arra a memória területre rajzolunk, mint amit megjelenítünk.

Erre megoldás az úgynevezett double buffer technika. Ezt a glutInitDisplayMode függvénnyel engedélyezhetjük. Ezt adjuk hozzá a main függvényhez, a glutCreateWindow előtt!

int main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); // <--
    glutCreateWindow("My First OpenGL App");
    glutDisplayFunc(display);
    glutIdleFunc(display);
    glutMainLoop();
    return 0;
}

Double buffer használata esetén a rajzolás befejezésekor glFlush helyett glutSwapBuffers-t kell használni (a glutSwapBuffers meghívja a glFlush-t mielőtt befejeződne)!

A forgatáshoz az OpenGL ModelView mátrixát kell átállítanunk.

Az OpenGL a transzformációs mátrixokhoz úgynevezett mátrix-vermeket használ. Minden mátrix művelet a verem tetején lévő mátrixra hat, és rajzoláskor szintén a verem tetején lévő mátrix adja meg a transzformációt.

Az akuális mátrix-verem a glMatrixMode függvénnyel választható ki.

A verembe az identitás mátrix a glLoadIdentity hívással tölthető be.

Forgatás a glRotate segítségével történik (a szöget fogban kell megadni). Ez, a többi transzformációval együtt, a verem tetején lévő mátrixot szorozza meg a transzformációval. Lásd még: glTranslate és glScale. A transzformációk nem hívhatók glBegin/glEnd pár között.

Összefoglalva:

void display(void)
{
    static float rot = 0;

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(rot, 1,1,1);
    rot += 1;

    glClear(GL_COLOR_BUFFER_BIT);
    axes();
    glutSwapBuffers();
}

3. Legyen a tengelyek közti "sarkakban" egy-egy négyzet, ami a két tengely színének keveréke!

void corners()
{
    glBegin(GL_QUADS);
        glColor3f(1, 1, 0);
        glVertex3f(0, 0, 0);
        glVertex3f(0.4f, 0, 0);
        glVertex3f(0.4f, 0.4f, 0);
        glVertex3f(0, 0.4f, 0);

        glColor3f(1, 0, 1);
        glVertex3f(0, 0, 0);
        glVertex3f(0.4f, 0, 0);
        glVertex3f(0.4f, 0, 0.4f);
        glVertex3f(0, 0, 0.4f);

        glColor3f(0, 1, 1);
        glVertex3f(0, 0, 0);
        glVertex3f(0, 0.4f, 0);
        glVertex3f(0, 0.4f, 0.4f);
        glVertex3f(0, 0, 0.4f);
    glEnd();
}

void display(void)
{
    static float rot = 0;

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glRotatef(rot, 1,1,1);
    rot += 1;

    glClear(GL_COLOR_BUFFER_BIT);
    axes();
    corners(); // <--
    glutSwapBuffers();
}

Ez helyenként elég furcsán fog kinézni:

no depth buffer

Ez az okozza, hogy a program nem használ mélységi buffert, ezért mindig a legutóbb rajzolt dolog, mélység értéktől függetlenül átrajzolja a már megjelenítetteket.

A mélységi buffer engedélyezéséhez írjuk át megint a main függvényt:

int main(int argc, char **argv)
{
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); // <--
    glutCreateWindow("My First OpenGL App");

    glClearDepth(1.0f);         // <--
    glEnable(GL_DEPTH_TEST);    // <--
    glDepthFunc(GL_LEQUAL);     // <--

    glutDisplayFunc(display);
    glutIdleFunc(display);
    glutMainLoop();
    return 0;
}

Az eredmény egyelőre rosszabb, mint volt:

no depth clear

Most mélységi buffert ugyan használ a programunk, de annak a tartalmát nem töröljük képkockánként, ezért már nem látható elemek takarják ki a legutóbbi rajzolás eredményét.

A mélységi buffer törlése szintén a glClear-rel történik.

Írjuk át ezt a display függvényben:

glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

A teljes program kódja letölthető innen.