Ugrás a tartalomhoz

3D megjelenítési technikák

Dr. Fekete Róbert Tamás, Dr. Tamás Péter, Dr. Antal Ákos, Décsei-Paróczi Annamária (2014)

BME-MOGI

Első lépések az OpenGL-ben

Első lépések az OpenGL-ben

Az OpenGL 3 dimenziós grafikus alprogramrendszer. Ahhoz, hogy megteremtett eszközkapcsolat segítségével rajzolni tudjunk, tisztában kell lennünk néhány alapfogalommal. A rendszer RGBA színeket használ az objektumok megjelenítésére, színes elemek megjelenítésére használhatjuk az OpenGL függvényeit. Az az elem jelenik meg amit valamilyen rajzrutinnal beírunk a megjelenítési pufferekbe. A rajzrutinok térbeli objektumokat képeznek le a síkra, ahhoz, hogy megjelenjenek a leképezéseket szintén rajzrutinokkal állíthatjuk be. Végül mivel az OpenGL megjelenítési csövet használ (a rajzrutinok a rajzutasításokat „bedobálják” 4.1. ábra - Az Open GL megjelenítési csővezeték bal oldali bemenetébe, gondoskodnunk kell arról, hogy megjelenjen a rajzolat a képernyőn (a cső alján).

Színek megadása

A GDI+-hoz hasonlóan az OpenGL rendszerben is használhatjuk az RGBA színmodellt. Ha egy objektum színét szeretnénk megadni, akkor meg kell adni a szín piros (r), zöld(g) és kék(b) és esetleg alfa(a) – az áttetszőséget szabályozó – összetevőjét. A kapcsos zárójelek között a választható típusjelölők vannak.

void glColor3{b s i f d ub us ui} (TYPE r, TYPE g, TYPE b);
void glColor4{b s i f d ub us ui} (TYPE r, TYPE g, TYPE b, TYPEa);

Mivel az egyes színek intenzitását 0 és 1 közé eső valós számokkal jellemezhetjük. Ha a függvény valós számokat használ paraméterként, mint például a

void glColor3f (GLfloat red, GLfloat green, GLfloat blue);

függvény, amelyik 3 valós argumentummal határozza meg az RGB színt. Ha az alfa színösszetevőt is szeretnénk használni akkor a

void glColor4f (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);

függvényt használhatjuk, az argumentumok közvetlen 0 és 1 közé állításával. Lehetőségünk van például arra is, hogy byte vagy int típusú adatokat használjunk az intenzitások meg­adására. A rendszer ebben az esetben is a [0,1] intervallumon képzi az intenzitást, az adott típus legnagyobb értékéhez viszonyítva a megadott argumentumot. Például, az alábbi színdefiníciók egyenértékűek.

glColor3f (1.0, 1.0, 1.0);
glColor3b (127, 127, 127);

Ha megadunk egy színt, akkor az lesz a létrehozott elemek jellemző színe.

Használhatunk egy indexet egy színpaletta adott pozíciójának kiválasztására (ha a PIXELFORMATDESCRIPTOR iPixelType adattagja PFD_TYPE_COLORINDEX) és ezzel az aktuális szín kiválasztására. Ha a színindexet szeretnénk használni, akkor a paletta indexszel adhatjuk meg a színt a

void glIndex{s i f d ub}(TYPE c);

függvénnyel.

Rajzpufferek, a pufferek előkészítése

Már a megjelenítési kapcsolat kialakításánál találkoztunk a színeket- és a Z-távolságokat tároló, a takarásokat és az egyéb képtároló lehetőségeket támogató segédpufferekkel. A kép megjelenítésének kezdetén célszerű ezen adatterületeket törölni. A

void glClear(GLbitfield mask);

függvényt használhatjuk az adatpufferek törlésére. A függvény mask paraméterének bitjei a különböző puffereket azonosítják. A GL_COLOR_BUFFER_BIT bit a színek törléséről, a GL_DEPTH_BUFFER_BIT a Z-puffer adatainak törléséről, a GL_ACCUM_BUFFER_BIT a akkumulátor puffer törléséről, a GL_STENCIL_BUFFER_BIT pedig a stencilpuffer törléséről gondoskodik.

Az alábbi hívás szín- és a Z-puffert egyaránt törli:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

A pufferek törlésére használt adatokat is megadhatjuk a

void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
void glClearIndex(GLfloat index);
void glClearDepth(GLdouble depth);
void glClearStencil(GLint s);
void glClearAccum(GLfloat red, GLfloat green, GLfloat blue,GLfloat alpha);

függvényeket használva. Szürke hátteret készíthetünk például az alábbi hívással. (Az első három az rgb argumentum, az utolsó pedig az α, melynek értelmezésével a későbbiekben foglalkozunk.)

glClearColor( 0.8, 0.8, 0.8,1.0 );

Ha indexelt színnel törlünk, akkor a

void glClearIndex(TYPE c))

függvényt használhatjuk a szín c paraméterrel való kiválasztásával.

Kirajzolás hagyományos módon

Felmerül a kérdés, hogy mikor rajzoljuk meg az OpenGL rajzokat. Ha a kép statikus, akkor rajzolhatjuk a Paint vagy a Resize esemény kezelőjében. Ha folyamatosan változó képet szeretnénk megjeleníteni, akkor használhatjuk a Timer komponenst, szervezhetünk a rajzolásnak önálló programszálat is.

Mivel a megjelenítés az OpenGL-rendszerben a „megjelenítési csövön keresztül” történik, és a rajzoló és a megjelenítő gép akár különbözőek is lehetnek. A cső előtt még van egy parancssor, amibe az OpenGL gyűjti a rajzparancsokat. Ha ez megtelik, akkor rajzolja ki, így a GPU-t jobban ki tudja használni. Ha nem telik meg a sor, akkor szerepe van a kirajzolást kezdeményező

void glFlush() és
void glFinish()

függvényeknek. Az előző kezdeményezi a kirajzolást, azonban a program futása folytatódik, az utóbbi nem tér addig vissza, míg a kirajzolás be nem fejeződik.

Ha a megjelenítési kapcsolat kialakításakor több puffer használatát írtuk elő (PFD_DOUBLEBUFFER), akkor a

void SwapBuffers(HDC hdc)

függvénnyel cserélhetjük az adott eszközkapcsolat (hdc) „első” és „hátsó” adatterületét.

Hello OpenGL hagyományosan a C++/CLI-vel

Az alábbi példa C++/CLI form Paint eseményében töröl szürkével az ablakot.

private: System::Void Form1_Load(System::Object^  sender,
System::EventArgs^  e) {
    this->Text=”Hello OpenGL”;
    hwnd=(HWND)this->Handle.ToInt32();
    m_hDC = GetDC(hwnd);
    if(m_hDC) {
        MySetPixelFormat(m_hDC);
    }
}
private: System::Void Form1_FormClosing(System::Object^  sender,
                System::Windows::Forms::FormClosingEventArgs^ e) {
    wglMakeCurrent(0, 0);
    wglDeleteContext(m_hglrc);
}
private: System::Void Form1_Paint(System::Object^  sender,
                System::Windows::Forms::PaintEventArgs^ e) {
    glClearColor(0.5,0.5,0.5,1);
    glClear(GL_COLOR_BUFFER_BIT );
    glFinish();
}

4.4. ábra - Hagyományos OpenGL alkalmazás

Hagyományos OpenGL alkalmazás


Hello OpenGL a glut-tal

Első lépésként a glut.h fejléc (header) fájlt be kell építeni a forrásfájlba, és a glut32.lib fájlt hozzá kell szerkeszteni (link) a programhoz. A glut.h automatikusan beépíti a GL/gl.h és a GL/glu.h header fájlokat, a szükséges platformfüggő fejállományokkal együtt.

A GLUT használata előtt a GLUT-ot inicializálni kell a

void glutInit(int *argcp, char **argv);

függvénnyel, amelynek paraméterként meg kell adni a main-ben kapott parancssori argumentumokat tartalmazó paramétereket (argc, argv). Így lehetőség van a GLUT néhány paraméterét parancssorból is állítani. Ezután a

void glutInitWindowSize(int width, int height);

függvény az ablak szélességét és magasságát (width, height) állítja be, a

void glutInitWindowPosition(int x, int y);

pedig meghatározza a létrehozandó ablak pozícióját (x, y) lehet beállítani.

A

void glutInitDisplayMode(unsigned int mode);

függvénnyel lehet beállítani a létrehozandó ablakban lévő framebuffer tulajdonságait. A mode paraméter értékei a PIXELFORMATDESCRIPTOR adattagjainak elérését szolgálják. (a GLUT_RGBA, GLUT_INDEX a színek megadása, a GLUT_SINGLE, GLUT_DOUBLE a pufferelés módja, a GLUT_ACCUM, GLUT_ALPHA, GLUT_DEPTH, GLUT_STENCIL – a pufferek megadása stb.)

A GLUT_RGBA konstanssal jelezzük, hogy a framebuffer a színeket RGBA-ként tárolja, azaz minden pixelre jut egy vörös, zöld, kék és alfa összetevő.

A

int glutCreateWindow(char *name);

függvénnyel létrehozunk egy ablakot, amelynek feliratát argumentumként meg kell adni. Az ablak, a korábban a glutInit kezdetű függvényekkel beállított paraméterek alapján jön létre.

#include <GL/glut.h>
int main(int argc, char* argv[]){
  glutInit(&argc, argv);
  glutInitWindowSize(640, 480);
  glutInitWindowPosition(0, 0);
  glutInitDisplayMode(GLUT_RGBA);
  glutCreateWindow(”Hello OpenGL”);

Ezután be kell regisztrálni a callback függvényeket, amelyeket az ablakon történő egyes események hatására a GLUT fog meghívni. Ehhez adott prototípusú függvényekre mutató pointereket kell megadni, azaz a függvény nevét függvényhívó operátor nélkül. Végül a glutMainLoop() függvénnyel elindítjuk az eseménykezelő hurkot. Ez egy végtelen ciklus, amely folyamatosan várakozik a külső eseményekre, és meghívja a megfelelő beregisztrált függvényt. Innen a vezérlés nem fog vissza térni a main()-be. (A main() végén a return 0;-ra azért van szükség, hogy a fordító ne figyelmeztessen, hogy a visszatérési érték hiányzik).

Az OpenGL inicializálása utána az alkalmazás is végezhet incializálást, amelyet csak egyszer, a program legelején szükséges elvégezni. Ezért az eseménykezelő hurok indítása előtt meghívunk egy saját inicializáló függvényt. A tartalmát a main()-be is lehetne írni, de így áttekinthetőbb.

  glutDisplayFunc(onDisplay);
  onInit();
  glutMainLoop();

Ezután írjuk meg a hiányzó két függvényt.

void onInit()
{
  glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
}
void onDisplay()
{
  glClear(GL_COLOR_BUFFER_BIT);
  glFinish();
}

Az onInit() a glClearColor() függvénnyel átállítja a színt, amellyel a színpuffert töröljük. Az onDisplay() törli a színpuffer tartalmát a korábban beállított színnel. Paraméterként több puffer azonosítóját is meg lehet adni logikai VAGY kapcsolattal, de most a szín pufferen kívül nincs más puffer, ezért csak ezt töröljük.

Az OpenGL függvényhívások asszinkron hívások, azaz rögtön visszatérnek, mielőtt a művelet ténylegesen végrehajtódott volna. A glFinish() függvénnyel azt kérjük a rendszertől, hogy a kiadott parancsokat hajtsa végre, és ezt meg is várjuk. A programot lefordítva és futtatva a 4.5. ábra - GLUT alkalmazás képét kapjuk.

Ha a megjelenítési kapcsolat kialakításakor több puffer használatát írt/uk elő (glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE)), akkor a

void glutSwapBuffers();

függvényt hívhatjuk a pufferek cseréjére.

4.5. ábra - GLUT alkalmazás

GLUT alkalmazás


A továbbiakban az egyes lehetőségek bemutatása során a példákat elsősorban GLUT rendszerben készítjük el. Egyes esetekben, ahol interaktív lehetőségeket is megvalósítunk, visszatérünk a C++/CLI rendszerhez.