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

Rajzolás

Rajzolás

Az OpenGL térben (esetleg homogén koordinátákkal) definiált objektumokból készít - a képernyőn - síkbeli képet. Ahhoz, hogy a rendszer segítségével rajzot jelenítsünk meg, tisztában kell lennünk a képalkotás módszerével. Képzeljük el, hogy van egy munkaterünk, amelyben térbeli objektumokat helyezünk el! A cél az, hogy valamilyen irányból fényképet készítsünk az elhelyezett testekről és a fénykép jelenjen meg a képernyőn.

Alaphelyzetben a megjelenítési kapcsolat kialakításakor az ablak méretei határozzák meg a munkateret. A munkatér olyan, hogy a határoló tégla alaprajzát az ablak szélessége és magassága határozza meg, és a tégla magassága is megegyezik az ablak magasságával. A tér koordináta-rendszere olyan, hogy az origó a tégla közepére kerül, és a tégla szélei minden irányban 1 távolságra vannak az origótól.

A kamera az origóban helyezkedik el és negatív Z irányba „néz”. (4.6. ábra - A kamera analógia). A kép alapesetben úgy jön létre, hogy a kamera a végtelenből érkező, Z-tengely irányú, párhuzamos vetítősugarak képét rögzíti.

Az OpenGL rendszerben történő megjelenítés alapobjektumai az úgynevezett primitívek. Az alapvető primitívek a pont, a vonalszakasz, a szakaszokból alkotott vonallánc, a háromszög, a négyszög és a poligon. Ezek mellett használhatunk többféle felületi objektumot is.

4.6. ábra - A kamera analógia

A kamera analógia


Alapvető rajzelemek

Minden egyes primitív létrehozását a glBegin() függvény hívása kezdeményezi, és glEnd() zárja. A két függvény között definiált pontok (vertexek) határozzák meg a primitívek térbeli elhelyezkedését. A glBegin() függvény GLenum típusú mode paraméterétől függ, hogy milyen primitívet hozunk létre.

void glBegin(GLenum mode);

A mode paraméter lehetséges értékeit és azok értelmezését az alábbiakban foglaljuk össze:

GL_POINTS

A vertexek pontokat határoznak meg.

GL_LINES

Minden pár sarokpont egy vonalszakaszt határoz meg. A szakaszok nem alkotnak láncot. Ha páros n pontot adunk meg n/2 darab szakasz jön létre.

GL_LINE_STRIP

Minden pár sarokpont egy vonalszakaszt definiál. A szakaszok láncot alkotnak, n pont (n-1) darab szakaszt határoz meg.

GL_LINE_LOOP

A szakaszok zárt láncot alkotnak. Az n pont n szakaszt definiál.

GL_TRIANGLES

Minden ponthármas egy háromszöget határoz meg. A kifestett három­szögek nem alkotnak láncot. Hárommal osztható n pont esetén n/3 három­szög jön létre.

GL_TRIANGLE_STRIP

A megadott pontok egymáshoz oldalaival illeszkedő, kifestett három­szögekből álló láncot definiálnak. Ha n>3, akkor n-2 darab háromszög keletkezik.

GL_TRIANGLE_FAN

Záródó, kifestett háromszöglánc, melynek kezdőpontja közös (n>3 esetén n-2 darab háromszög).

GL_QUADS

Minden pontnégyes egy kifestett négyszöget határoz meg. A négyszögek nem alkotnak láncot. Ha n a 4 többszöröse, akkor n/4 négyszög keletkezik.

GL_QUAD_STRIP

A kifestett négyszögekből alkotott lánc, páros n esetén n/2 elemmel.

GL_POLYGON

A pontok n oldalú, kifestett poligont határoznak meg.

A csúcspontokat (vertex) a glVertexszámtípus() alakú függvényekkel adhatjuk meg a glBegin() és a glEnd() hívások között. Ha csak két koordinátát adunk meg, akkor a Z-koordináta automatikusan 0 lesz. Mivel dolgozhatunk a homogén koordinátás térben is, a rendszer automatikusan kezeli a negyedik koordinátát. Ha csak három koordinátát definiálunk, a negyedik - homogén - koordináta automatikusan az 1 értéket kapja.

4.7. ábra - Az alakzatok

Az alakzatok


Az alábbi példa téglalapot rajzol az ablakba. Azért téglalap, mert az 1 abszolút értékű koordináták a téglalap alakú ablak széleihez tartoznak. A módosított onDisplay() a következő lesz.

void onDisplay(){
  glClear(GL_COLOR_BUFFER_BIT);
  glBegin(GL_TRIANGLE_STRIP);
    glVertex2f(-0.5f, 0.5f);
    glVertex2f(0.5f, 0.5f);
    glVertex2f(-0.5f, -0.5f);
    glVertex2f(0.5f, -0.5f);
  glEnd();
  glFinish();
}

4.8. ábra - Az alakzatok

Az alakzatok


Négyszög rajzoláshoz használhattuk volna a GL_QUADS primitív típust is, de inkább GL_TRIANGLE_STRIP-et használtuk. A GL_QUADS, GL_QUAD_STRIP, GL_POLYGON OpenGL 2.0-ban még támogatott, azonban az OpenGL későbbi verzióiból kivették hatékonysági okokból.

A geometriai objektumok és megjelenítési módjaik

Az eddig megismertek alapján be tudjuk állítani a pixelnyi pontok, a vonalak, a kifestett poligonok és a kifestett felületek színét, megjelenítési módját.

Pontok

Pontok rajzolásakor (GL_POINTS) lehetőségünk van a pont megjelenítésének módosítására, a vonalak vastagságának és mintázatának meghatározására, a felületelemek oldalainak különböző módszerrel történő ábrázolására is.

A megjelenített pontok raszterpontban mért átmérőjét (size) állíthatjuk be a

void glPointSize (GLfloat size);

függvénnyel. Az alábbi példa az ablak közepére rajzol egy zöld 10 pixel méretű pontot

void onDisplay()
{
  glClear(GL_COLOR_BUFFER_BIT);
  glPointSize(10);
  glColor3f(0,1,0);            // a zöld szín
  glBegin(GL_POINTS);
   glVertex3f( 0, 0,0);
  glEnd();
  glFinish();
}

4.9. ábra - 10 pixeles zöld pont

10 pixeles zöld pont


Vonalak

Vonalak megjelenítésekor (GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP) a vonalak pixelben mért vastagságát (width) szabályozza az alábbi függvény:

void glLineWidth (GLfloat width);

Ha a vonalmintázatok használatát engedélyezzük glEnable(GL_LINE_STIPPLE), akkor saját mintázatú vonalakat hozhatunk létre. A vonal mintázatát a

void glLineStipple (GLint factor, GLushort pattern);

függvény argumentumaival határozhatjuk meg. A 16 bites pattern paraméter nem 0 bitjei definiálják a rajzolandó pontokat. Azért, hogy a mintázat ne csak 16-pontos legyen, használhatjuk a factor paramétert, melynek hatására a pattern minden egyes bitje factor darab, egymás utáni pontot jelent a vonalon kirajzolva. Az alábbi példa vastag folyamatos és vastag szaggatott vonallal rajzolt átlót jelenít meg.

void onDisplay()
{
    glClear(GL_COLOR_BUFFER_BIT);
    glLineWidth(10);
    glColor3f(1,1,0);            // a sárga szín
    glBegin(GL_LINES);
        glVertex3f( -1,-1,0);
        glVertex3f( 1,1,0);
    glEnd();
    glColor3f(0,1,1);            // a magenta szín
    glLineStipple (5, 0x0C0F);
    glEnable(GL_LINE_STIPPLE);
    glBegin(GL_LINES);
        glVertex3f( -1,1,0);
        glVertex3f( 1,-1,0);
    glEnd();
    glDisable(GL_LINE_STIPPLE);
 glFinish();
}

4.10. ábra - Vonaltípusok

Vonaltípusok


Konvex sokszögek

Konvex poligonok (GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIP, GL_POLYGON) megjelenésekor a térbeli sokszögeknek (poligonok) van első és hátsó oldaluk. Alapesetben az a poligon első oldala, amely irányból nézve a pontok óramutató járásával ellentétes körüljárást adnak. A sokszögek első oldala kiválasztható a

void glFrontFace(GLenum mode);

függvénnyel – a mode paraméterben a GL_CCW az alapesetet, a GL_CW pedig az alapesettel ellentétes oldalt meghatározó argumentumok megadásával.

A sokszögek rajzolásakor dönthetünk arról, hogyan jelenjen meg az alakzat első és a hátsó oldala. Alaphelyzetben a poligonok kifestettek. A kirajzolás azonban történhet csak a sarokpontokkal, az élekkel és végül a felületek lehetnek kifestettek. A

void glPolygonMode(GLenum face, GLenum mode);

függvényt használjuk az adatok beállítására. A face paramétertől függ, hogy melyik oldalt állítjuk be (GL_FRONT, GL_BACK, GL_FRONT_AND_BACK – felső, hátsó vagy mindkettő), a mode paraméter értékei pedig megjelenítés típusát adják (GL_POINT, GL_LINE, GL_FILL).

A nem láncban (STRIP) rajzolt sokszögek éleinek láthatóságát is szabályozhatjuk, így lehetőségünk van arra, hogy a nem konvex alakzatokat konvex alakzatokból összeállítva rajzoljuk ki. Az élek láthatóságát a megelőző vertex-nél határozhatjuk meg a

void glEdgeFlag(GLboolean flag);

függvénnyel. A flag logikai paraméter megadásakor használhatjuk a GL_TRUE és a GL_FALSE konstansokat is. Az élek láthatósága a poligonban nem változik egészen a következő glEdgeFlag() függvényhívásig.

void onDisplay()
{
glClear(GL_COLOR_BUFFER_BIT);
    glLineWidth(10);
    glColor3f(1,1,1);             // a fehér szín
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glBegin(GL_POLYGON);
        glEdgeFlag(GL_TRUE);
        glVertex3f(-0.5,-0.5,0);
        glEdgeFlag(GL_FALSE);
        glVertex3f(0.5,-0.5,0);
        glEdgeFlag(GL_TRUE);
        glVertex3f(0,0.5,0);
    glEnd();
glFinish();
}

4.11. ábra - Háromszög láthatatlan éllel

Háromszög láthatatlan éllel


Mintázatot is definiálhatunk a sokszögeknek a

void glPolygonStipple(const GLubyte *mask);

függvénnyel, ahol a mask tömb egy 32*32 méretű bitkép pontjainak megjelenését definiálja. Alaphelyzetben ennek értelmezése: ahol 1 van ott van poligon szín, ahol 0 ott nincs. Ezt a képet mint tapéta mintázatot használjuk. Ahhoz, hogy a mintázat megjelenjen, használnunk kell a glEnable(GL_POLYGON_STIPPLE) függvényhívást. Megszüntethetjük a mintázást a glDisable(GL_POLYGON_STIPPLE) függvényhívással. Az alábbi példa téglalapot jelenít meg véletlen mintázattal

void onDisplay()
{
    glClear(GL_COLOR_BUFFER_BIT);
    GLubyte vl[128];                 // 32*32/8
    for (int i=0 ; i<128 ; i++) {
        vl[i]=rand()%128+1;                 // stdlib.h
    }
    glColor4b(127,127,0,64);                 // sárga
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glEnable (GL_POLYGON_STIPPLE);
    glPolygonStipple (vl);
    glBegin(GL_POLYGON);
        glVertex3f( -0.6, -0.6,-0.5);
        glVertex3f( 0.6,-0.6,-0.5);
        glVertex3f(0.6,0.6,-0.5);
        glVertex3f(-0.6, 0.6,-0.5);
    glEnd();
    glDisable (GL_POLYGON_STIPPLE);
glFinish();
}

4.12. ábra - Mintázott sokszög

Mintázott sokszög


Ha megvilágítottan és árnyalva szeretnénk megjeleníteni a felületet, akkor szükségünk van a normálvektorokra. A háromszög három pontja meghatározza a sík normálvektorát. Azonban így egyik háromszögről a másikra áthaladva a normálvektor nem folytonosan változik, ezért az élek kiemelődnek. Elkerülhetjük ezt a jelenséget, ha minden ponthoz egy normálvektort is definiálunk. A később tárgyalásra kerülő árnyalt ábrázoláshoz a poligon minden sarokpontjába normálist is definiálhatunk a glNormalszámTípus() függvénnyel megadva a normális koordinátáit.

Színek, interpolálás

A glVertex() hívással hozunk létre egy vertexet, azaz bekerül a feldolgozandó vertexek közé. Az OpenGL egy nagyállapot gép, ezért a vertex attributúmait a legutoljára beállított állapot határozza meg. A glColor() függvénnyel akár vertexenként is beállíthatjuk a színeket.

void onDisplay()
{ 
  glClear(GL_COLOR_BUFFER_BIT);
  glBegin(GL_TRIANGLE_STRIP);
    glColor3f(1.0f, 0.0f, 0.0f); // vertex szín attribútum
    glVertex2f(-0.5f, 0.5f); // vertex létrehozás
    glColor3f(0.0f, 1.0f, 0.0f); // vertex szín attribútum
    glVertex2f(0.5f, 0.5f); // vertex létrehozás
    glColor3f(0.0f, 0.0f, 1.0f); // vertex szín attribútum
    glVertex2f(-0.5f, -0.5f); // vertex létrehozás
    glColor3f(1.0f, 1.0f, 0.0f); // vertex szín attribútum
    glVertex2f(0.5f, -0.5f); // vertex létrehozás
  glEnd();
  glFinish();
}

A grafikus csővezetékben a raszterizáló a létrejövő fragmenseken interpolálja a vertex attribútumokat. Ennek módja a

void glShadeModel( GLenum mode );

függvénnyel állítható. GL_SMOOTH hatására egy háromszögnél mind a három vertex attribútumai alapján interpolálja a fragmensek attribútumait (4.13. ábra - GL_SMOOTH). A GL_FLAT hatására az egész háromszög fragmensei az egyik vertex attributúmait veszik át (4.14. ábra - GL_FLAT). Ez az onInit()-ben állítható.

void onInit()
{ 
  glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
  glShadeModel(GL_SMOOTH);
}

4.13. ábra - GL_SMOOTH

GL_SMOOTH


4.14. ábra - GL_FLAT

GL_FLAT


A poligonok vertexek megadásával történő rajzolása helyett téglalapok rajzolására használhatjuk a

void glRecttipus(tipus x1, tipus y1,
       tipus x2, tipus y2);

függvényeket. Az alábbi példa is egy előző ábrán (4.8. ábra - Az alakzatok) látható téglalapot rajzolja ki csak pirossal.

private: System::Void Form1_Paint(System::Object^  sender,
 System::Windows::Forms::PaintEventArgs^  e) {
glColor3f(1,0,0);             // a piros szín
glRectf(-0.6, -0.6,0.6, 0.6);
glFinish();
}

Görbevonalú alakzatok rajzolása, tesszalláció

Görbevonalú alakzatok határát úgy rajzoljuk, hogy kiszámoljuk annak pontjait, és azokat egyenessel kötjük össze. Kör esetén a kör parametrikus egyenletébe néhányszor behelyettesítünk, és így hozzájutunk a kör néhány pontjához, amelyeket a kör középpontjával összekötve háromszögek alakulnak ki (4.15. ábra - Kör tesszeláció). Egy egyenletekkel megadott alakzat háromszögekké alakítása a tesszeláció. Látszik, hogy minél többször mintavételezzük a körvonalat, annál íveltebb lesz a háromszögelt kör. Cserébe több ponton kell a GPU számoljon.

4.15. ábra - Kör tesszeláció

Kör tesszeláció


void drawCircle(float radius, int tesselation = 32)
{
  glBegin(GL_TRIANGLE_FAN);
    glColor3f(1.0f, 1.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 0.0f); // középső vertex
    glColor3f(1.0f, 0.0f, 0.0f); // köríven lévő vertexek színe
    float delta = 2.0f * static_cast<float>(M_PI) / tesselation;
    for (int i = 0; i <= tesselation; ++i)
    {
      float t = i * delta;
      glVertex3f(radius * cosf(t), radius * sinf(t), 0.0f);
    }
  glEnd();
}

A kör középpontja minden háromszögnek az egyik pontja lesz, ezért GL_TRIANGLE_FAN-ként adjuk meg a csúcspontokat. A középpontnak, és a köríven lévő pontoknak különböző színt állítunk be. Ezután a tesszeláció mértéke alapján kiszámoljuk, hogy mekkora szöget kell fordulni minden mintavételkor (delta). Majd egy for ciklussal végig mintavételezzük a kör parametrikus egyenletét. Az egyenlet itt egyszerűbb, mint a fenti képletben, mert feltételezzük, hogy a kör mindig az origóban lesz. A for ciklus összesen tesselation + 1-szer fut le. Az utolsó vertex a 2π-nél van, azaz ott, ahonnan elindultunk. Erre azért van szükség, hogy az utolsó háromszög összekösse az utolsó pontot az elsővel.

Az alábbi példa az ablak arányaihoz igazított torzított körlapot rajzol a DrawCircle() függvénnyel.

void onDisplay()
{
  glClear(GL_COLOR_BUFFER_BIT);
  drawCircle(0.50F);
  glFinish();
}

4.16. ábra - Körlap

Körlap


Transzformációk

Hogyan jön létre a térbeli háromdimenziós - vagy akár homogén-koordinátákkal négydimenziós – objektumok képe a képernyőn? Ahhoz, hogy egy térbeli pontot leképezzünk a képernyő egy ablakába, több geometriai transzformációt is végre kell hajtanunk. (4.17. ábra - A képalkotási transzformációk sora)

A képet alkotó objektumokat úgy helyezhetjük el a térben, hogy felépítjük a geometriát, mondjuk az origó körül, és aztán azt geometriai transzformációval a kamerához képest a megfelelő térbeli pozícióba mozgatjuk (nézeti transzformáció).

A térbeli alakzatok síkbeli ábrázolásához először síkra kell vetíteni a képet. Ha a síkba történő vetítés sugarai párhuzamosak, akkor azt axonometriának hívjuk. Ha a szem működését szeretnénk modellezni, akkor centrális vetítést kell alkalmaznunk. (vetítő transzformáció)

Ha a síkba vetített képet ténylegesen a képernyő egy ablakában szeretnénk látni, akkor alkalmaznunk kell egy téglalapot, és a téglalapba konvertáló transzformációt (Window-Viewport transzformáció).

4.17. ábra - A képalkotási transzformációk sora

A képalkotási transzformációk sora


Az OpenGL a transzformációkat homogén koordinátákkal, 4*4-es mátrixszorzással végzi úgy, hogy egyetlen modellnézeti transzformációs és egyetlen vetítési transzformációs mátrixot használ. A képet alkotó elemek mindegyik pontja a modellezési transzformációnak megfelelően elmozdul, és a vetítési transzformációnak megfelelően kerül a síkbeli képre.

 

(4.1)

Minden újabb transzformáció az aktuális vetítési vagy modellezési transzformációs mátrix szorzását jelenti a megadott transzformációval.

A módosítás előtt a glMatrixMode() függvénnyel kiválaszthatjuk, hogy melyik mátrixra vonatkoznak a műveletek:

void glMatrixMode(GLenum mode);

Ha a mode paraméter értéke GL_MODELVIEW, akkor modellezési-, ha GL_PROJECTION, akkor vetítési transzformációt adunk meg. (A glGet(GL_MATRIX_MODE) hívással lekérdezhetjük, hogy éppen melyik mód az aktuális). A programban rendszerint a vetítési transzformáció megelőzi a modellezési transzformációt, hiszen először a kép méreteit, a kamera jellegzetességeit kell beállítanunk, és azután megfelelő pozícióból elkészíteni a képet.

Akármilyen mátrixmódban is dolgozunk, a glLoadIdentify() hívás hatására az aktuális transzformációs mátrixunk az egységmátrix lesz, ami a helyben hagyásnak felel meg. Lehetőségünk van arra, hogy 16 darab valós számmal definiáljuk a transzformációs mátrix koordinátáit.

void glLoadMatrixd (const GLdouble *m);
void glLoadMatrixf (const GLfloat *m);

Az m paraméter double (d eset), illetve float (f eset) típusú elemeket oszlopfolytonosan (!) tartalmazó tömb mutatója.

Az aktuális mátrixot akár be is szorozhatjuk egy argumentumként megadott tömbbel a glMultMatrixf() és a glMultMatrixd() függvények segítségével.

A fenti lehetőségekkel minden geometriai transzformáció végrehajtható, azonban gyakrabban használjuk a szemléletes, geometriai jelentést hordozó transzformációkat megvalósító függvényeket.

Modellezési transzformációk

A modellezési transzformációk glMatrixMode(GL_MODELVIEW) beállítás esetén kétféle szemléletmóddal is értelmezhetők. Felfoghatjuk a transzformációkat úgy, mint a térben elhelyezett objektumok mozgatását a fix helyzetben lévő kamera előtt (modellező transzformációk), illetve úgy is, hogy a kamera pozíciója és iránya változik (nézeti transzformáció) a képalkotáskor. Célszerű azonban a kétféle gondolkodás közül az egyiket kiválasztani, és annak tükrében értelmezni a geometriai transzformációkat. Az alábbiakban mi is ezt tesszük, a transzformációkat, mint a térbeli objektumok mozgatását fogjuk fel.

A legegyszerűbb geometriai transzformáció az eltolás, melyet az argumentumok típusától függően a

void glTranslated (GLdouble x, GLdouble y, GLdouble z);
void glTranslatef (GLfloat x, GLfloat y, GLfloat z);

függvényekkel valósíthatunk meg. Mindkét függvény hívásának hatása az, hogy a homogén koordinátás modell-transzformációs mátrixot megszorozza az (x,y,z) eltolást megvalósító mátrixszal.

Térbeli origón átmenő tengely körüli forgatásnak megfelelő transzformációt eredményeznek a

void glRotated(GLdouble angle,GLdouble x,GLdouble y,GLdouble z)
void glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z)

függvények. Az elforgatás tengelyvektorának irányát az (x,y,z) paraméterek, fokban mért szögét pedig az alpha paraméter határozzák meg.

Transzformációk sorozatát írhatjuk elő a segédprogramok könyvtárának - nézeti transzformációs szemléletű - gluLookAt() függvényével.

void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez,
          GLdouble centerx, GLdouble centery, GLdouble centerz,
          GLdouble upx, GLdouble upy, GLdouble upz );

A függvény paraméterei a kamera pozícióját (eyex,eyey,eyez), a kamera által „meg­célzott” térbeli pontot (centerx,centery,centerz) és a kamera felső oldalának irányát (upx,upy,upz) rögzítik. A transzformáció a modelltér koordináta-rendszerét (X,Y,Z) transzformálja új pozícióba (X’,Y’,Z’)

A függvény által megvalósított transzformáció a modellmátrix transzformációs sorának végére kerül, azaz először a többi geometriai transzformáció kerül sorra.

4.18. ábra - A gluLookAt() függvény kamera-pozíciója

A gluLookAt() függvény kamera-pozíciója


Az alábbi példa sárga, térben elforgatott téglalapot rajzol. A téglalap mellé még pirossal a koordináta-tengelyeket is megrajzoltuk.

void onDisplay()
{
         glMatrixMode(GL_MODELVIEW);
         glRotatef(60,10,10,10);
         glColor3f(1,1,0);            
         glRectf(-0.6, -0.6,0.6, 0.6);
         glColor3f(1.0, 0.0, 0.0);
         glBegin(GL_LINES);
            glVertex3f( 0,0,0);    
            glVertex3f( 0.2,0,0);
            glVertex3f( 0,0,0);
            glVertex3f( 0,0.2,0);
            glVertex3f( 0,0,0);
            glVertex3f( 0,0,0.2);
         glEnd();
 glFinish();
}

4.19. ábra - A forgó téglalap

A forgó téglalap


Vetítési transzformációk

A „A tér leképezése síkra” fejezetben áttekintettük a tér síkba vetítésének matematikai hátterét.

A vetítési transzformációk glMatrixMode(GL_PROJECTION) beállítás segítségével módosíthatjuk az alaphelyzet (4.20. ábra - A glOrtho() leképezés) merőleges párhuzamos vetítési előírása mellett a modelltér méreteit is. Alaphelyzetben a munkatér +X, -X, +Y, -Y és –Z irányban 1 kiterjedésű. A merőleges leképezés előírása mellett, a munkatér méreteit is meghatározza az alábbi függvény:

void glOrtho (GLdouble left, GLdouble right,
              GLdouble bottom, GLdouble top,
              GLdouble near, GLdouble far );

A left és right paraméterek a téglalap alakú munkatér jobb- és bal oldalának centrumtól mért távolságát, a top és bottom paraméterek pedig a munkatér felső és alsó szélének középponttól vett távolságát definiálják. A munkatér egy első és egy hátsó vágósíkkal határolt, melyek origótól való távolságát a near és far paraméterek határozzák meg (4.20. ábra - A glOrtho() leképezés).

A párhuzamos vetítés eltér a szem képalkotási módszerétől, így képen a távolabbi objektumok mérete ugyanakkora, mint a közelebbieké. Ha a szem képalkotásához szeretnénk illeszkedni, akkor centrális vetítést kell használnunk.

Centrális leképezést valósíthatunk meg a glFrustum(), illetve a segédkönyvtár gluPerspective() függvényeinek segítségével. Mindkét függvény definiálja a munkateret és a nézeti ablakot.

4.20. ábra - A glOrtho() leképezés

A glOrtho() leképezés


A paramétereinek értelmezéséhez az 4.21. ábra - A centrális leképezés lehet segítségünkre.

void glFrustum(GLdouble left, GLdouble right,
               GLdouble bottom, GLdouble top,
               GLdouble near, GLdouble far);
void gluPerspective(GLdouble alpha, GLdouble aspect,
               GLdouble near, GLdouble far);

Az alpha értéket fokokban kell megadni, az aspect paraméter csak a nézeti ablak oldalainak arányát rögzíti (4.2).

 

aspect=(left+right)/(top+bottom)

(4.2)

További, a

void glSetClipPlane(GLenum plane, GLdouble *equation);

függvény segítségével definiált vágósíkok is megadhatók (GL_MAX_CLIP_PLANES darab). Az equation pointer az Ax+By+Cz+D = 0 egyenlet együtthatóit tartalmazó mátrix pointere. Lekérdezhetjük az adatokat a

void glGetClipPlane(GLenum plane, GLdouble *equation);

A vágósíkok használatát egyenként engedélyezhetjük a glEnable(GL_CLIP_PLANEi) hívással.

4.21. ábra - A centrális leképezés

A centrális leképezés


Viewport-transzformációk

A „Window-Viewport transzformáció” fejezetben láttuk a Window-ViewPort leképezést. Ez is programozható OpenGL-ben.

A megjelenő nézeti ablak mérete alapértelmezés szerint az ábrának otthont adó ablak teljes mérete. A glViewport() függvénnyel lehetőségünk nyílik arra, hogy az aktív területnél kisebb ablakot jelöljünk ki.

void glViewport(GLint x, GLint y,
    GLsizei width, GLsizei height);

Az x, y paraméter a nézeti ablak bal alsó sarokpontját, a width és a height a szélességet és magasságot definiálja. Mindegyik paraméterérték pixelben értendő.

Rajzolás pixel koordinátákkal

Példaként állítsuk be egy olyan vetítés-mátrixot, hogy a kirajzolandó objektumokat az ablak megszokott grafikus koordináta-rendszerében lehessen megadni! Gondot jelent azonban ha az ablakot elkezdjük méretezni kézzel, akkor az elején beállított arány már nem lesz jó. CLI-ben ez nem okoz problémát, GLUT esetén a glutReshapeFunc() függvénnyel be lehet regisztrálni egy callback függvényt, amely az ablak méretezéskor hívódik meg az ablak új méreteivel. Ez az esemény az ablak létrejöttekor is aktiválódik.

int main(int argc, char* argv[]) {
  //glutInit...
  glutDisplayFunc(onDisplay);
  glutReshapeFunc(onResize);
  onInit();
  glutMainLoop();
  return 0;
}

A bal felső sarok az origó, az X tengely jobbra, az Y tengely lefelé nő, és mindkét tengelyen 1 egység 1 pixelnek feleljen meg!

A transzformációt a gluOrtho2D() függvénnyel lehet beállítani. A Z tengely -1 és +1 közötti része lesz látható. A gluOrtho2D() azonban az aktuális mátrixot szorozza meg ezzel a transzformációs mátrix-szal, így a glLoadIdentity() függvénnyel előtte inicializálni kell a transzformációt, különben érdekes meglepetések érhetnek minket az ablak méretezéskor. Az új onResize() függvény tehát így néz ki:

void onResize(int width, int height) {
  glViewport(0, 0, width, height); // viewport állítás
  if (height == 0)
    height = 1;
  glMatrixMode(GL_PROJECTION); // projection mátrix kiválasztása
  glLoadIdentity(); // egység mátrix beállítása
  gluOrtho2D(0.0, width, height, 0.0); // transzformáció beállítása
  glMatrixMode(GL_MODELVIEW); // modelview mátrix kiválasztása
}

A kört viszont mindig az origóba rajzoljuk, amely most az ablak bal felső sarkában található. A glTranslatef() függvénnyel az aktuális mátrixhoz egy eltolás transzformációt lehet hozzáadni. Az aktuális mátrix a modelview, mert az onResize() végén erre álltunk vissza. Mivel a modelview mátrixban ott maradhat az előző transzformáció, ezért ezt is a glLoadIdentity()-vel egység mátrixba állítjuk.

void onDisplay() {
  glClear(GL_COLOR_BUFFER_BIT);
   glLoadIdentity();
  glTranslatef(100.0f, 100.0f, 0.0f);
   drawCircle(50.0f, 64);
  glFinish();
}

Így a színes kör középpontja a 100,100 pontba kerül.

4.22. ábra - Kör pixel koordinátákkal

Kör pixel koordinátákkal


Mátrix verem

A transzformációk tárolására típusonként verem áll rendelkezésünkre. Ebbe a verembe menthetjük az aktuális mátrixot a glPushMatrix() hívással, illetve az utolsónak mentett mátrix aktuálissá tehető a glPopMatrix() hívással.

Takarások kezelése - a Z-puffer

A nem látható elemek eltüntetésére a legegyszerűbb megoldás a hátsó lap eldobásának módszere. („A láthatóság” fejezet) Ennek alapkoncepciója az, hogy ha a felületet (testet) alkotó poligonháló kifelé mutató normálisa n egy irányba mutat a ránézés irányával r , n*r>0, akkor az a háromszög nem látható.

A hátsó lapok eldobásának szabályozására használható a

void glCullFace(GLenum mode);

A mode paraméter határozza meg, hogy mely poligonokat dobjuk el (GL_FRONT – az elsőket, GL_BACK – a hátsókat, vagy GL_FRONT_AND_BACK – mindkettőt). A működés beállítható a glEnable(GL_CULL_FACE) hívással, és letiltható a glDisable(GL_CULL_FACE) függvény aktiválásával. Ha hagyományos – PIXELFORMATDESCRIPTOR strukúrával inicializálunk, akkor a z-puffer az alapértelmezett (Letilthatjuk a dwFlag PFD_DEPTH_DONTCARE bitjével). GLUT használatakor a

glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH);

hívás alkalmazható.

A takart részek eltávolítására az OpenGL a manapság már legelterjedtebb algoritmust a „A láthatóság” fejezetben ismertetett Z-puffer algoritmust használja.

A takarás vizsgálata csak akkor történik meg, ha azt engedélyeztük. Alapállapotban nincs távolságvizsgálat, és az objektumok a rajzolás sorrendjében a képernyőre kerülnek.

A mélységi teszt a glEnable(GL_DEPTH_TEST) függvény hívásával engedélyezhető. Az engedélyezett műveletet természetesen tilthatjuk is a glDisable(GL_DEPTH_TEST) függvénnyel.

Azt is szabályozhatjuk, hogyan történjen a Z-koordináták összehasonlítása. A

void glDepthFunc (GLenum func);

függvényt többféle GLenum típusú konstanssal is hívhatjuk. Például, ha engedélyezett távolságvizsgálat esetén a GL_LESS-t használjuk argumentumként, akkor a pont csak akkor jelenik meg, ha z-koordinátája kisebb, mint az aktuálisan tárolt z-érték. A vizsgálat fordítva is történhet, ha a GL_GREATER konstanst használjuk.

Az eddigi példák alapján vizsgáljuk meg a takarást! A kör koordinátáinak z komponense 0 volt. A gluOrtho2D() olyan transzformációs mátrixot készít, hogy az objektumok a z=-1 és +1 között kerülnek a képernyőre. Kipróbálhatjuk a takarást, ha a z=0 síkra kört, a z=-1 síkra pedig négyszöget rajzolunk.

Ha először kirajzoljuk a négyszöget, és utána a kört, akkor ez biztos jó lesz. Ezt hívják „painters” algoritmusnak, amikor először a legtávolabbi objektumot rajzoljuk ki, és utána rá a többit. De az OpenGL használható 3D grafikára is, így van más módja is a takarási probléma megoldásának.

void onDisplay()
{ 
  glClear(GL_COLOR_BUFFER_BIT);
 
  // kör rajzolás
  glLoadIdentity();
  glTranslatef(100.0f, 100.0f, 0.0f);
  drawCircle(50.0f, 64);
 
  // négyszög rajzolás
  glLoadIdentity();
  glColor3f(0.0f, 1.0f, 0.0f);
  glBegin(GL_TRIANGLE_STRIP);
    glVertex3f(100.0f,  50.0f, -1.0f);
    glVertex3f(200.0f,  40.0f, -1.0f);
    glVertex3f( 90.0f, 160.0f, -1.0f);
    glVertex3f(210.0f, 140.0f, -1.0f);
   glEnd();
 
  glFinish();
}

A kört eltoljuk a (100,100) pontba. A modelview mátrix a glTranslatef() után tartalmaz egy eltolás transzformációt, így minden utána következő objektum el lesz tolva. Mivel a négyszög koordinátáit közvetlenül világ koordinátákban adjuk meg, ezért az eltolásra már nincs szükség, így a modelview mátrixot a glLoadIdentity() függvénnyel egység mátrixba állítjuk.

4.23. ábra - A takarás kirajzolási sorrenddel

A takarás kirajzolási sorrenddel


A négyszög a kör elé került, pedig mögötte kellene lennie. A takarási probléma megoldására szükség van a mélységi pufferre (depth puffer, z-puffer). A mélységi puffer tárolja minden pixelre a pixel mélységi értékét. Amikor a fragmens shader után a fragmens a raszter műveletekhez jut, a pixel framebufferbe írása előtt a rendszer összehasonlítja az új fragmens mélységi értékét a mélységi pufferben lévő értékkel. Ha az új érték kisebb, mint a régi, azaz közelebb van a kamerához, akkor a pixel felül írja a framebuffer-ben lévő pixelt. Ha nagyobb, akkor a rendszer eldobja (4.24. ábra - Mélység teszt algoritmus) a pixelt.

Mint láttuk, a glutInitDisplayMode() függvénynek meg lehet adni, hogy az RGBA típusú színpuffer (GLUT_RGBA) mellé kérünk még egy mélységi puffert (GLUT_DEPTH) is. Ezután az OpenGL-ben engedélyezni kell a mélység tesztelést a glEnable(GL_DEPTH_TEST) hívással. A glDepthFunc() függvénnyel megadhatjuk a mélységi értékek összehasonlítási módját. Ez alapértelmezetten GL_LESS, azaz a mélységi teszten csak a kisebb mélységű pixelek fognak átjutni. Végül még arra is figyelni kell, hogy a rajzolások előtt ne csak a színpuffert töröljük, hanem a mélységi puffert is.

int main(int argc, char* argv[])
{
  //glutInit...
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH);
  glutCreateWindow(”Hello Circle”);
  //...
}
void onInit()
{
  glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_DEPTH_TEST);
}
void onDisplay()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  // kör és négyszög rajzolás
  glFinish();
}

4.24. ábra - Mélység teszt algoritmus

Mélység teszt algoritmus


4.25. ábra - Takarás mélység teszt algoritmussal

Takarás mélység teszt algoritmussal


Így az objektumok az elvártnak megfelelően takarják egymást.

Animáció

Az animáció két problémát is felvet. Biztosítani kell, hogy a geometria többször, villogásmentesen kerüljön a képernyőre.

A CLI-rendszerben a Timer komponens kezdeményezhet kirajzolást, és a Timer méri az eltelt időt is. A GLUT rendszerben a glutIdleFunc() regisztrál egy olyan callback függvényt, amelyet a GLUT akkor hív, ha éppen nincs semmilyen más esemény (ablakméretezés, rajzolás). Ha fizikai mozgást szeretnénk modellezni akkor a glutGet(GLUT_ELAPSED_TIME) hívással le lehet kérdezni a program indítása óta eltelt időt ezredmásodpercben.

Dupla pufferelés

Ha mindig ugyanabba a pufferbe rajzolunk, ami a képernyőn megjelenik, akkor az a kép villogását okozhatja. Az OpenGL parancsok asszinkron műveletek, így nem rögtön hajtódnak végre, először egy műveleti parancssorba kerülnek. A GPU-n lévő ütemező dönt a végrehajtás idejéről. A problémára megoldás a dupla pufferelés. Ekkor két framebuffer van, az egyik jelenik meg a képernyőn, miközben a másikba rajzolunk. A képernyőn lévő puffert front buffernek, az aktuális puffert back buffernek nevezzük. A rajzolás befejezésekor a két puffert ki kell cserélni, így a teljesen megrajzolt kép fog a felhasználónak megjelenni (4.26. ábra - Dupla pufferelés).

4.26. ábra - Dupla pufferelés

Dupla pufferelés


Hagyományos módon történő inicializálás esetén a PIXELFORMATDESCRIPTION struktúra dwFlag adattagjába be kell állítania PFD_DOUBLEBUFFER bitet, és a SwapBuffers() függvény hívásával cserélhetjük a puffereket.

GLUT esetén a dupla pufferelést a glutInitDisplayMode(GLUT_DOUBLE); függvényhívással lehet aktivizálni. A pufferek cserélését pedig a glutSwapBuffers() függvénnyel lehet kérni. A glutSwapBuffers() meghívja a glFinish() függvényt, így erre külön nincs szükség.

Példaként módosítsuk a 4.25. ábra - Takarás mélység teszt algoritmussal programját, hogy mozogjon a labda. Ehhez szükség lesz egy 2-dimenziós Vector osztályra, amellyel a vektor műveletek kényelmesen elvégezhetőek.

struct Vectorú
{
  float x, y;
  Vector(float x = 0.0f, float y = 0.0f) : x(x), y(y) {  }
  Vector& operator +=(const Vector& v) {
    x += v.x;
    y += v.y;
    return *this;
  }
} ;
Vector operator *(const float& a, const Vector& v)
{
  return Vector(a*v.x, a*v.y);
}

Ezután deklaráljunk néhány globális változót, amelyek tartalmazzák majd a labda pozícióját (position), sebességét (velocity), az ablak méretét, illetve az animációhoz szükséges utolsó frissítés idejét (lastTime).

Vector position(320.0f, 240.0f), velocity(100.0f, 100.0f);
int lastTime = 0;
float windowWidth = 640.0f, windowHeight = 480.0f;

A glutIdleFunc() függvénnyel regisztráljuk azt a callback függvényt, amelyet a GLUT fog hívni.

int main(int argc, char* argv[])
{
  //glutInit...
  glutDisplayFunc(onDisplay);
  glutReshapeFunc(onResize);
  glutIdleFunc(onIdle);
  onInit();
  glutMainLoop();
  return 0;
}
void onIdle()
{
  // idő mérés
  int now = glutGet(GLUT_ELAPSED_TIME);
  float dt = (now - lastTime) / 1000.0f;
  lastTime = now;
  // labda mozgatás
  position += dt * velocity;
  // ha a labda kiért a képernyőből
  // ...
  glutPostRedisplay();
}

A glutGet(GLUT_ELAPSED_TIME) kérdezi le a program indítása óta eltelt időt ezredmásodpercben. Ebből kivonjuk az utolsó lekérdezés idejét (lastTime), majd osztjuk 1000-el, így megkapjuk az előző onIdle() hívás óta eltelt időt másodpercben. Végül a lastTime változót frissítjük a mostani idővel.

Az eltelt idő, és a labda sebessége alapján frissítjük a labda pozícióját, majd megkérjük a rendszert, hogy frissítse az ablak tartalmát (glutPostRedisplay()). Így az alkalmazás eseménysorába bekerül az ablak újrarajzolását kiváltó esemény, és meghívódik az onDisplay() függvény. Közvetlenül is meg lehetne hívni az onDisplay()-t a glutPostRedisplay() helyett, de ez problémákhoz vezethet, ha egy bonyolult grafikánál az onDisplay() sokáig futna.

Azonban egy idő után a labda kiér a képernyőből. Ilyenkor a labdát vissza kellene rakni a képernyő közepére, majd egy véletlenszerű irányba elindítani.

// ha a labda kiért a képernyőből
if (position.x <= 0.0f || position.x >= windowWidth ||
    position.y <= 0.0f || position.y >= windowHeight)
{
  position.x = windowWidth / 2.0f;
  position.y = windowHeight / 2.0f;
  float length = 200.0f * randomFloat(0.5f, 1.0f);
  float angle = 2.0f * M_PI * randomFloat();
  velocity.x = length * cosf(angle);
  velocity.y = length * sinf(angle);
}

A randomFloat() függvény egy minimum és egy maximum érték között visszaad egy véletlen számot.

float randomFloat(float minValue = 0.0f, float maxValue = 1.0f)
{
  float randomBetween0and1 =
      static_cast<float>(rand()) / static_cast<float>(RAND_MAX);
  return minValue + randomBetween0and1 * (maxValue - minValue);
}
#define _USE_MATH_DEFINES
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <GL/glut.h>
void onInit()
{
  srand(time(NULL));
  glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
  glShadeModel(GL_SMOOTH);
  glEnable(GL_DEPTH_TEST);
  lastTime = glutGet(GLUT_ELAPSED_TIME);
}

Az onInit()-ben a véletlenszám generátor inicializálása, és az OpenGL működésének beállítása mellett még beállítjuk az eltelt idő számításánál használt lastTime változó kezdő értékét is.

Végül már csak a labdát kell a megfelelő helyre rajzolni, illetve az ablak átméretezésekor az ablak méreteit eltárolni a windowWidth, windowHeight változókban, hogy a következő labda indításkor is középről induljon a labda.

void onDisplay()
{
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 // kör rajzolás
 glLoadIdentity();
 glTranslatef(position.x, position.y, 0.0f);
 drawCircle(50.0f, 64);
 // négyszög rajzolás
 //...
 glFinish();
}
void onResize(int width, int height)
{
 windowWidth = static_cast<float>(width);
 windowHeight = static_cast<float>(height);
 glViewport(0, 0, width, height); // viewport állítás
 // ...
}

Így elértük, hogy mozogjon a képernyőn a labda. Ebből egy mozzanat látható az alábbi képernyőképen.

4.27. ábra - Dupla pufferelés

Dupla pufferelés


int main(int argc, char* argv[])
{
  glutInit(&argc, argv);
  glutInitWindowSize(640, 480);
  glutInitWindowPosition(0, 0);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
  glutCreateWindow(”Hello Circle”);
  //...
}
void onDisplay()
{
  //...
  glutSwapBuffers(); // glFinish helyett
}

Az alábbi CLI példa perspektívában forgó, csúszkával nagyítható kockát rajzol. A

static GLfloat z=30;
static GLfloat aspect=1;
static GLfloat szog=0;
int meret;

globális változók a zoom érték (z) az arány (aspect) és a szög (szog), illetve az ablakméret (meret). Az átméretezés esemény beállítja a méret értékei.

private: System::Void Form1_Resize(System::Object^  sender,
                                System::EventArgs^  e) {
    meret=min(this->Width,this->Height);
    glViewport((this->Width-meret)/ 2, (this->Height-meret) / 2 ,
     meret, meret);
    Invalidate();
    Refresh();
}

Az időzítő forgat:

private: System::Void timer1_Tick(System::Object^  sender,
                            System::EventArgs^  e) {
    szog+=5;
    Invalidate();
    Refresh();
}

A csúszkával a nagyítás állítható:

private: System::Void Zoom_Scroll(System::Object^  sender,
                System::Windows::Forms::ScrollEventArgs^  e) {
    z=Zoom->Value;
    Refresh();
}

Végül minden kirajzolást a Paint esemény végez:

private: System::Void Form1_Paint(System::Object^  sender,
                System::Windows::Forms::PaintEventArgs^  e) {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(z, 1, 1.0, 20.0);
    meret=min(this->Width,this->Height);
    glViewport((this->Width-meret) / 2, (this->Height-meret) / 2 ,
            meret, meret);
    glClearColor(0.5,0.5,0.5,1);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, -2.0, -15.0);
    glRotated(szog,0,1,0);
    glColor3f(1.0, 0.0, 0.0);
    glBegin(GL_POLYGON);
        glVertex3f(1.0, 1.0, 1.0);
        glVertex3f(-1.0, 1.0, 1.0);
        glVertex3f(-1.0, -1.0, 1.0);
        glVertex3f(1.0, -1.0, 1.0);
    glEnd();
    glColor3f(0, 1.0, 0.0);
    glBegin(GL_POLYGON);
        glVertex3f(1.0, 1.0, 3.0);
        glVertex3f(-1.0, 1.0, 3.0);
        glVertex3f(-1.0, -1.0, 3.0);
        glVertex3f(1.0, -1.0, 3.0);
    glEnd();
    glColor3f(0, 0.0, 1.0);
    glBegin(GL_POLYGON);
        glVertex3f(1.0, 1.0, 1.0);
        glVertex3f(1.0, 1.0, 3.0);
        glVertex3f(1.0, -1.0, 3.0);
        glVertex3f(1.0, -1.0, 1.0);
    glEnd();
    glColor3f(1.0, 1.0, 0.0);
    glBegin(GL_POLYGON);
        glVertex3f(-1.0, 1.0, 1.0);
        glVertex3f(-1.0, 1.0, 3.0);
        glVertex3f(-1.0, -1.0, 3.0);
        glVertex3f(-1.0, -1.0, 1.0);
    glEnd();
    glColor3f(1.0, 0.0, 1.0);
    glBegin(GL_POLYGON);
        glVertex3f(-1.0, 1.0, 1.0);
        glVertex3f(-1.0, 1.0, 3.0);
        glVertex3f(1.0, 1.0, 3.0);
        glVertex3f(1.0, 1.0, 1.0);
    glEnd;
    glColor3f(0.0, 1.0, 1.0);
    glBegin(GL_POLYGON);
        glVertex3f(-1.0, -1.0, 1.0);
        glVertex3f(-1.0, -1.0, 3.0);
        glVertex3f(1.0, -1.0, 3.0);
        glVertex3f(1.0, -1.0, 1.0);
    glEnd();
    glFlush();
}

4.28. ábra - Forgó perspektivikus kocka

Forgó perspektivikus kocka


A glu segédkönyvtár térbeli alapobjektumai

A segédkönyvtár lehetővé teszi, hogy olyan egyszerű térbeli felületeket hozzunk létre, mint a gömb, a tárcsa, a henger.

A létrehozáshoz deklarálni kell egy (GLUquadricObj típusra mutató) felületobjektum-pointert:

GLUquadricObj *quadObj;

Ezek után létrehozhatjuk az új felületobjektumot a

quadObj = gluNewQuadric ();

hívással. A felületeket az

 

(4.3)

egyenlet definiálja.

Az alapállapotban kifestett módon megjelenő felületek színét a már ismert színbeállítással lehet megadni. A térbeli felületeket a rendszer poligonokkal közelíti, ezért az elemek létrehozásakor meg kell adnunk a felosztások számát két független felületi irányban.

Középen lyukas lemezt hozhatunk létre az X-Y síkban az origó körül a

void gluDisk(GLUquadricObj *qobj, GLdouble innerRadius,
 GLdouble outerRadius, GLint slices, GLint loops );

függvénnyel. A függvény paraméterei a létrehozott felületobjektum (*qobj), a belső- (innerRadius) és a külső sugár (outerRadius). Megadhatjuk azt is, hogy a lemez hány cikkből (slices) és hány gyűrűből (loops) álljon.

A

void gluPartialDisk(GLUquadricObj *qobj, GLdouble innerRadius,
   GLdouble outerRadius, GLint slices,
   GLint loops,
   GLdouble startAngle, GLdouble sweepAngle);

egy fokokban megadott kezdőszög (startAngle) és a középponti szög (sweepAngle) paraméterrel jellemzett lemezcikket hoz létre.

Gömbfelületet készíthetünk az origó körül a

void gluSphere(GLUquadricObj *qobj, GLdouble radius, GLint slices, GLint stacks);

függvénnyel. A radius a sugarat a slices és a stacks a szélességi és hosszúsági körök számát határozza meg.

A

void gluCylinder(GLUquadricObj *qobj,
   GLdouble baseRadius, GLdouble topRadius,
   GLdouble height, GLint slices, GLint stacks);

függvény nevével ellentétben csonkapúpot (felfogható általánosított hengerfelületnek) készít. Az alapkör középpontja az origó, sugara a baseRadius. A +Z irányú kúp magassága height, a fedőlap sugara pedig topRadius. A slices és a stacks a felosztások száma.

Ha már nincs szükségünk a létrehozott objektumra megszüntethetjük a

void gluDeleteQuadric(quadObj);

függvény hívásával, melynek argumentuma az objektumra mutató pointer.

Gondoskodhatunk a térbeli objektumok megjelenítéséről is a felületobjektumok tulajdonságainak beállításával.

Az első és hátsó oldal szempontjából a sokszögekhez hasonló a helyzet a felületi elemek esetében. Az alábbi függvénnyel beállíthatjuk, hogy a felületi objektumnak melyik legyen az első felülete:

void gluQuadricOrientation (GLUquadricObj *qobj,
   GLenum orientation);

A paraméterként megadott orientation lehetséges értékei GLU_OUTSIDE és GLU_INSIDE lehetnek. A különböző felületi elemek esetén persze másképpen értelmezhető a külső és belső oldal.

A felületi elemeket határoló sokszögek kifestési módját a

void gluQuadricDrawStyle(GLUquadricObj *qobj, GLenum drawStyle);

függvénnyel állíthatjuk be. A gobj által mutatott elemre vonatkozó beállítások a (a drawstyle lehetséges értékei GLU_FILL - a kifestett poligonok az irányítottságot meghatározó normálvektor szerint kerülnek a képre, GLU_LINE - a felületek foltjaik határvonalával jelennek meg, GLU_SILHOUETTE - a felületfoltok határvonalai jelennek meg, ha a kapcsolódó felületfoltok nem párhuzamosak, GLU_POINT - a felület csúcspontokkal jelenik meg.

Poligonoknál is használhatunk kifestési mintázatot, amelyet a glEnable(GL_POLYGON_STIPPLE); hívással engedélyezünk, és a

void glPolygonStipple(const GLubyte *mask);

függvénnyel definiálunk. A mask paraméter egy 32*32-bites kétszínű bitkép.

Ha az előző fejezet végén a forgó kocka rajz Paint eseményében a kocka helyett gömböt, hengert, kúpot és poligont rajzolunk, kipróbálhatjuk a glu objektumok rajzolását is (4.29. ábra - A glu elemek).

private: System::Void Form1_Paint(System::Object^  sender,
System::Windows::Forms::PaintEventArgs^  e) {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(z, 1, 1.0, 20.0);
    meret=min(this->Width,this->Height);
    glViewport((this->Width-meret) / 2, (this->Height-meret) / 2 , meret, meret);
    glClearColor(0.5,0.5,0.5,1);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, -1.0, -15.0);
    glRotated(szog,0,1,0);
    GLUquadricObj *quadObj_gomb;
    quadObj_gomb = gluNewQuadric ();
    glColor3f(1,0,0);
    gluQuadricDrawStyle (quadObj_gomb, GLU_LINE);
    gluSphere (quadObj_gomb, 1.5, 16, 16);       
    gluDeleteQuadric(quadObj_gomb);
    glColor3f(0,1,0);
    GLUquadricObj *quadObj_henger;
    quadObj_henger = gluNewQuadric ();
    gluQuadricDrawStyle (quadObj_henger, GLU_FILL);
    gluCylinder(quadObj_henger, 0.3, 0.0, 0.6, 15, 10);
    gluDeleteQuadric(quadObj_henger);
    glColor3f(0,0,1);
    glPushMatrix ();
    glRotatef ((GLfloat)90.0, (GLfloat)1.0, (GLfloat)0.0, (GLfloat)0.0);
    glTranslatef ((GLfloat)0.0, (GLfloat)0.0, (GLfloat)-1.0);
    GLUquadricObj *quadObj_kup;
    quadObj_kup = gluNewQuadric ();
    gluQuadricDrawStyle (quadObj_kup, GLU_FILL);
    gluCylinder (quadObj_kup, 0.3, 0.3, 0.6, 12, 2);
    gluDeleteQuadric(quadObj_kup);
    glPopMatrix ();
    glPolygonMode(GL_FRONT_AND_BACK , GL_FILL);
    glColor3f(1,1,0);
    glBegin(GL_POLYGON);
        glVertex3f(0.5, 0.5, 1.0);
        glVertex3f(-0.5, 0.5, 1.0);
        glVertex3f(-0.5, -0.5, 1.0);
        glVertex3f(0.5, -0.5, 1.0);
    glEnd();
    glFlush();
    SwapBuffers(m_hDC);
}

4.29. ábra - A glu elemek

A glu elemek


Szabad formájú görbék és felületek

Többféle görbét és felületet is használhatunk.

Bezier görbék és felületek

Az OpenGL a tartópontok által meghatározott Bezier-görbék és -felületek kiszámolt pontjainak használatát is lehetővé teszi.

A „Bezier-féle harmadfokú görbeszakasz” fejezetben megismertek alapján tetszőleges [u1, u2] intervallumra könnyen felírható a Bezier-görbe. Az OpenGL az alábbi függvényeket kínálja az interpoláció végzésére:

void glMap1d(GLenum target, GLdouble u1, GLdouble u2,
             GLint stride, GLint order, const GLdouble *points);
void glMap1f(GLenum target, GLfloat u1, GLfloat u2,
             GLint stride, GLint order, const GLfloat *points);

A függvények target paramétere (GL_MAP1_VERTEX_3, GL_MAP1_VERTEX_4) szabályozza, hogy 3 vagy négy dimenzióban dolgozunk. Az u1, u2 a paramétertartományt jelöli ki. A stride paraméter az egy ponthoz tartozó valós adatok számát jelzi, az order pedig a közelítő polinom fokszámát rögzíti. A pont adatokat a points mutató által jelzett tömbben kell elhelyezni.

Az így definiált görbe pontjait használhatjuk kirajzolásra a glBegin(GL_LINRSTRIP) és a glEnd() hívások között megadott pontokkal. Azonban a pontokat nem a glVertex() függvények, hanem a glEvalCoords1típus() függvényekkel kell meghatározni.

A kijelölt interpoláció alapján a görbe tetszőleges u[u1,u2] paraméterhez tartozó pontját a (glEnable(GL_MAP1_VERTEX_3), vagy a glEnable(GL_MAP1_VERTEX_4)) vagy akár a normálisát (glEnable(GL_MAP1_NORMAL)) a

void glEvalCoord1d (GLdouble u);
void glEvalCoord1f (GLfloat u);

függvényekkel számíthatjuk.

Az alábbi példában a GLUT onDisplay() függvénye egy síkbeli Bezier görbét és tartópontjait rajzolja.

void onDisplay()
{
    glClear(GL_COLOR_BUFFER_BIT );
    GLfloat ctrlpoints[4][3] = {{ -0.8, -0.8, 0.0}, { -0.4, 0.8, 0.0},
                        {0.4, -0.8, 0.0}, {0.8, 0.8, 0.0}};
    glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, (GLfloat*)&ctrlpoints[0][0]);
    glEnable(GL_MAP1_VERTEX_3);
    glColor3f(1.0, 0.0, 0.0);
    glLineWidth(2.0);
    glBegin(GL_LINE_STRIP);
    for (int i = 0; i <= 30; i++)
        glEvalCoord1f((GLfloat) i/30.0);
    glEnd();
    glPointSize(4);
    glColor3f(0.0, 1.0, 0.0);
    glBegin(GL_POINTS);
    for (int i = 0; i < 4; i++)
        glVertex3fv((GLfloat*)&ctrlpoints[i][0]);
    glEnd();
    glFinish();
}

4.30. ábra - A Bezier görbe

A Bezier görbe


A görbékhez hasonlóan járhatunk el Bezier-felületek esetén.

A két paraméteren történő interpolációhoz az alábbi függvényeket használhatjuk:

void glMap2d(GLenum target, GLdouble u1, GLdouble u2,
 GLint ustride, GLint uorder,
 GLdouble v1, GLdouble v2,
 GLint vstride, GLint vorder,
 const GLdouble *points);
void glMap2f(GLenum target, GLfloat u1, GLfloat u2,
 GLint ustride, GLint uorder,
 GLfloat v1, GLfloat v2,
 GLint vstride, GLint vorder,
 const GLfloat *points);

Hasonlóan a görbékhez, attól függően, hogy a glEnable() függvény paramétereként a GL_MAP2_VERTEX_3, GL_MAP2_VERTEX_4 vagy a GL_MAP2_NORMAL értéket adjuk meg a

void WINAPI glEvalCoord2f( GLfloat u, GLfloat v);
void WINAPI glEvalCoord2d( GLdouble u, GLdouble v);

függvényeket használhatjuk kirajzoláskor a felületi görbék csúcspontjai helyett.

Ha „A glu segédkönyvtár térbeli alapobjektumai” fejezetben látható (quadric elemeket forgató) példa Paint eseményét az alábbira cseréljük, akkor Bezier-felületek paramétervonalait rajzoljuk ki.

private: System::Void Form1_Paint(System::Object^  sender,
                    System::Windows::Forms::PaintEventArgs^  e) {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(z, 1, 1.0, 20.0);
    meret=min(this->Width,this->Height);
    glViewport((this->Width-meret) / 2, (this->Height-meret) / 2 , meret, meret);
    glClearColor(0.5,0.5,0.5,1);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, -1.0, -15.0);
    glRotated(szog,0,1,0);
    GLfloat ctrlpoints[4][4][3] = {{{-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},
                     {0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
                     {{-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},
                     {0.5, -0.5, 0.0}, {1.5, -0.5, 1.0}},
                     {{-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},
                     {0.5, 0.5, 3.0}, {1.5, 0.5, 4.0}},
                     {{-1.5, 1.5, 2.0}, {-0.5, 1.5, 2.0},
                     {0.5, 1.5, 0.0}, {1.5, 1.5, 1.0}}
};
    glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,0, 1, 12, 4,
           (GLfloat*)&ctrlpoints[0][0][0]);
    glEnable(GL_MAP2_VERTEX_3);
    glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3f(1.0, 0.0, 0.0);
    glLineWidth(2);
    for (int j = 0; j <= 8; j++) {
        glBegin(GL_LINE_STRIP);
            for (int i = 0; i <= 30; i++)
                glEvalCoord2f((GLfloat)i/30.0, (GLfloat)j/8.0);
        glEnd();
        glBegin(GL_LINE_STRIP);
            for (int i = 0; i <= 30; i++)
                glEvalCoord2f((GLfloat)j/8.0, (GLfloat)i/30.0);
            glEnd();
}
    glFlush();
    SwapBuffers(m_hDC);
}

4.31. ábra - A Bezier felület

A Bezier felület


A glu NURBS görbéi és felületei

A „Interpoláció B-Spline görbékkel” és „Racionális, nem egyenközű B-spline (Non-Uniform Rational B-Spline - NURBS)” fejezetben megismert B-spline és NURBS görbék és felületek is megjeleníthetők.

A segédprogramok könyvtára a NURBS-görbék és -felületek létrehozását egyaránt támogatja. A Bezier-elemekkel ellentétben görbe-, vagy felületobjektum létrehozásakor nem kell törődnünk az interpolált pontok alapján történő megjelenítéssel. A NURBS-objektumot a

GLUnurbsObj* gluNewNurbsRenderer (void);

függvény hívásával hozhatunk létre. Ha már nincs szükség rá, az objektumot az alábbi függvénnyel törölhetjük:

void gluDeleteNurbsRenderer (GLUnurbsObj *nobj);

Ha görbét szeretnénk létrehozni, annak adatait a

void gluBeginCurve( GLUnurbs *nobj);

és a

void gluEndCurve( GLUnurbs *nobj);

a felület adatait pedig a

void gluBeginSurface( GLUnurbs *nobj);

és a

void gluEndSurface( GLUnurbs *nobj);

függvények hívásai között kell megadnunk.

NURBS-görbe adatainak megadása a

void gluNurbsCurve (GLUnurbsObj *nobj,
   GLint nknots, GLfloat *knot, GLint stride,
   GLfloat *ctlarray,
   GLint order, GLenum type);

függvénnyel történik. A paraméterek közül az nobj az objektumot azonosítja. NURBS-elemek esetén a nem egységes paraméterezést úgy valósíthatjuk meg, hogy a paraméterintervallumon a tartópontok számához a közelítés fokát hozzáadjuk, és ennek megfelelő számban nem csökkenő értékű csomópontsort hozunk létre. A knots a csomópontok számát tartalmazza a knot pedig a csomóponttömbre mutat. A stride az egy vezérlőponthoz tartozó adatok száma, míg a ctrlarray a tartópontok koordinátáit tartalmazó tömbre mutat. Az order a közelítés fokszáma +1, a type pedig a már ismert GL_MAP1_VERTEX_3 és GL_MAP1_VERTEX_4 értékek közül valamelyik.

Mivel a NURBS-felület kétparaméteres, ezért mindkét paraméterirányba meg kell adni az adatokat:

void gluNurbsSurface (GLUnurbsObj *nobj,
GLint uknot_count, GLfloat *uknot,
GLint vknot_count, GLfloat *vknot,
GLint u_stride, GLint v_stride,
GLfloat *ctlarray,
GLint uorder, GLint vorder, GLenum type);

a type pedig a már ismert GL_MAP2_VERTEX_3 és GL_MAP2_VERTEX_4 értékek közül valamelyik.

A NURBS-felületeknek nem kell négyzet topológiával rendelkezniük, levághatjuk a széleket a gluBegintTrim() és a gluEndTrim() függvények hívása között megadott zárt görbével. A gluNurbsCurve()függvény első (nobj) paramétere a létrehozott NURBS-objektumra mutató pointer A vágáshoz a gluPwlCurve() függvényt használhatjuk, amely a paramétertér pontjaival definiálja a vágást. Ilyen esetben csak a GLU_MAP1_TRIM_2, és a GLU_MAP1_TRIM_3 használható a type paraméterben.

A

void gluNurbsProperty( GLUnurbs *nobj,
             GLenum property,  GLfloat value);

függvény a NURBS megjelenítési módját szabályozza. A nobj paraméter a NURBS-t azonosítja, a property a megjelenítési beállítást tartalmazza, ha GLU_DISPLAY_MODE, akkor a value - GLU_FILL –kitöltés, GLU_OUTLINE_PATCH körvonal, GLU_OUTLINE_POLYGON – megjelenítő poligonok körvonala. Ha például a propertyGLU_U_STEP, vagy GLU_V_STEP, akkor a value a felosztást szabályozza (100 az alapérték).

A glu objektumok felületi normálisainak beállításával a megjelenítés minősége állítható. A

void gluQuadricNormals(GLUquadric *quadObject,GLenum normals);

függvénnyel a megadott objektumra (quadObject) beállíthatjuk az felületi normálisok megadásának módját. A normals paraméter értékei (GLU_NONE nincs normális megadva, GLU_FLAT -felületdarabonként egy normális, GLU_SMOOTH – minden vertexnél egy normális).

Az alábbi példában NURBS felületként készítünk Bezier felületet. A „A geometriai objektumok és megjelenítési módjaik” fejezet példájában csak a Paint eseményt cseréljük.

private: System::Void Form1_Paint(System::Object^  sender,
                    System::Windows::Forms::PaintEventArgs^  e) {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(z, 1, 1.0, 20.0);
    meret=min(this->Width,this->Height);
    glViewport((this->Width-meret) / 2, (this->Height-meret) / 2 ,
            meret, meret);
    glClearColor(0.5,0.5,0.5,1);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, -1.0, -15.0);
    glRotated(szog,0,1,0);
    GLfloat ctrlpoints[4][4][3] = {{{-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},
                        {0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
                        {{-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},
                        {0.5, -0.5, 0.0}, {1.5, -0.5, 1.0}},
                        {{-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},
                        {0.5, 0.5, 3.0}, {1.5, 0.5, 4.0}},
                        {{-1.5, 1.5, 2.0}, {-0.5, 1.5, 2.0},
                        {0.5, 1.5, 0.0}, {1.5, 1.5, 1.0}}
    };
    GLfloat csp[8]={0.0,0.0,0.0,0.0,1.0,1.0,1.0,1.0};
    glColor3f(1,1,0);
    GLUnurbsObj * nurbs;
    gluNurbsProperty (nurbs, GLU_DISPLAY_MODE,GLU_OUTLINE_POLYGON);
    nurbs=gluNewNurbsRenderer();
    gluBeginSurface(nurbs);
    gluNurbsSurface(nurbs,        // az objektum
            8,csp,        // az u-irányú csomópontok
            8,csp,        // az v-irányú csomópontok
            4*3,        // két u-ir. szomszédos pont
                    // távolsága a tömbben
            3,        // két v-ir. szomszédos pont
                    // távolsága a tömbben
            (GLfloat*)&ctrlpoints[0][0][0],
            // a pontokat tároló tömb
            4,4,        // a spline fokszám+1 u,v irányban
            GL_MAP2_VERTEX_3    // háromdimenziós csúcspontok
        );
    gluEndSurface(nurbs);
    glFlush();
    SwapBuffers(m_hDC);
}

4.32. ábra - A Bezier felület

A Bezier felület


Listák

A fejezet elején, a megjelenítési cső tárgyalásakor láttuk, hogy objektumokból listát készíthetünk, amelyet aztán egyetlen objektumként jeleníthetünk meg. A lista létrehozása a

void glNewList (GLuint list, GLenum mode);

függvény hívásával történik. Az (egész típusú) list paraméter egyértelműen azonosítja a listát. A mode paraméter meghatározza, hogy a hívást követően a listaelemek (primitívek) csak hívásukkal (GL_COMPILE), vagy végrehajtva (kirajzolva) kerüljenek a listába (GL_COMPILE_AND_EXECUTE).

A listát a

void glEndList(void);

függvény hívásával zárjuk.

A lista „lejátszható” (megjeleníthető), ha a

void glCallList(GLuint list);

függvény egyetlen argumentumaként (list) a lista azonosítóját adjuk meg.

Az alábbi példában egy piros gömböt és egy sárga lemezcikket tartalmazó listát készítünk a form betöltésekor:

private: System::Void Form1_Load(System::Object^  sender,
                            System::EventArgs^  e) {
    hwnd=(HWND)this->Handle.ToInt32();
    m_hDC = GetDC(hwnd);
    if(m_hDC) {
        MySetPixelFormat(m_hDC);
    }
    GLUquadricObj *quadObj;
    glNewList(1, GL_COMPILE);
        quadObj = gluNewQuadric ();
        gluQuadricDrawStyle (quadObj, GLU_LINE);
        glColor3f(1,0,0);
        gluSphere (quadObj, .5, 16, 16);
        glColor3f(1,1,0);
        gluPartialDisk(quadObj, .3,.6,20,20,90,180);
        gluDeleteQuadric(quadObj);
    glEndList();
    gluDeleteQuadric(quadObj);
}

A Paint eseményben csak a („A glu segédkönyvtár térbeli alapobjektumai” fejezetben látható, a quadric elemeket forgató példának megfelelő) forgatás, a kameraállítás és a lista kirajzolása történik.

private: System::Void Form1_Paint(System::Object^ sender,
                System::Windows::Forms::PaintEventArgs^ e){
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(z, 1, 1.0, 20.0);
    meret=min(this->Width,this->Height);
    glViewport((this->Width-meret) / 2, (this->Height-meret) / 2 , meret, meret);
    glClearColor(0.5,0.5,0.5,1);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -15.0);
    glRotated(szog,0,1,0);
    glCallList(1);
    glFlush();
    SwapBuffers(m_hDC);
}

4.33. ábra - A Bezier felület

A Bezier felület


Áttetsző anyagok, az RGBA színmodell használata

Ha a megjelenítési kapcsolatban az RGBA színmodellt használjuk (PFD_TYPE_RGBA), akkor a felületek áttetsző módon jelenhetnek meg. Az OpenGL úgy készít áttetsző felületet, hogy a pont színének meghatározásakor az adott pixelen már definiált színt is figyelembe veszi.

Az áttetsző megjelenítéshez engedélyeznünk kell a már kirajzolt és a rajz színének keverését a glEnable(GL_BLEND) hívással. Letilthatjuk a glDisable(GL_BLEND) hívással.

Azt, hogy a megjelenítendő objektum színe és az adott pixelre már betöltött szín milyen módon keveredik, a

void glBlendFunc( GLenum sfactor, GLenum dfactor);

függvény paramétereivel szabályozhatjuk. Arról van szó, hogy szorzófaktorokat definiálunk színenként a megjelenítendő (forrás - source - sfactor) színkomponensekre (Sr,Sg,Sb,Sa) és a már pixelen lévő (cél - destination - dfactor) színkomponensekre (Dr,Dg,Db,Da). Ha a megjelenítendő szín (Rs,Gs,Bs,As), és a pixel már kifestett színe (Rd,Gd,Bd,Ad), akkor a végső RGBA színdefiníció az alábbi kifejezés szerint adódik:

RGBA=( Rs * Sr + Rd * Dr , Gs * Sg + Gd * Dg , Bs * Sb + Bd * Db , As * Sa + Ad * Da )

Az S és D faktorok lehetséges értékei:

GL_ZERO

(0,0,0,0)

GL_ONE

(1,1,1,1)

GL_DST_COLOR

(Rd,Gd,Bd,Ad)

GL_SRC_COLOR

(Rs,Gs,Bs,As)

GL_ONE_MINUS_DST_COLOR

(1,1,1,1) - (Rd,Gd,Bd,Ad)

GL_ONE_MINUS_SRC_COLOR

(1,1,1,1) - (Rs,Gs,Bs,As)

GL_SRC_ALPHA

(As, As, As, As)

GL_ONE_MINUS_SRC_ALPHA

(1,1,1,1) - (As, As, As, As)

GL_DST_ALPHA

(Ad, Ad, Ad, Ad)

GL_ONE_MINUS_DST_ALPHA

(1,1,1,1) - (Ad, Ad, Ad, Ad)

GL_SRC_ALPHA_SATURATE

(f, f, f, f ) ahol f=min(As , 1- Ad)

Az alábbi

void glAlphaFunc(GLenum func, GLclampf ref );

függvénnyel előírhatjuk, hogy a fenti összevetést az alpha értékek függvényében hogyan használjuk. Az első paraméter az összevetés módját (GL_NEVER - soha, GL_ALWAYS – mindig, GL_LESS – kisebb, GL_GREATER nagyobb), a második pedig a küszöbértéket szabályozza. Alapértelmezés szerint az összevetés ki van kapcsolva.

Az alábbi példában a forgó kocka színeit áttetszőnek definiáljuk. Az áttetszőséget az alpha csúszkával szabályozhatjuk. Ilyenkor a PIXELFORMATDESCRIPTOR iPixelType adattagja PFD_TYPE_RGBA kell, legyen.

private: System::Void Form1_Paint(System::Object^  sender,
                System::Windows::Forms::PaintEventArgs^  e) {
glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(z, 1, 1.0, 20.0);
    meret=min(this->Width,this->Height);
    glViewport((this->Width-meret) / 2, (this->Height-meret) / 2 , meret, meret);
    glClearColor(0.5,0.5,0.5,1);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
    glTranslatef(0.0, -2.0, -15.0);
    glRotated(szog,0,1,0);
    // A színkeverés (alpha) figyelembevétele
    glEnable (GL_BLEND);
    // Az RGBA szinmodellben a rajzolt lapok alpha faktorát használjuk
    glBlendFunc (GL_SRC_ALPHA, GL_ONE);
    // Az alpha értéket a csúszka poziciója változtatja
float alp=(float)alpha->Value/alpha->Maximum;
    glColor4f(0,0,1,alp);
    glBegin(GL_POLYGON);
        glVertex3f(1.0, 1.0, 1.0);
        glVertex3f(-1.0, 1.0, 1.0);
        glVertex3f(-1.0, -1.0, 1.0);
        glVertex3f(1.0, -1.0, 1.0);
    glEnd();
    // Az összes lapra hasonlóan
…
    glFlush();
    SwapBuffers(m_hDC);
}

4.34. ábra - RGBA színek

RGBA színek


Megvilágítások

A térben elhelyezett felületi objektumokat megvilágíthatjuk, illetve megadhatjuk, hogyan verik vissza a fényt.

Ha a fényforrásokat engedélyezni szeretnénk, akkor a glEnable(GL_LIGHTING) függvényhívást kell alkalmaznunk.

Legfeljebb 8 fényforrást definiálhatunk GL_LIGHT0-GL_LIGHT7-ig sorszámozva, melyek közül a GL_LIGHT0-nak kitüntetett szerepe van. A fényforrások adatait az alábbi függvényekkel állíthatjuk be:

void glLightf (GLenum light, GLenum pname,GLfloat param );
void glLighti (GLenum light, GLenum pname,GLint param );
void glLightfv (GLenum light,GLenum pname,const GLfloat *params);
void glLightiv (GLenum light,GLenum pname,const GLint *params );

Mint ismeretes azokat a függvényeket, amelyek az i típusjelzőt hordozzák nevükben int adatokkal hívjuk, míg az f típusjelző float adatokra utal. Vannak olyan fényforrás beállítások, melyeket több adat is jellemez. Ilyenkor a jellemzők tömbjét használjuk argumentumként, ha a v típusjelzőt hordozó nevű függvényt hívjuk.

A függvények light paramétere a fényforrás sorszámát. a pname paraméter pedig a fényforrás típusát, illetve a fényforrást definiáló adat típusát rögzíti.

GL_AMBIENT

Négy paraméter a környezeti szórt fény RGBA intenzitását definiálja. Az alapérték (0,0,0,1.0).

GL_DIFFUSE

Négy paraméter a sugárzó fény RGBA intenzitását adja. Az alapérték a GL_LIGHT0 esetén (1.0, 1.0, 1.0,1.0), különben (0, 0, 0, 1.0).

GL_SPECULAR

A négy paraméter a tükröződő fény RGBA intenzitása. Az alapérték a GL_LIGHT0 esetén (1.0,1.0,1.0,1.0), különben (0,0,0,1.0).

GL_POSITION

A fényforrás homogén-koordinákban megadott térbeli helye. A négy paraméter alapértéke (0, 0, 1.0, 0).

GL_SPOT_DIRECTION

A fényforrás térkoordinátákban megadott iránya. A három koordináta alap­értéke (0, 0, -1.0).

GL_SPOT_EXPONENT

A fényforrás fókuszáltsága, azaz mennyivel csökken a visszaverődés intenzitása a beesési merőlegestől távolodva. A paraméter értéke 0-128 között kell, hogy legyen. Minél nagyobb az érték, annál inkább fókuszált a fény. Az alapérték 0.

GL_SPOT_CUTOFF

A megvilágítás terjedési szöge fokokban. A paraméter alapértéke 180.

GL_CONSTANT_ATTENUATION

GL_LINEAR_ATTENUATION

GL_QUADRATIC_ATTENUATION

A fényforrás távolsággal való intenzítás-csökkenése egy másodfokú polinom reciprokaként változik.

f=1/(c+l*d+q*d2)

A megadható paraméterek: a konstans (c), a lineáris (l) és a másodfokú tag (q) együtthatója. Az alap­értékek (1, 0, 0).

A megvilágítási modell paramétereit az alábbi egész vagy valós, egyparaméteres vagy paramétervektort alkalmazó függvényekkel is beállíthatjuk:

void glLightModelf (GLenum pname, GLfloat param);
void glLightModeli (GLenum pname, GLint param);
void glLightModelfv (GLenum pname, const GLfloat *params);
void glLightModeliv (GLenum pname, const GLint *params);

A paraméterek értelmezése:

GL_LIGHT_MODEL_AMBIENT

A négy paraméter a teljes modell szórt megvilágításának [0,1.0] közti RGBA értékét definiálja. Az alapérték (0.2, 0.2, 0.2, 1.0)

GL_LIGHT_MODEL_LOCAL_VIEWER

Egyetlen egész, vagy valós paraméter szabályozza, hogyan használja a rendszer ránézési irányt. 0 esetén a ránézési irány a –Z-tengely, egyébként a kamerát és az aktuális pontot összekötő egyenes. Az alapérték 0.

GL_LIGHT_MODEL_TWO_SIDE

Egyetlen egész, vagy valós paraméter szabályozza, hogy a felületek megvilágításakor egy vagy két oldalt vegyen figyelembe a rendszer. Az alapérték 0.

Érdemes megjegyezni, hogy akár ködös képet is ké­szíthetünk a glEnable(GL_FOG) hívással. A glFogi(), glFogf(), glFogiv() és a glFogfv() függvényeket használhatjuk a köd paramétereinek beállítására.

Anyagok

A felületek megjelenése nemcsak a megvilágítás tulajdonságaitól függ, hanem attól is, milyen „anyagtulajdonságai” vannak a megvilágított felületeknek.

A felületek normálisának fontos szerepe van a felületek oldalainak megkülönböztetésében, illetve a visszaverődés adatainak számításakor. A felületelem definiálásakor - a glBegin() és a glEnd() között - az aktuális elem normálvektora beállítható glNormal3típus() függvényekkel a vektor három koordinátáját megadva, illetve a glNormal3típusv() függvényekkel, melyek paramétere a normálvektort tartalmazó tömb kezdőcíme.

A megjelenítéskor az anyagtulajdonságokkal rendelkező elemek színe az alábbi tényezőkből számítva keletkezik:

szín=színanyag + megvilágításkörnyezet * megvilágításanyag + fényforrások(l, n, v)

A megjelenítés színe tehát függ az anyag színétől, a környezeti megvilágítástól, és attól, hogyan veri vissza az anyag a fényforrások felől érkező fénysugarakat. A fényforrások hatásában vehetjük figyelembe, hogy azok reflektorszerűek is lehetnek, azaz a megvilágítás iránya (d), a normálvektor (n), és a fényforrást és a pontot összekötő vektor (v), valamint a nézőpontot és a pontot összekötő vektor (l) hogyan határozzák meg a fény visszaverődését. Az alábbi tájékoztató jellegű képletben a vektorok egységvektorok és a „(,)” jelzés a skaláris szorzást jelenti amennyiben a visszaverődés értelmezett.

fényforrások(l, n, v) = intenzításfényforrások *
      (v,d) * (megvilágításkörnyezet * megvilágításanyag +
      (v,n)*szórtfényforrások * szórtanyag + (v+l,n)*tükrfényforrások * tükranyag)

Az anyag reflexiós tulajdonságainak beállításához egész és valós paraméterekkel rendelkező függvényeket használhatunk:

void glMateriali (GLenum face, GLenum pname, GLint param);
void glMaterialf (GLenum face, GLenum pname, GLfloat param);

A függvények paramétereit az alábbi táblázat segítségével értelmezhetjük:

face

Megadhatjuk, hogy a felület első (GL_FRONT), hátsó (GL_BACK), vagy mindkét oldalára (GL_FRONT_AND_BACK) érvényes-e a beállítás.

pname

Az anyag reflexiós tulajdonsága:

 

GL_AMBIENT

azonos fényesség a felületen,

GL_DIFFUSE

minden irányban szórt fény,

GL_ AMBIENT_AND_DIFFUSE

a fenti két elem összege,

GL_SPECULAR

tükröződő anyag,

GL_SHINESS

a fényesség nem függ a megvilágítás irányától,

GL_EMISSION

világító anyag,

GL_COLOR_INDEXES

a megvilágítás színe 0-s a indexű szín a szórtat és a tükröződőt a 1-s indexű szín definiálja.

param

A megadott anyagtípus RGBA paraméterei

Pontosabb beállításokhoz többparaméteres függvényeket használhatunk, ahol a params a beállítási adatokat tartalmazó vektor kezdőcíme.

void glMaterialfv(GLenum face, GLenum pname,
const GLfloat *params);
void glMaterialiv (GLenum face, GLenum pname,
const GLint *params);

Ha a glEnable(GL_COLOR_MATERIAL) függvényt meghívjuk, akkor az anyagtulajdonságokat a színek határozzák meg. Ilyenkor szín adatai alapján az anyag reflexiós tulajdonságait a

void glColorMaterial (GLenum face, GLenum mode);

függvénnyel állíthatjuk be. A face paraméter lehetséges értékei itt is a felület kérdéses oldalát jelölik. A mode paraméter értéke pedig a GL_EMISSION, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, vagy a GL_AMBIENT_AND_DIFFUSE értékek egyike lehet (az utolsó az alapértelmezett).

Az alábbi GLUT példa mozgó, tartópontokkal modellezett, árnyalt Bezier felületet mutat.

A mozgatáshoz szükséges adatok:

// ránézés távolsága
GLdouble tav;
// a tartópontok tömbje
GLfloat ctlpoint[4][4][3];
// a NURBS objektum
GLUnurbsObj * nurbs;

Az onInit() függvényben gondoskodunk a felület megvilágításáról és fényvisszaverés tulajdonságairól.

void onInit()
{
    glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
    // A felület szórt fényre való reakciója
    GLfloat szort[4] ={0.6,0.6,0.0,1.0};
    // A felület tükröződési adatai
    GLfloat tukros[4] ={1.0,1.0,0.0,1.0};
    // A felület fényessége
    GLfloat fenyes[1] ={100.0};
    GLfloat     viszony;
    glClearDepth( 1.0 );
    glEnable(GL_DEPTH_TEST);
    // A felület fényvisszaverési adatai
    glMaterialfv(GL_FRONT,GL_DIFFUSE,szort);     // szórt fény
    glMaterialfv(GL_FRONT,GL_SPECULAR,tukros);   // tükröződés
    glMaterialfv(GL_FRONT,GL_SHININESS,fenyes);  // fényesség
    // A default megvilágítás
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
   
    glEnable(GL_DEPTH_TEST);
   
    // A felület normálvaktorainak számítása tükröződéshez
    glEnable(GL_AUTO_NORMAL);
    glEnable(GL_NORMALIZE);
   
    // A NURBS objektum létrehozása
    nurbs=gluNewNurbsRenderer();
   
    // A mintavétel sűrüsége az arnyaláskor (pixel)
    gluNurbsProperty(nurbs,GLU_SAMPLING_TOLERANCE,25.0);
   
    // A NURB megjelenítése
    gluNurbsProperty(nurbs,GLU_DISPLAY_MODE,GLU_FILL);
   
}

Az onIdle() függvény időzít

void onIdle()
{
  // idő mérés
  int now = glutGet(GLUT_ELAPSED_TIME);
  float dt = (now - lastTime) / 1000.0f;
  if (dt>0.1)
  {
    lastTime = now;
    glutPostRedisplay();
  }
}

A kirajzolást az onDisplay() függvény végzi.

void onDisplay()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    int u,v;    // A tartópontok véletlen meghatározása
    for (u=0;u<4;u++)
      {
        for (v=0;v<4;v++)
          {
             ctlpoint[u][v][0]=2.0*((GLfloat)u-1.5);        // x
             ctlpoint[u][v][1]=2.0*((GLfloat)v-1.5);        // y
             ctlpoint[u][v][2]=3.0*rand()/(float)RAND_MAX;  // f(x,y)
          }
      }
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glPushMatrix();
    glTranslated(0.0, 0.0, -tav);
    glRotated(-30, 1.0, 0.0, 0.0);
    glScalef(.5,.5,.5);
    glPolygonMode(GL_FRONT_AND_BACK , GL_FILL);
    GLfloat csp[8]={0.0,0.0,0.0,0.0,1.0,1.0,1.0,1.0};
    // A NURBS felület létrehozása
    gluBeginSurface(nurbs);
      gluNurbsSurface(nurbs,          // az objektum
        8,csp,            // az u irányú csomópontok
        8,csp,            // az v irányú csomópontok
        4*3,              // két u-ir. szomszédos pont táv. a tömbben
        3,                // két v-ir. szomszédos pont táv. a tömbben
        &ctlpoint[0][0][0],     // a pontokat definiáló tömb
        4,4,                    // a spline fokszám+1 u,v irányban
        GL_MAP2_VERTEX_3        // háromdimenziós csúcspontok
        );
    gluEndSurface(nurbs);
    glPopMatrix();
    glutSwapBuffers();
}

Végül az időzítő duplán pufferelő főprogram.

int main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitWindowSize(640, 480);
    glutInitWindowPosition(0, 0);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
    glutCreateWindow("Árnyalás");
    glutIdleFunc(onIdle);
    onInit();
    glutReshapeFunc(onResize);
    glutDisplayFunc(onDisplay);
    glutMainLoop();
    return 0;
}

4.35. ábra - Árnyalt zászló

Árnyalt zászló


Árnyalások

A

void glShadeModel (GLenum mode);

függvénnyel a rajzolás árnyalási modelljét állíthatjuk be. A GL_SMOOTH (az alapértelmezett) normálist a vertexekben számítja a rendszer (4.35. ábra - Árnyalt zászló/a), vagy GL_FLAT az egyszerű felületdarabonként csak egy normális használatos. Ha az onInit() függvényben beállítjuk a

glShadeModel(GL_FLAT);

értéket, akkor a zászló kockássá válik (4.35. ábra - Árnyalt zászló/b).

Textúra

Az anyagminták (textúrák) használatának lehetőségeire vonatkozó ízelítővel zárjuk az OpenGL bemutatkozását célzó fejezetet. Az anyagminták használatának első lépése az anyagminta létrehozása. A textúra lehet egydimenziós, azonban az alapeset a kétdimenziós kép, melyet felületen szeretnénk megjeleníteni. Általában egyetlen képet használunk, és azt feszítjük rá a felületekre. Azonban arra is van lehetőségünk, hogy egyetlen képet több felbontásban is elkészítsünk, és mindig a leggazdaságosabban megjeleníthetőt használjuk (mip-map), ennek részleteivel azonban nem foglalkozunk.

A textúra elkészítése

A textúrát egy pontjaival meghatározott kép alapján készíthetjük el a

void glTexImage2D (GLenum target, GLint level, GLint components,
    GLsizei width, GLsizei height, GLint border,
    GLenum format, GLenum type, const GLvoid *pixels);

függvény segítségével. A függvény target paramétere GL_TEXTURE_2D érték kell legyen. A level paraméter a mip-map-ek számát adja meg, ha csak egy kép van, akkor értéke 0. A components paraméterben a színkomponensek számát definiálhatjuk. A kép méreteit a width, height, keretét a border paraméter tartalmazza. A format paraméter definiálja a használt színmodellt (pl. GL_RGB, GL_RGBA stb.). A type paraméter a színadatok típusát tárolja (GL_INT, GL_FLOAT stb.) és végül a pixels a pontokat definiáló színeket tartalmazó tömb kezdőcíme.

Az alábbi függvények a textúra-kép használatát szabályozzák:

void glTexParameterf(GLenum target, GLenum pname, GLfloat param);
void glTexParameteri(GLenum target, GLenum pname, GLint param);

A target paraméter értéke mindkét esetben GL_TEXTURE_2D kell, legyen. A pname paraméter nevezi meg azt a tulajdonságot, melyet a textúrára vonatkozóan be szeretnénk állítani, a param pedig a beállító értéket tartalmazza. Ha a pname értéke például a GL_TEXTURE_WRAP_S, vagy GL_TEXTURE_WRAP_T, akkor a param segítségével a különböző irányokban előírhatjuk, hogy a kép a felületre nyújtva (GL_CLAMP), vagy eredeti méretben ismétlődésekkel tölti ki a felületet (GL_REPEAT ez az alapérték). Ha a pname értéke GL_TEXTURE_MIN_FILTER vagy GL_TEXTURE_MAG_FILTER, akkor a textúraelem pixelre történő kicsinyítésének, illetve nagyításának módját írhatjuk elő. A paramGL_NEAREST értéke esetén a több leképzett képpont közül a pixelhez legközelebbi pont színe a meghatározó, a GL_LINEAR (alapérték) esetben pedig a szóba kerülő pontok színe átlagolódik.

A textúra leképezés során azt, hogy a felület színe és a képpontok színe milyen módon kerül összevetésre a glTexEnvtipus() függvénnyel szabályozhatjuk:

void glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
GLfloat param);

Az első két paraméter a megadott konstans kell, legyen. A param paraméter GL_DECAL beállításkor a kép színe kerül a felületre, mintha matricaként rátennénk, egyébként (GL_MODULATE, GL_BLEND) a textúra keveredik a háttérszínnel. Az ilyen módon meghatározott textúra megjelenik minden olyan felületen, amely a glEnable(GL_TEXTURE_2D) beállítással jött létre.

Az alábbi programrészletben egy legfeljebb MERET*MERET nagyságú bitkép adataival töltjük fel a textúrát. A bitképek egyszerű kezelése érdekében – CLI-t használunk.

private: System::Void Form1_Load(System::Object^  sender,
                        System::EventArgs^  e) {
    hwnd=(HWND)this->Handle.ToInt32();
    m_hDC = GetDC(hwnd);
    if(m_hDC) {
        MySetPixelFormat(m_hDC);
    }
try {
        // létrehozunk egy TBitmap objektumot és abba töltjük a képet
        Bitmap ^ Bm = gcnew Bitmap("C:\\M\\Mogi.png");
        BYTE kep[MERET][MERET][3];
        // Áttöltjük a bitkép színeit a képafdat tömbbe
        for (int i=0;i<Bm->Width;i++)
            for (int j=0;j<Bm->Height;j++) {
                kep[i][j][0]=Bm->GetPixel(i,j).R;
                kep[i][j][1]=Bm->GetPixel(i,j).G;
                kep[i][j][2]=Bm->GetPixel(i,j).B;
            }
        // A kétdimenziós mintázat definíciója
        glTexImage2D(GL_TEXTURE_2D,0,
        // szintek a nagyításhoz
            3,                   // színkomponensek száma
            Bm->Height,Bm->Width,// méretek
            0,                   // a keret vastagsága
            GL_RGB,              // színformátum
            GL_UNSIGNED_BYTE,    // színadatok
            &kep                 // az adatok tömbje
        );
        // A  mindkét irányban a 0,1 paraméterekhez kapcsolódunk
        glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
        glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
        // A pixel textúra elemre való nagyításának,
        // kicsinyítésének módja
        glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
        glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
        // A mintázat matricaként kerül a felületre takarva azt.
        glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
        glEnable(GL_TEXTURE_2D);
        // Használat után letöröljük a bitkép objektumot
        delete Bm;
}
    catch (...) {
}
}

4.36. ábra - Textúra

Textúra