OpenGL alapok
Ajánlott irodalom
- OpenGL RedBook
- OpenGL Referencia
- GLUT leírás és referencia
- Legjobb netes OpenGL tutorial (GLUT helyett WinAPI-t használ)
FreeGLUT
- Hivatalos oldal
- Letöltés
- Fordítás/Telepítés: README.win32 alapján
- Magyar leírás
Első OpenGL programunk
Visual Studio-ban:
- File/New Project...
- Visual C++/Win32 - Win32 Console Application
- Application Settings - Empty Project
2. Új cpp file hozzáadása
- Project/Add New Item...
- Visual C++/Code - C++ File
- Í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.
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:
- Tools/Options...
- Projects and Solutions/VC++ Directories
- Show directories for: Include files - FreeGLUT include könyvár hozzáadása
- Show directories for: Library files - FreeGLUT lib könyvár hozzáadása
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:
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:
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.