PARTEA a II-a : Implementarea de aplicaţii OpenGL folosind biblioteca GLUT
OpenGL este o interfaţă de programare formată din circa 150 de funcţii care pot fi folosite pentru modelarea si vizualizarea scenelor 3D. Două aspecte importante au determinat creşterea continuă a popularităţii sale :
Funcţiile OpenGL sunt independente de platforma hardware-software OpenGL oferă funcţii de sinteză de nivel înalt, care sunt realizate fie software fie
prin echipamente grafice specializate
In prezent există implementări OpenGL pentru sistemele de operare Microsoft Windows, Unix şi IBM PS/2. Acestea se prezintă sub forma unor biblioteci integrate (sau care pot fi integrate) în mediile de dezvoltare a aplicaţiilor. Deoarece OpenGL nu conţine funcţii de gestiune a ferestrelor de afişare şi interacţiune cu utilizatorul, implementările OpenGL sunt completate cu astfel de funcţii. O asemenea implementare extinsă este şi GLUT (OpenGL Utilities Toolkit), la a cărei utilizare ne referim în această parte a îndrumarului.
Dintre funcţiile de sinteză de nivel înalt oferite de OpenGL menţionăm: Maparea texturilor: aplicarea de imagini pe suprafeţe 3D Eliminarea automată din imagini a părţilor nevizibile ale obiectelor prin algoritmul Z-
buffer ; Efecte de iluminare a scenelor 3D folosind diferite modele de iluminare şi una sau
mai multe surse de lumină; Simularea reflexiei si a transmisiei luminii ţinând cont de proprietăţile materialelor ; Transformarea din spaţiul 3D utilizator în spaţiul 2D ecran prin specificarea
matricelor de transformare, posibilitatea de a modifica poziţia şi dimensiunea obiectelor în spaţiul 3D utilizator.
Exista de asemenea funcţii care permit generarea simplă a unor obiecte solide, curbe şi suprafeţe de formă liberă precum şi funcţii de lucru cu imagini.
II.1. Convenţii de numire a funcţiilor, constantelor şi tipurilor de date OpenGL
Numele funcţiilor OpenGL conţin prefixul gl (de exemplu glClearColor) iar constantele prefixul GL_ (de exemplu GL_COLOR_BUFFER_BIT). Declaraţiile funcţiilor OpenGL sunt de forma:
void glFunction{nr}{b s i f d}{v}(argumente);
nr - reprezintă numărul de argumente ale funcţieib sau s sau i sau f sau d sau v - specifică tipul argumentelor funcţiei, iar v dacă
argumentele funcţiei sunt date sub formă de vector (b – byte, s – short, i – int, f – float, d – double, v – vector)
Exemple :
glVertex2i(1, 3);glVertex2f(1.0, 3.0);
Primul apel furnizează coordonatele vârfurilor ca întregi pe 32 biti iar al 2-lea ca numere reale (în formatul cu virgulă mobilă).
glColor3f(1.0, 0.0, 0.0);GLfloat color_array[] = {1.0, 0.0, 0.0};glColor3fv(color_array);
Funcţia glColor3f() setează culoarea de desenare şi are 3 parametri ce corespund componentelor roşu, verde şi albastru, iar funcţia glColor3fv() setează culoarea de desenare având ca parametru un vector ce conţine componentele culorii.
Tipuri de date OpenGL
Pentru un acelaşi tip de date în C, diferitele implementări OpenGL pot alege tipuri diferite. Pentru portabilitate, indiferent de implementarea OpenGL se recomandă utilizarea tipurilor de date definite în OpenGL. Acestea sunt prezentate in tabelul II.1.
OpenGL defineşte şi tipul GLvoid. Acesta este cel mai adesea folosit în apelurile de funcţii OpenGL care acceptă pointeri la vectori de valori.
Sufix Tipul de date Corespondentul în CTipul definit în OpenGL
b 8-bit integer signed char GLbytes 16-bit integer Short GLshorti 32-bit integer int sau long GLint, GLsizeif 32-bit floating-point Float GLfloat, GLclampfd 64-bit floating-point Double GLdouble, GLclampdub 8-bitunsigned integer unsigned char GLubyte, GLbooleanus 16-bit unsigned integer unsigned short GLushort
ui 32-bit unsigned integerunsigned int sau unsigned long
GLuint,GLenum, GLbitfield
Tabelul II.1. Tipuri de date OpenGL
II.2. Funcţii GLUT de realizare a interfeţei cu utilizatorul
II.2.1. Gestiunea ferestrelor
Iniţializare fereastră
void glutInit(int *argc, char **argv);
Funcţia glutInit() iniţializează variabilele interne ale pachetului de funcţii GLUT şi procesează argumentele din linia de comandă. Ea trebuie sa fie apelată înaintea oricarei alte comenzi GLUT. Parametrii funcţiei au aceeaşi semificaţie ca şi parametri funcţiei main.
Iniţializare mod de afişare
void glutInitDisplayMode(unsigned int mode);
unde mode specifică modul de afişare: folosirea modelului RGBA (culoarea se specifică prin componentele sale roşu,
verde, albastru şi transparenţa sau opacitatea) sau a modelului de culoare bazat pe indecşi de culoare. În general se recomandă folosirea modelului RGBA.
folosirea unei ferestre cu un singur buffer sau cu buffer dublu, pentru realizarea animaţiei.
folosirea bufferului de adâncime pentru algoritmul z-buffer.
De exemplu, dacă se doreşte crearea unei ferestre cu buffer dublu ce foloseşte un model de culoare RGBA şi buffer pentru algoritmul z-buffer, se va apela:
glutInitDisplayMode(GLUT_DOUBLE |GLUT_RGB |GLUT_DEPTH;
Iniţializare poziţie fereastră
void glutInitWindowPosition(int x, int y) ;
Funcţia glutInitWindowPosition specifică colţul stânga sus al ferestrei în coordonate relative la colţul stânga sus al ecranului.
Iniţializare dimensiune fereastră
void glutInitWindowSize(int width, int height) ;
Funcţia glutInitWindowSize specifică dimensiunea în pixeli a ferestrei : lăţimea (width) şi înălţimea (height).
Creare fereastră
int glutCreateWindow(char *string) ;
Funcţia glutCreateWindow creează o fereastra cu un context OpenGL. Ea intoarce un identificator unic pentru fereastra nou creată. Fereastra nu va fi afişată înainte de apelarea funcţiei glutMainLoop. Valoarea întoarsă de funcţie reprezintă identificatorul ferestrei, care este unic.
Creare ferestre copil
int glutCreateSubWindow(int win, int x, int y, int width, int height);
Funcţia creează o fereastră având ca părinte fereastra identificată de win, unde :- win - reprezintă identificatorul ferestrei părinte;- (x, y) - reprezintă colţul stânga sus al ferestrei (x şi y sunt exprimate în pixeli şi
sunt relative la originea ferestrei părinte);- width - reprezintă lăţimea ferestrei (exprimată în pixeli);- height - reprezintă înălţimea ferestrei (exprimată în pixeli);
Fereastra nou creată devine fereastra curentă. Funcţia întoarce identificatorul ferestrei create.
Distrugere fereastră
void glutDestroyWindow(int win) ;
Funcţia distruge fereastra specificată de win. De asemenea, este distrus şi contextul OpenGL asociat ferestrei. Orice subfereastră a ferestrei distruse va fi de asemenea distrusă. Dacă win identifică fereastra curentă, atunci ea va deveni invalidă.
Selectarea ferestrei curente
void glutSetWindow(int win) ;
Funcţia selectează fereastra curentă ca fiind cea identificată de parametrul win.
Aflarea ferestrei curente
int glutGetWindow(void) ;
Funcţia întoarce identificatorul ferestrei curente. Funcţia întoarce 0 dacă nu există nici o fereastră curentă sau fereastra curentă a fost distrusă.
Selectarea cursorului asociat ferestrei curente
void glutSetCursor(int cursor) ;
Funcţia modifică cursorul asociat ferestrei curente transformându-l în cursorul specificat de parametrul cursor, care poate avea una din următoarele valori: GLUT_CURSOR_RIGHT_ARROW, GLUT_CURSOR_LEFT_ARROW, GLUT_CURSOR_WAIT, GLUT_CURSOR_HELP, GLUT_CURSOR_TEXT, GLUT_CURSOR_CROSSHAIR, GLUT_CURSOR_UP_DOWN, GLUT_CURSOR_LEFT_RIGHT, GLUT_CURSOR_TOP_SIDE, GLUT_CURSOR_BOTTOM_SIDE, GLUT_CURSOR_LEFT_SIDE, GLUT_CURSOR_RIGHT_SIDE, GLUT_CURSOR_TOP_LEFT_CORNER, GLUT_CURSOR_TOP_RIGHT_CORNER, GLUT_CURSOR_BOTTOM_RIGHT_CORNER, GLUT_CURSOR_BOTTOM_LEFT_CORNER, GLUT_CURSOR_NONE, GLUT_CURSOR_INHERIT (foloseşte cursorul asociat ferestrei părinte).
II.2. 2. Gestiunea meniurilor
Crearea meniurilorMeniurile create cu ajutorul GLUT-ului sunt meniuri simple, pop-up în cascadă. În timp ce un meniu este folosit el nu poate fi şters, nu i se pot adăuga alte opţiuni şi nu i se pot şterge din opţiuni.
int glutCreateMenu(void (*func)(int value)) ;
Funcţia creează un nou meniu pop-up şi întoarce identificatorul său (întreg unic). Identificatorii meniurilor încep de la valoarea 1. Valorile lor sunt separate de identificatorii ferestrelor.
- parametrul func specifică o funcţie callback a aplicaţiei, care va fi apelată de biblioteca GLUT la selectarea unei opţiuni din meniu. Parametrul transmis funcţiei callback (value) specifică opţiunea de meniu selectată.
Adăugarea unei opţiuni într-un meniuvoid glutAddMenuEntry(char* name, int value) ;
Funcţia adaugă o nouă opţiune meniului curent. Opţiunea este adăugată la sfârşitul listei de articole a meniului.
- name - specifică textul prin care va fi reprezentată opţiunea introdusă- value - reprezintă valoarea transmisă funcţiei callback asociată meniului la
selectarea opţiunii respective
Adăugarea unui sbmeniu într-un meniu
void glutAddSubMenu(char* name, int menu) ;
Funcţia adaugă un submeniu la sfârşitul meniului curent.- name - reprezintă numele submeniului introdus (textul prin care va fi afişat în
meniu)- menu - reprezintă identificatorul submeniului.
Ştergerea unei opţiuni sau a unui submeniu
void glutRemoveMenuItem(int entry) ;
Funcţia şterge opţiunea sau submeniul identificat de parametrul entry. Opţiunile din meniu de sub opţiunea ştearsă sunt renumerotate.
Distrugerea unui meniu
void glutDestroyWindow(int menu) ;
Funcţia distruge meniul specificat prin parametru. Distrugerea unui meniu nu are nici un efect asupra submeniurilor. Dacă meniul distrus este cel curent, atunci meniul curent va deveni invalid.
Setarea meniului curent
void glutSetMenu(int menu) ;
Funcţia setează meniul curent ca fiind cel identificat de parametrul menu.
Aflarea meniului curent
int glutGetMenu(void) ;
Funcţia întoarce identificatorul meniului curent. Funcţia întoarce 0 dacă nu există meniu curent sau meniul curent a fost distrus.
Ataşare / detaşare meniu unui buton al mouse-ului
void glutAttachMenu(int button) ;void glutDetachMenu(int button) ;
Funcţia glutAttachMenu ataşează meniul curent butonului mouse-ului specificat de parametrul button. Funcţia glutDetachMenu detaşează butonul asociat meniului curent. Prin ataşarea unui buton al mouse-ului meniului curent, meniul va fi derulat la apăsarea butonului respectiv în poziţia curentă a cursorului.
II.2.3. Controlul evenimentelor de intrare
In categoria evenimentelor de intrare sunt incluse evenimentele provocate de apăsarea unei taste sau a unuia dintre butoanele mouse-ului, de deplasarea mouse-ului şi de redimensionarea ferestrei de afişare de către utilizator. Pentru fiecare dintre aceste tipuri de evenimente programatorul trebuie să specifice o funcţie callback care va fi apelată la producerea unui eveniment de tipul respectiv. Funcţiile GLUT menţionate în continuare servesc acestui scop.
Redimensionarea ferestrei
void glutReshapeFunc(void (*func)(int width, int height)) ;
Parametrul funcţiei glutReshapeFunc indică funcţia callback care va fi apelată la redimensionarea ferestrei. Parametrii width şi height transmişi funcţiei callback reprezintă noile valori ale lăţimii şi înălţimii ferestrei.
Apăsarea / eliberarea unei taste
void glutKeyboardFunc(void (*func)(unsigned char key, int x, int y)) ;
Parametrul func reprezintă funcţia callback care va fi apelată la apăsarea / eliberarea unei taste care generează un caracter ASCII. Parametrul key al funcţiei callback reprezintă
valoarea ASCII. Parametrii x şi y indică poziţia mouse-ului la apăsarea tastei (poziţie specificată în coordonate fereastră).
Apăsarea / eliberarea unui buton al mouse-ului
void glutMouseFunc(void (*func)(int button, int state, int x, int y)) ;
Parametrul func specifică funcţia callback care va fi apelată la apăsarea / eliberarea unui buton al mouseului.Parametrul button al funcţiei callback poate avea una din următoarele valori: GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON sau GLUT_RIGHT_BUTTON. Parametrul state poate fi GLUT_UP sau GLUT_DOWN după cum butonul mouse-ului a fost apăsat sau eliberat. Parametrii x şi y reprezintă poziţia mouse-ului la apariţia evenimentului. Valorile x şi y sunt relative la colţul stânga sus al ferestrei aplicaţiei.
Deplasarea mouse-ului
void glutMotionFunc(void (*func)(int x, int y)) ;
Se specifică funcţia callback care va fi apelată la deplasarea mouse-ului în timp ce un buton este apăsat. Parametrii x şi y reprezintă poziţia mouse-ului în momentul apăsării tastei. Valorile x şi y sunt relative la colţul stânga sul al ferestrei aplicaţie.
II.2.4. Afisarea textelor
Pentru afişarea unui caracter se apeleaza functia glutStrokeCharacter :
void glutStrokeCharacter(void *font, int character);- font specifică fontul vectorial folosit, care poate avea una din următoarele valori:
GLUT_STROKE_ROMAN GLUT_STROKE_MONO_ROMAN
- character specifică caracterul ce va fi afişat
ExempluFuncţia output prezentata mai jos realizeaza afişarea unui text cu format, incepand dintr-o pozitie specificata a ferestrei curente.
void output(GLfloat x, GLfloat y, char *format,...){ va_list args;
char buffer[200], *p;
va_start(args, format); vsprintf(buffer, format, args); va_end(args); glPushMatrix(); glTranslatef(x, y, 0); for (p = buffer; *p; p++) glutStrokeCharacter(GLUT_STROKE_ROMAN, *p); glPopMatrix();}
II.3. Execuţia aplicaţiei.
Funcţia de afişare callback
void glutDisplayFunc(void (*func)(void)) ;
Parametrul funcţiei glutDisplayFunc specifică funcţia callback a aplicaţiei care va fi apelată de GLUT pentru afişarea conţinutului iniţial al ferestrei aplicaţiei precum şi ori de câte ori trebuie refăcut conţinutul ferestrei ca urmare a cererii explicite a aplicaţiei, prin apelul funcţiei glutPostRedisplay().
Bucla de execuţie a aplicaţiei
void glutMainLoop(void) ;
Aceasta este ultima funcţie care trebuie apelată in funcţia main a aplicaţiei. Ea conţine bucla de execuţie (infinită) a aplicaţiei în care aplicaţia aşteaptă evenimente. Orice eveniment este tratat prin rutina callback specificată anterior în funcţia main, prin apelul funcţiei GLUT specifice tipului de eveniment. Execuţia unui proces în background
void glutIdleFunc(void (*func)(void)) ;
Parametrul transmis funcţiei glutIdleFunc este o funcţie callback care va fi executată in perioadele în care nu există evenimente în curs de tratare sau în aşteptarea tratării - funcţia idle. Dacă argumentul funcţiei glutIdleFunc este NULL atunci funcţia idle existentă este dezactivată.
Terminarea afişării
O aplicaţie GLUT poate rula pe mai multe maşini. De exemplu, să presupunem că programul principal este rulat pe o maşină client şi rezultatul procesării (imaginea afişată) apare pe un terminal sau pe o staţie de lucru (server), care este conectat în reţea. De obicei clientul adună o colecţie de comenzi într-un singur pachet înainte de a-l trimite în reţea. Codul de reţea de la client nu permite detectarea momentului în care programul grafic a terminat desenarea unui cadru sau a unei scene 3D. Astfel, clientul poate aştepta la infint comenzi de desenare ca să completeze un pachet. Pentru a forţa clientul să trimită pachetul chiar dacă nu este plin se foloseşte funcţia glFlush .
void glFlush(void);
În cazul în care nu există nici un client şi toate comenzile sunt executate pe server functia glFlush nu va avea nici un efect. Pentru ca un program să funcţioneze corect atât în reţea cât şi pe o singură maşina, se va include apelul functieie glFlush la sfârşitul fiecărei scene. Funcţia glFlush nu aşteaptă terminarea desenării, ci doar forţează începerea execuţiei desenării. Dacă folosirea funcţiei glFlush nu este suficientă pentru o aplicaţie, atunci se va folosi şi funcţia glFinish. Aceasta funcţionează ca şi glFlush dar aşteaptă răspuns de notificare de la echipamentul grafic de indicare a terminări desenarii. glFinish se va folosi pentru sincronizarea task-urilor.
void glFinish(void);
Observaţie: folosirea excesivă a lui glFinish poate reduce performanţele aplicaţiei mai ales dacă se lucrează în reţea. Dacă este suficientă folosirea funcţiei glFlush se va folosi aceasta în locul funcţiei glFinish.
II.4. Crearea unei aplicaţii OpenGL folosind GLUT
Pentru a crea executabilul corespunzător unei aplicaţii OpenGL se va deschide fişierul sursă *.C cu Microsoft Visual C++. În momentul în care se va compila programul, mediul Visual C++ va genera automat un proiect în care va fi inclus fişierul sursă anterior deschis. Pentru editarea legăturilor trebuie adăugate bibliotecile opengl32.lib, glu32.lib şi glut32.lib. Setările corespunzătoare pentru această operaţie sunt următoarele:
Se selectează opţiunea Settings din meniul Project. In cutia de dialog afişată se selectează Link In zona de editare Object/library modules se adaugă cele trei biblioteci
de mai sus.
Rularea unui program OpenGL necesită de asemenea următoarele biblioteci cu legare dinamică : opengl32.dll, glu32.dll, glut32.dll.
In fişierele sursă se va include fişierul header glut.h.
Exemplu 1Programul din fişierul exemplu11.c afişează un dreptunghi centrat în fereastră.
/* fişierul exemplul1.c */
#include "glut.h" /* glut.h se află în directorul curent */
void display(void){/* şterge toţi pixelii */ glClear (GL_COLOR_BUFFER_BIT);
/* afişează un dreptunghi cu interiorul alb avand colţurile în punctele(0.25, 0.25, 0.0) şi (0.75, 0.75, 0.0) */ glColor3f (1.0, 1.0, 1.0); glBegin(GL_POLYGON); glVertex3f (0.25, 0.25, 0.0); glVertex3f (0.75, 0.25, 0.0); glVertex3f (0.75, 0.75, 0.0); glVertex3f (0.25, 0.75, 0.0); glEnd();
glFlush ();}
void init (void) {/* selectează culoarea de fond */ glClearColor (0.0, 0.0, 0.0, 0.0);
/* iniţializează transformarea de vizualizare */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);}
/* * declară dimensiunea iniţială a ferestrei, poziţia şi modul de afişare (buffer singular şi RGBA). * Deschide o fereastră cu titlul "hello". * Apelează rutinele de iniţializare. * Înregistrează funcţia callback de refacere a ferestrei. * Se intră în bucla principală şi se procesează evenimentele. */int main(int argc, char** argv){ glutInit(&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutInitWindowSize (250, 250); glutInitWindowPosition (100, 100); glutCreateWindow ("hello"); init (); glutDisplayFunc(display); glutMainLoop(); return 0; }
Exemplu 2
Programul din fişierul exemplu2.c tratează evenimentele de la mouse. Se afişează un dreptunghi centrat în fereastra de afişare. La apăsarea butonului stânga al mouse-ul dreptunghiul va fi rotit până la apăsarea butonului drept al mouse-ului.
/* fişierul exemplul2.c */
#include "glut.h" /* glut.h se află în directorul curent */#include <stdlib.h>
static GLfloat spin = 0.0;
void init(void) { glClearColor (0.0, 0.0, 0.0, 0.0);}
void display(void){ glClear(GL_COLOR_BUFFER_BIT); glPushMatrix(); glRotatef(spin, 0.0, 0.0, 1.0); glColor3f(1.0, 1.0, 1.0); glRectf(-25.0, -25.0, 25.0, 25.0); glPopMatrix(); glutSwapBuffers();}
void spinDisplay(void){ spin = spin + 2.0; if (spin > 360.0) spin = spin - 360.0; glutPostRedisplay();}
void reshape(int w, int h){ glViewport (0, 0, (GLsizei) w, (GLsizei) h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-50.0, 50.0, -50.0, 50.0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity();}
void mouse(int button, int state, int x, int y) { switch (button) { case GLUT_LEFT_BUTTON: if (state == GLUT_DOWN) glutIdleFunc(spinDisplay); break; case GLUT_RIGHT_BUTTON: if (state == GLUT_DOWN)
glutIdleFunc(NULL); break; default: break; }}
int main(int argc, char** argv){ glutInit(&argc, argv); glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB); glutInitWindowSize (250, 250); glutInitWindowPosition (100, 100); glutCreateWindow (argv[0]); init (); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMouseFunc(mouse); glutMainLoop(); return 0;}
II.5. Afişarea obiectelor 3D predefinite
GLUT conţine funcţii pentru afişarea următoarelor obiecte 3D:
con icosaedru teapotcub octaedru tetraedrudodecaedru sfera tor
Aceste obiecte pot fi afişate prin familii de curbe sau ca obiecte solide.
Exemplu: funcţii de desenare cub, sferă si tor prin două familii de curbe şi ca solide.
Desenare cub de latură size prin două familii de curbe
void glutWireCube(GLdouble size);
Desenare cub solid de latură size
void glutSolidCube(GLdouble size);
Desenare sferă prin două familii de curbe
void glutWireSphere(GLdouble radius, GLint slices, GLint stacks);
Desenare sferă solidă
void glutSolidSphere(GLdouble radius, GLint slices, GLint stacks);
Desenare tor prin două familii de curbe
void glutWireTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings);
Desenare tor solid
void glutSolidTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings);
Alte funcţii sunt:
void glutWireIcosahedron(void); void glutSolidIcosahedron(void); void glutWireOctahedron(void); void glutSolidOctahedron(void); void glutWireTetrahedron(void); void glutSolidTetrahedron(void); void glutWireDodecahedron(GLdouble radius); void glutSolidDodecahedron(GLdouble radius); void glutWireCone( GLdouble radius, GLdouble height, GLint slices,GLint stacks); void glutSolidCone(GLdouble radius, GLdouble height, GLint slices,GLint stacks);
void glutWireTeapot(GLdouble size); void glutSolidTeapot(GLdouble size);
Toate aceste obiecte sunt desenate centrate în originea sistemului de coordonate real.
În momentul în care se fac modificări asupra unui obiect complex poate apare efectul de “pâlpâire” a imaginii. Pentru evitarea acestui efect se asociază ferestrei aplicaţiei un buffer dublu. Astfel, într-un buffer se păstrează imaginea nemodificată (imaginea ce este afişată pe ecran), iar în cel de-al doilea se construieşte imaginea modificată. În momentul în care s-a terminat construirea imaginii modificate se interschimbă buffer-ele (lucrul cu două buffere este asemănator lucrului cu mai multe pagini video în DOS). Pentru interschimbarea bufferelor se foloseşte funcţia: glutSwapBuffers :
void glutSwapBuffers(void) ;
II.6. Specificarea culorilor
II.6.1. Culoarea de desenare
Într-un program OpenGL mai întai trebuie setată culoarea de desenare şi apoi se face desenarea efectivă a obiectelor. Cât timp culoarea de desenare nu se modifică, toate obiectele vor fi desenate cu acea culoare. Exemplu:
set_current_color(red); draw_object(A); draw_object(B); set_current_color(green); set_current_color(blue); draw_object(C);
La execuţia secvenţei din exemplu, obiectele A şi B vor fi desenate cu roşu iar obiectul C cu albastru. Comanda set_current_color(green) nu are nici un efect.
Pentru a seta o culoare de desenare se poate apela funcţia glColor3f :
void glColor3f(GLfloat red, GLfloat green, GLfloat blue) ;
Parametrii funcţiei specifică componentele roşu, verde şi albastru ale culorii ce va fi setată. Ei au valori în intervalul [0.0 , 1.1].Exemple:
glColor3f(0.0, 0.0, 0.0); /* negru */glColor3f(1.0, 0.0, 0.0); /* roşu */glColor3f(0.0, 1.0, 0.0); /* verde */glColor3f(1.0, 1.0, 0.0); /* galben */glColor3f(0.0, 0.0, 1.0); /* albastru */glColor3f(1.0, 0.0, 1.0); /* magenta */ glColor3f(0.0, 1.0, 1.0); /* cyan */glColor3f(1.0, 1.0, 1.0); /* alb */
Alte modalităţi de specificare a culorilor sunt prezentate în capitolul II.10.
II.6.2. Ştergerea ferestrei
Stergerea fondului ferestrei este necesar să fie efectuată înaintea începerii creerii unei noi imagini.
ExempluSe afişează toţi pixelii ferestrei în culoarea negru
glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_COLOR_BUFFER_BIT);
Prima comandă setează culoarea de ştergere negru şi comanda următoare şterge întreaga fereastră folosind culoarea curentă de ştergere. Parametrul funcţiei glClear indică bufferul care va fi şters. De obicei se setează o singură dată culoarea de ştergere la începutul aplicaţiei şi apoi se apelează funcţia de ştergere de câte ori este necesar.
void glClearColor( GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
Valorile red, green, blue sunt în intervalul [0, 1]. Culoarea implicită de ştergere este (0, 0, 0, 0) - negru. Parametrul alpha specifică opacitatea; valoarea sa implicită este 0.0.
void glClear(GLbitfield mask);
Funcţia setează bufferul indicat prin parametrul mask la valoarea specificată. Valorile posibile ale parametrului mask sunt prezentate în tabelul II.2:
Mask Buffer-ul ce va fi şters GL_COLOR_BUFFER_BIT Buffer-ul curent pentru setarea culorii de desenare
GL_DEPTH_BUFFER_BIT Buffer-ul de adăncime
Tabelul II.2.
II.7. Definirea primitivelor geometrice
Orice primitivă geometrică este definită printr-o secvenţă de puncte 3D numite vârfuri. Fiecare vârf este reprezentat intern prin cele 3 coordonate (x, y, z). Pentru vârfurile specificate de programator în 2D, sistemul asignează coordonatei z valoarea 0.
În OpenGL, linia înseamnă segment de dreaptă. Poligoanele sunt suprafeţe mărginite de un contur poligonal închis format din
segmente de dreaptă. Segmentele de dreaptă sunt specificate prin vârfurile capetelor segmentelor. OpenGL impune câteva restricţii asupra poligoanelor (figura II.1): laturile poligoanelor nu se pot intersecta (poligoane simple) şi poligoanele trebuie să fie convexe. Nu pot fi descrise poligoane cu treceri interioare (ele nu sunt convexe).
Figura II.1. Poligoane valide si nevalide
Deoarece în OpenGL vârfurile sunt întotdeauna puncte 3D, punctele ce formează frontiera unui poligon nu este necesar sa fie coplanare. Dacă vârfurile unui poligon nu sunt coplanare, atunci după rotaţii, modificarea poziţiei observatorului şi proiecţia în ecran, punctele pot să nu mai formeze un poligon convex simplu.Problema din figura II.2 poate fi evitată prin folosirea triunghiurilor, deoarece orice trei puncte sunt coplanare.
Figura II.2
II.7.1. Definirea vârfurilor
În OpenGL toate primitivele geometrice sunt definite prin mulţimi ordonate de vârfuri. Pentru a specifica un vârf se foloseşte funcţia:
void glVertex{234}{sifd}[v](TYPE coords);
Funcţia poate avea: 2 parametri – vârful va fi specificat în 2D prin coordonatele sale (x, y) 3 parametri – vârful va fi specificat în 3D prin coordonatele sale (x, y, z) 4 parametri – vârful va fi specificat în 3D prin coordonatele sale omogene (x, y,
z, w)Şi în acest caz TYPE reprezintă tipul coordonatelor vârfului (s - short, i – int , f - float, d - double). Apelul funcţiei glVertex trebuie să fie făcut între perechea de comenzi glBegin şi glEnd.
Exemplu Modalităţi de definire a vârfurilor:
glVertex2s(2, 3); glVertex3d(0.0, 0.0, 3.1415926535898); glVertex4f(2.3, 1.0, -2.2, 2.0);
GLdouble dvect[3] = {5.0, 9.0, 1992.0};glVertex3dv(dvect);
II.7.2. Puncte, linii şi poligoane
Pentru a crea o mulţime de puncte, o linie sau un poligon pornind de la vârfuri, fiecare set de vârfuri trebuie să fie apelat între comenzile glBegin şi glEnd. Argumentul funcţiei glBegin determină tipul primitivei ce va fi afişată :
void glBegin(GLenum mode);
Parametrul mode poate avea una din valorile (figura II.3): GL_POINTS GL_LINES GL_POLYGON GL_TRIANGLES GL_QUADS GL_LINE_STRIP GL_LINE_LOOP GL_TRIANGLE_STRIP GL_TRIANGLE_FAN GL_QUAD_STRIP
Figura II.3. Tipurile de primitive geometrice definite în OpenGL.
GL_LINES specifică afişarea mai multor segmente de dreaptă, câte unul pentru fiecare pereche de vârfuri. Vârfurile sunt conectate în ordinea v0 – v1, v2 – v3, v4 – v5 etc. Dacă este dat un număr impar de vârfuri, ultimul vârf este ignorat.GL_LINE_STRIP specifică o polilinie de la vârful v0 la varful vn, conectând vârfurile în ordinea dată;GL_LINE_LOOP specifică un poligon, vârfurile fiind unite în ordinea dată; ultimul vârf este conectat cu primul.GL_POLYGON specifică un poligon de la vârful v0 la vârful vn-1 (n trebuie să fie cel puţin 3). Poligonul trebuie să fie un poligon convex simplu. Pentru poligoanele concave rezultatul afişării este nedefinit. De asemenea, vârfurile trebuie să fie situate în acelaşi plan;GL_QUADS specifică o serie de patrulatere separate. Primul patrulater este desenat folosind vârfurile v0, v1, v2 şi v3, următorul v4, v5, v6 şi v7 etc. Dacă n nu este multiplu de 4 vârfurile suplimentare sunt ignorate;GL_TRIANGLES specifică o serie de triunghiuri separate;GL_QUAD_STRIP specifică o serie de patrulatere conectate. Primul patrulater este desenat folosind vârfurile v0, v1, v2 şi v3, Următorul refoloseşte ultimele două vârfuri v2, v3 şi foloseşte următoarele două în ordinea v5 şi v6. Fiecare patrulater foloseşte ultimele două vârfuri de la patrulaterul anterior. În fiecare caz n trebuie să fie cel puţin 4 şi multiplu de 2;GL_TRIANGLE_STRIP specifică o serie de triunghiuri conectate. Primul triunghi foloseşte vârfurile v0, v1 şi v2. Următorul v2, v1 şi v3, următorul v2, v3 şi v4. Se observă că ordinea asigură ca toate triunghiurile să fie orientate la fel;GL_TRIANGLE_FAN specifică o serie de triunghiuri conectate într-un vârf comun, vârful v0. Primul triunghi este desenat folosind vârfurile v0, v1 şi v2, următorul foloseşte v0, v2 şi v3, următorul v0, v3 şi v4 etc.
Funcţia glEnd marchează sfârşitul listei de vârfuri.
void glEnd(void);
Exemplu:Primitiva redată în figura II.4 este specificată prin următoarea secvenţă :
glBegin(GL_POLYGON); glVertex2f(0.0, 0.0); glVertex2f(0.0, 3.0); glVertex2f(3.0, 3.0);
glVertex2f(4.0, 1.5); glVertex2f(3.0, 0.0);glEnd();
Dacă se foloseşte GL_POINTS în loc de GL_POLYGON, primitiva va fi formată din 5 puncte.
Figura II.4
Restricţii de folosire pentru glBegin şi glEnd
Coordonatele vârfurilor se specifică folosind funcţia glVertex*. Pentru un vârf pot fi furnizate informaţii suplimentare, cum ar fi: culoarea, vectorul normală, etc. In tabelul II.3 sunt specificate funcţiile care pot fi apelate între glBegin şi glEnd :
Funcţia ComentariiglVertex* Stabileşte coordonatele vârfului
glColor*Stabileşte culoarea curentă a vârfului
glIndex*Setează indexul curent de culoare
glNormal* Stabileste vectorul normalăglEvalCoord* Generează coordonateleglCallList, glCallLists
Execută lista (listele) de afişare
glTexCoord*Setează coordonatele de texturare
glEdgeFlag*contrololează desenarea muchiilor
glMaterial*Stabileşte proprietăţile de material
Tabelul II.3. Funcţii care pot fi apelate între glBegin şi glEndDacă între apelurile funcţiilor glBegin şi glEnd apar apelurile altor funcţii GLUT
se va genera cod de eroare. Pot apare însă instrucţiuni ale limbajului de programare în care este implementată aplicaţia.
ExempluDesenarea unui cerc prin linii.
#define PI 3.1415926535897; GLint circle_points = 100;
glBegin(GL_LINE_LOOP); for (i = 0; i < circle_points; i++) { angle = 2*PI*i/circle_points; glVertex2f(cos(angle), sin(angle)); } glEnd();
Acest exemplu nu reprezintă cel mai eficient mod de a desena un cerc, mai ales dacă acest lucru se va face în mod repetat. Comenzile grafice folosite sunt rapide, dar pentru fiecare vârf se calculează un unghi şi se apelează functiile sin şi cos. În plus apare şi overhead-ul introdus de buclă. Dacă cercul va fi afişat de mai multe ori, pentru a îmbunătăţi timpul de execuţie, se vor calcula o singură dată coordonatele vârfurilor şi se vor salva într-un vector sau se va crea o listă de afişare (display list).
Între comenzile glBegin şi glEnd vârfurile sunt generate doar la întâlnirea apelului glVertex*. În momentul apelului funcţiei glVertex*, sistemul asociază vârfului respectiv culoarea curentă, coordonatele de texturare, vectorul normală etc.
ExempluPrimul vârf este desenat cu roşu, iar al doilea şi al treilea cu albastru:
glBegin(GL_POINTS); glColor3f(0.0, 1.0, 0.0); /* verde */ glColor3f(1.0, 0.0, 0.0); /* roşu */ glVertex(...); glColor3f(1.0, 1.0, 0.0); /* galben */ glColor3f(0.0, 0.0, 1.0); /* albastru */ glVertex(...); glVertex(...);
glEnd();
II.7.3. Dreptunghiuri în planul XOY
OpenGL furnizează o funcţie particulară, care permite desenarea unui dreptunghi cu interiorul plin, în planul XOY:
void glRect{sifd}(TYPE x1, TYPE y1, TYPE x2, TYPE y2); void glRect{sifd}v(TYPE* v1, TYPE* v2);
Funcţia afişează dreptunghiul definit de colţurile (x1, y1) şi (x2, y2). Dreptunghiul se află în planul z=0 şi are laturile paralele cu axele x, respectiv y. Dacă este folosită varianta cu vectori, colţurile dreptunghiului sunt specificate prin doi vectori, fiecare conţinând o pereche (x, y). TYPE reprezintă tipul coordonatelor colţurilor dreptunghiului (s - GLshort, i – GLint , f - GLfloat, d - GLdouble).
II.8. Specificarea atributelor de afişare ale primitivelor geometrice
În mod implicit un punct este desenat ca un singur pixel ecran, un segment de dreapta este desenat ca o linie continuă de lăţime egală cu un pixel, iar poligoanele sunt afişate având conturul cu linie continuă şi interior gol.
II.8.1. Dimensiunea punctelor
Pentru a specifica dimensiunea unui punct afişat se foloseşte funcţia glPointSize.
void glPointSize(GLfloat size);
Funcţia setează lăţimea în pixeli a punctelor ce vor fi afişate. Parametrul size reprezintă dimensiunea punctului exprimată în pixeli ecran. Ea trebuie să fie mai mare ca 0.0, iar valoarea sa implicită este 1.0.
II.8.2. Atributele liniilor
În OpenGL segmentele de dreaptă pot fi desenate având diferite lăţimi, ca linii continue sau ca linii punctate sau întrerupte.
II.8.2.1.Lăţimea liniilor
void glLineWidth(GLfloat width);
Funcţia setează lăţimea în pixeli a liniilor ce vor fi afişate; width trebuie să fie mai mare ca 0.0, iar valoarea implicită este 1.0.
II.8.2.2. Tipul liniilor
Pentru afişarea liniilor punctate sau întrerupte se va apela funcţia glLineStipple, care defineşte şablonul de generare a liniilor, apoi se va activa folosirea liniilor punctate sau întrerupte folosind funcţia glEnable:
void glLineStipple(GLint factor, GLushort pattern);
Funcţia setează şablonul curent de generare a liniilor. - factor - este un multiplicator pentru fiecare bit din şablonul de generare a
liniei cu şablon. De exemplu, dacă factor = 3 atunci fiecare bit din şablon va fi folosit de trei ori înainte ca următorul bit din şablon să fie folosit. Are valori în intervalul [1, 255]
- pattern - reprezintă o succesiune de 16 biţi care va fi repetată pentru a genera o linie cu şablon. Dacă bit = 1 se face afişarea, dacă bit = 0 nu se afişează; ordinea de parcurgerea a biţilor în şablon este de la bitul cel mai puţin semnificativ către bitul cel mai semnificativ. Şablonul poate fi multiplicat prin folosirea parametrului factor, care multiplică fiecare succesiune de biţi cu valori egale (1 sau 0) din şablon cu valoarea factor. Astfel, dacă în sablon apar 3 biţi consecutivi cu valoarea 1 şi factor = 2, se vor genera 6 biţi cu valoarea 1.
In figura II.5 sunt exemplificate diferite tipuri de linii obţinute prin alegerea valorilor pattern şi factor.
Figura II.5
Generarea de linii cu şablon se va activa apelând funcţia glEnable cu parametrul GL_LINE_STIPPLE şi va fi dezactivată prin apelul funcţiei glDisable cu acelaşi argument.
void glEnable(Glenum cap);
void glDisable(GLenum cap);
cap este o constantă simbolică ce specifică o ‘capabilitate’ OpenGL. Ea poate avea una din următoarele valori:
GL_CULL_FACE - activează / dezactivează eliminarea părţilor nevizibile ale obiectelor folosind metoda Back Face Culling.
GL_DEPTH_TEST –activează / dezactivează calcularea valorilor buffer-ului de adâncime.
GL_LIGHTi - activează / dezactivează luarea în calcul a sursei de lumină şi la calculul de iluminare.
GL_LIGHTING – activează / dezactivează calcularea culorii corespunzătoare fiecărui vârf.
GL_LINE_STIPPLE – activează / dezactivează generarea liniilor folosind şablon.
GL_NORMALIZE - acitivează /dezactivează normalizarea vectorilor normală. GL_POLYGON_STIPPLE - activează / dezactivează folosirea şablonului
curent la afişarea poligoanelor.
Exemplu:
glLineStipple(1, 0x3F07);glEnable(GL_LINE_STIPPLE);
În acest exemplu, pentru şablonul 0x3F07 (0011111100000111), o linie va fi desenată cu 3 pixeli vizibili, apoi 5 invizibili, 6 vizibili şi 2 invizibili (ordinea de parcurgere este de la bitul cel mai puţin semnificativ către cel mai semnificativ). Dacă factor = 2, şablonul va fi multiplicat: 6 pixeli vizibili, 10 invizibili, 12 vizibili şi 4 invizibili. Figura următoare exemplifică linii desenate folosind diverse şabloane şi diferiţi factori de multiplicare. Dacă nu se activează generarea liniilor cu şablon, afişarea va decurge ca şi cum pattern ar fi 0xFFFF şi factor 1. Generarea liniilor cu şablon poate fi combinată cu setarea diferitelor lăţimi de linie pentru a produce linii punctate de diferite lăţimi.
O modalitate de a privi generarea liniilor cu şablon este aceea că şablonul este deplasat cu o poziţie de fiecare dată când este afişat un pixel (sau factor pixeli sunt afişati, dacă factor este diferit de 1). Dacă sunt desenate o serie de segmente de dreaptă
conectate între apelurile funcţiilor glBegin şi glEnd, atunci şablonul va continua să fie deplasat când se trece de la un segment la altul. În acest mod un şablon este folosit pentru mai multe segmente de dreaptă conectate între ele. La execuţia funcţiei glEnd şablonul va fi resetat şi dacă în continuare, înainte de a dezactiva generarea de segmente de dreaptă cu şablon, mai sunt afişate segmente de dreaptă, atunci la generare se va folosi şablonul de la început. Dacă segmentele de dreaptă sunt afişate folosind GL_LINES, atunci şablonul va fi resetat pentru fiecare segment de dreaptă.
Programul « exemplul3 » prezentat în continuare exemplifică folosirea diferitelorşabloane şi a diferitelor lăţimi pentru generarea liniilor.
/* exemplul3.c*/
#include "glut.h"
#define drawOneLine(x1,y1,x2,y2) glBegin(GL_LINES); \ glVertex2f ((x1),(y1)); glVertex2f ((x2),(y2)); glEnd();
void myinit (void) { /* fondul are culoarea neagră */ glClearColor (0.0, 0.0, 0.0, 0.0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(50.0, 150.0, -50.0, 350.0, -1.0, 1.0); }
void display(void){ int i;
glClear (GL_COLOR_BUFFER_BIT);/* afişează toate liniile cu alb */ glColor3f (1.0, 1.0, 1.0);
/* prima linie: 3 linii, fiecare cu un şablon */ glEnable (GL_LINE_STIPPLE); glLineStipple (1, 0x0101); /* linie punctată */ drawOneLine (50.0, 125.0, 150.0, 125.0);
glLineStipple (1, 0x00FF); /* linie întreruptă */ drawOneLine (150.0, 125.0, 250.0, 125.0); glLineStipple (1, 0x1C47); /* întreruptă/punctată/întreruptă */ drawOneLine (250.0, 125.0, 350.0, 125.0);
/* linia a doua */ glLineWidth (5.0); glLineStipple (1, 0x0101); drawOneLine (50.0, 100.0, 150.0, 100.0); glLineStipple (1, 0x00FF); drawOneLine (150.0, 100.0, 250.0, 100.0); glLineStipple (1, 0x1C47); drawOneLine (250.0, 100.0, 350.0, 100.0); glLineWidth (1.0);
/* linia a treia: 6 linii cu şablon întrerupt/punctat/întrerupt, *//* ca parte a unei singure linii */ glLineStipple (1, 0x1C47); glBegin (GL_LINE_STRIP); for (i = 0; i < 7; i++) glVertex2f (50.0 + ((GLfloat) i * 50.0), 75.0); glEnd ();
/* linia a patra: 6 linii independente cu şablon *//* întrerupt/punctat/întrerupt */ for (i = 0; i < 6; i++) { drawOneLine (50.0 + ((GLfloat) i * 50.0), 50.0, 50.0 + ((GLfloat)(i+1) * 50.0), 50.0); }
/* linia a cincea: 1 linie cu şablon întrerupt/punctat/întrerupt *//* şi factor de multiplicare egal cu 5 */ glLineStipple (5, 0x1C47); drawOneLine (50.0, 25.0, 350.0, 25.0); glFlush ();}
int main(int argc, char** argv){ glutInit(&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutInitWindowSize (250, 250); glutInitWindowPosition (100, 100); glutCreateWindow (argv[0]); myinit (); glutDisplayFunc(display); glutMainLoop(); return 0;}
II.8.3. Atributele poligoanelor
De obicei poligoanele sunt afişate prin contur continuu, dar pot fi afişate şi cu conturul punctat sau numai prin vârfurile ce-l compun. Un poligon poate avea interiorul generat uniform sau folosind un anumit şablon. Poligoanele sunt afişate astfel încât pentru poligoanele adiacente care împart o latură sau un vârf, pixelii ce formează latura respectivă sau vârful respectiv sunt afişaţi o singură dată – ei sunt incluşi numai într-un singur poligon.
II.8.3.1. Modul de afişareUn poligon este considerat ca având două feţe – cea din faţă şi cea din spate. El
poate fi generat ţinând cont de ambele feţe, numai de feţele din faţă (cele aflate spre observator) sau numai de feţele din spate. În mod implicit, feţele din faţă şi din spate sunt afişate în acelaşi mod. Pentru a modifica acest lucru sau pentru a afişa poligoanele prin contur sau numai prin vârfurile ce-l compun se va folosi funcţia glPolygonMode :
void glPolygonMode(GLenum face, GLenum mode);
- face specifică feţele la care se referă parametrul mode; poate fi GL_FRONT_AND_BACK (feţele faţă şi spate), GL_FRONT (feţele faţă), sau GL_BACK (feţele spate);
- mode reprezintă modul de afişare a feţelor selectate; poate fi: GL_POINT (poligoanele vor fi afişate prin vârfuri), GL_LINE (poligoanele vor fi afişate prin contur), sau GL_FILL (poligoanele vor fi afişate cu interior plin)
În mod implicit, ambele tipuri de feţe sunt afişate cu interiorul plin.
ExempluSe cere afişarea faţelor din faţă cu interiorul plin, iar a celor din spate prin contur:
glPolygonMode(GL_FRONT, GL_FILL);glPolygonMode(GL_BACK, GL_LINE);
II.8.3.2. Orientarea feţelor
In mod implicit, feţele ale căror vârfuri sunt parcurse în sens invers acelor de ceas sunt considerate feţe din faţă (orientate spre observator). Această convenţie poate fi modificată de programator, apelând funcţia glFrontFace.
void glFrontFace(GLenum mode);
Parametrul mode specifică orientarea feţelor din faţă ale poligoanelor. Valoarea GL_CCW (valoarea implicită) corespunde unei orientări în sensul invers acelor de ceas a conturului unei feţe proiectate în coordonate de afişare. Valoarea GL_CW, indică o orientare în sensul acelor de ceas a contururilor feţelor din faţă.
În cazul unei suprafeţe închise construită din poligoane cu o anumită orientare, toate feţele spate nu vor fi vizibile niciodată – ele vor fi ascunse de feţele din faţă ale poligoanelor. În această situaţie se poate mări viteza de afişare prin eliminarea poligoanelor imediat ce s-a determinat dacă ele reprezintă feţe spate. În mod similar dacă suntem în interiorul unui obiect atunci vor fi vizibile numai poligoanele care reprezintă feţele spate. Pentru a specifica eliminarea feţelor faţă, respectiv feţelor spate se va folosi funcţia glCullFace iar activarea eliminării feţelor respective se va face folosind funcţia glEnable având ca parametru GL_ CULL_FACE. Pentru dezactivare se va apela glDisable cu acelaşi argument.
void glCullFace(GLenum mode);
Funcţia indică ce poligoane vor fi eliminate înainte de a fi convertite în coordonate ecran. Parametrul mode poate avea una din valorile :
- GL_FRONT : se elimină feţele faţă- GL_BACK :se elimină feţele spate- GL_FRONT_AND_BACK :se elimină atât feţele faţă cât şi feţele spate.
II.8.3.3. Tipul interiorului
În mod implicit poligoanele cu interiorul plin sunt generate folosind un şablon plin. De asemenea pentru generarea interiorului unui poligon se poate folosi un şablon de 32x32 biţi, care se va specifica folosind funcţia glPolygonStipple.
void glPolygonStipple(const GLubyte *mask);
Funcţia defineşte şablonul curent pentru generarea interiorului poligoanelor. Argumentul mask este un pointer catre un bitmap de 32×32 care este interpretat ca o masca de 0 şi 1. Pixelul din poligon corespunzator valorii 1 din mască va fi afişat.
Folosirea şablonului curent la generarea poligoanelor este activată, respectiv dezactivată, apelând funcţiile glEnable respectiv glDisable cu argumentul GL_POLYGON_STIPPLE.
In programul din fişierul exemplu4.c este exemplificată folosirea şabloanelor pentru generarea interioarelor poligoanelor.
/* exemplu4.c */
#include "glut.h"
void display(void){ GLubyte fly[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x06, 0xC0, 0x03, 0x60, 0x04, 0x60, 0x06, 0x20, 0x04, 0x30, 0x0C, 0x20, 0x04, 0x18, 0x18, 0x20, 0x04, 0x0C, 0x30, 0x20, 0x04, 0x06, 0x60, 0x20, 0x44, 0x03, 0xC0, 0x22, 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x44, 0x01, 0x80, 0x22, 0x66, 0x01, 0x80, 0x66, 0x33, 0x01, 0x80, 0xCC, 0x19, 0x81, 0x81, 0x98, 0x0C, 0xC1, 0x83, 0x30, 0x07, 0xe1, 0x87, 0xe0, 0x03, 0x3f, 0xfc, 0xc0, 0x03, 0x31, 0x8c, 0xc0, 0x03, 0x33, 0xcc, 0xc0, 0x06, 0x64, 0x26, 0x60, 0x0c, 0xcc, 0x33, 0x30,
0x18, 0xcc, 0x33, 0x18, 0x10, 0xc4, 0x23, 0x08, 0x10, 0x63, 0xC6, 0x08, 0x10, 0x30, 0x0c, 0x08, 0x10, 0x18, 0x18, 0x08, 0x10, 0x00, 0x00, 0x08};
GLubyte halftone[] = { 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55}; glClear (GL_COLOR_BUFFER_BIT); glColor3f (1.0, 1.0, 1.0); glRectf (25.0, 25.0, 125.0, 125.0); glEnable (GL_POLYGON_STIPPLE); glPolygonStipple (fly); glRectf (125.0, 25.0, 225.0, 125.0); glPolygonStipple (halftone); glRectf (225.0, 25.0, 325.0, 125.0); glDisable (GL_POLYGON_STIPPLE); glFlush ();}
void myinit (void) { glClearColor (0.0, 0.0, 0.0, 0.0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, 400.0, -100.0, 300.0, -1.0, 1.0); }
int main(int argc, char** argv){ glutInitDisplayMode (GLUT_SINGLE | GLUT_RGBA); glutInitWindowSize (350, 150); glutInitWindowPosition (0, 0); glutCreateWindow (argv[0]); myinit (); glutDisplayFunc(display); glutMainLoop(); return 0;}
In mod implicit, pentru fiecare octet primul bit este considerat bitul cel mai semnificativ, Ordinea biţilor se poate modifica prin apelul funcţiei glPixelStore*().
II.8.3.4. Muchiile de contur
OpenGL permite desenarea de poligoane convexe, dar în practică apar deseori şi poligoane concave. Pentru a desena poligoane concave, de regulă acestea se descompun în poligoane convexe – de obicei triunghiuri, aşa cum se arată în figura II.7, şi apoi sunt desenate aceste triunghiuri.
Dacă se descompune un poligon în triunghiuri şi se face afişarea triunghiurilor nu se poate folosi functia glPolygonMode pentru a se desena numai laturile poligonului, deoarece laturile triunghiurilor se află în interiorul poligonului. Pentru a rezolva această problema se poate specifica dacă un vârf aparţine sau nu unei muchii de contur prin păstrarea pentru fiecare vârf a unui bit. La afişarea poligonului în modul GL_LINE nu vor fi desenate laturile care nu aparţin muchiilor de contur. În figura II.6 liniile punctate reprezintă laturi false.
Figura II.6. Divizarea unui poligon concav
În mod implicit orice vârf aparţine unei muchii de contur. Acestă convenţie poate fi controlată prin setarea flag-ului de apartenenţă la o muchie de contur cu ajutorul funcţiei glEdgeFlag*. Funcţia se apelează între comenzile glBegin şi glEnd şi acţionează asupra tuturor vârfurilor specificate după apelul sau până la următorul apel al funcţiei glEdgeFlag. Ea se aplică doar vârfurilor care aparţin poligoanelor (GL_POLYGON), triunghiurilor (GL_TRIANGLES) şi patrulaterelor (GL_QUADS). Nu are efect asupra primitivelor de tipurile GL_QUAD_STRIP, GL_TRIANGLE_STRIP şi GL_TRIANGLE_FAN.
void glEdgeFlag(GLboolean flag); void glEdgeFlagv(const GLboolean *flag);
Dacă flag este GL_TRUE, atunci flag-ul de contur va fi setat la TRUE (valoarea implicită ), şi orice vârf definit în continuare va fi considerat ca aparţinând muchiilor de contur până la un nou apel al funcţiei glEdgeFlag* cu parametrul GL_FALSE.
Exemplu: Marcarea muchiilor de contur ale unui poligon
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);glBegin(GL_POLYGON); glEdgeFlag(GL_TRUE); glVertex3fv(V0); glEdgeFlag(GL_FALSE); glVertex3fv(V1); glEdgeFlag(GL_TRUE); glVertex3fv(V2);glEnd();
Efectul acestor instrucţiuni este următorul (figura II.7):
Figura II.7.
II.8.3.5. Vectori normală
Un vector normală este un vector care are orientarea perpendiculară pe o suprafaţă. Pentru o suprafaţă plană este suficientă o singură direcţie perpendiculară, ea fiind aceeaşi pentru orice punct de pe suprafaţă, dar pentru o suprafaţă oarecare direcţia vectorului normală poate diferi pentru fiecare punct. În OpenGL se poate specifica un vector normală pentru fiecare vârf. Vectorii normală pot fi asociaţi numai vârfurilor. Ei sunt necesari în calculul iluminării suprafeţelor aproximate prin reţele poligonale.
Funcţia glNormal* se apelează pentru a specifica normala curentă. Normala curentă va fi asociată tuturor vârfurilor definite în continuare prin apeluri ale funcţiei glVertex*. Dacă fiecare vârf are asociată o normală diferită se va proceda ca în exemplul următor:
glBegin (GL_POLYGON); glNormal3fv(n0); glVertex3fv(v0); glNormal3fv(n1); glVertex3fv(v1); glNormal3fv(n2); glVertex3fv(v2); glNormal3fv(n3); glVertex3fv(v3);glEnd();
void glNormal3{bsidf}(TYPE nx, TYPE ny, TYPE nz);
void glNormal3{bsidf}v(const TYPE *v);
Funcţia glNormal3 setează componentele vectorului « normală curentă » la valoarile (nx,ny,nz), specificate prin parametri. Versiunea glNormal3*v a acestei funcţii primeşte ca parametru un vector de 3 elemente pentru a specifica normala curentă. Versiunile b, s şi i scalează liniar valorile parametrilor în domeniul [-1.0,1.0].
Pe o suprafaţă, într-un punct dat, sunt doi vectori perpendiculari având orientări în direcţii opuse. Prin convenţie, normala este acel vector care are direcţia spre exteriorul suprafeţei. Dacă se doreşte folosirea vectorului normală orientat spre interiorul suprafeţei unui obiect se va modifica fiecare vector normală de la (x, y, z) la (-x, -y, -z).
Vectorii normală pot fi specificaţi având orice lungime, dar ei vor trebui normalizaţi înainte de efectuarea calculelor de iluminare. În general, este bine ca vectorii normală să fie furnizaţi normalizaţi. Dacă se specifică vectori normală nenormalizaţi sistemul OpenGL îi poate normaliza în mod automat. Pentru a cere această operaţie se va apela funcţia glEnable având ca parametru GL_NORMALIZE. Implicit este dezactivată normalizarea automată. În unele implementari ale OpenGL-ului normalizarea automată necesită calcule suplimentare care pot reduce performanţele aplicaţiei.
II.9. Transformări ale obiectelor 3D în OpenGL
II.9.1. Etapele transformării din spaţiul 3D pe suprafaţa de afişare
OpenGL utilizează un sistem de coordonate 3D dreapta.Secvenţa de transformări aplicate asupra punctelor prin care este definit un obiect 3D
pentru a fi afişat pe ecran este următoarea (figura II.8): transformarea de modelare şi vizualizare ( ModelView) transformarea de proiecţie, însoţită de decupare la marginile volumului vizual canonic împărţirea perspectivă transformarea în poarta de vizualizare din fereastra curentă
Figura II.8. Secvenţa de transformari aplicate vârfurilor în OpenGL
Procesul de transformări necesar producerii imaginii dorite pentru a fi redată pe o suprafaţă de afişare este asemănator cu cel al efectuării unei fotografii. Aceşti paşi ar fi următorii:
Aranjarea scenei pentru a fi fotografiată în contextul dorit - transformarea de modelare;
Aranjarea aparatului de fotografiat şi încadrarea scenei -transformarea de vizualizare;
Alegerea lentilelor aparatului sau modificarea zoom-ului - transformarea de proiecţie;
Determinarea dimensiunii imaginii finale - transformarea în poarta de vizualizare.
II.9.2. Transformarea de modelare şi vizualizare
O aceeaşi imagine a unui obiect se poate obţine în două feluri : poziţionând obiectul în faţa unei camere de luat vederi fixe poziţionând camera de luat vederi în faţa obiectului fix
Prima operaţie corespunde unei transformări de modelare a obiectului. Cea de a doua operaţie corespunde unei transformări de vizualizare. Deoarece ambele pot fi folosite pentru a obţine o aceeaşi imagine, sunt tratate împreună, ca o singură transformare
Transformarea de modelare are drept scop poziţionarea obiectelor în scena 3D. Această transformare este necesară deoarece, în mod uzual, fiecare obiect este definit ca obiect unitate într-un sistem de coordonate local. De exemplu, un cub poate fi definit ca având latura de o unitate, centrat în originea unui sistem de coordonate carteziene 3D. Reprezentarea la mărimea dorită, poziţionarea şi orientarea sa în scena 3D, care este definită într-un sistem de coordonate global, poate să presupună o transformare compusă din scalare şi rotaţie faţa de originea sistemului de coordonate local, urmată de o translaţie. Prin această transformare se crează o instanţă a cubului, de aceea transformarea de modelare se mai numeşte şi transformare de instanţiere.
Transformarea de modelare este o transformare compusă din transformări geometrice simple care poate fi definită ca produs matricial, folosind funcţiile de translaţie, rotaţie şi scalare oferite de OpenGL.
Transformarea de vizualizare este determinată de poziţia observatorului (camera de luat vederi), direcţia în care priveşte acesta si direcţia sus a planului de vizualizare. In mod implicit, observatorul este situat in originea sistemului de coordonate în care este descrisă scena 3D, direcţia în care priveşte este direcţia negativa al axei OZ, iar direcţia sus a planului de vizualizare este direcţia pozitiva a axei OY. Cu aceste valori implicite, transformarea de vizualizare este transformarea identică.
Funcţia gluLookAt permite modificarea parametrilor impliciţi ai transformării de vizualizare :
void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez, GLdouble centerx,GLdouble centery,GLdouble centerz, GLdouble upx,GLdouble upy,GLdouble upz);
- eyex, eyey, eyez reprezintă poziţia observatorului - centerx, centery, centerz reprezintă direcţa în care se priveşte- upx, upy, upz reprezintă direcţia vectorului « sus » al planului de vizualizare
Poziţia observatorului reprezintă punctul de referinţă al vederii, R, iar direcţia în care se priveşte este direcţia normalei la planul de vizualizare, în R. Vectorul « sus » determină direcţia pozitivă a axei verticale a sistemului de coordonate 2D ataşat planului de vizualizare. Sistemul de coordonate 2D ataşat planului de vizualizare împreună cu normala la plan formează sistemul de coordonate 3D al planului de vizualizare, care în terminologia OpenGL este numit sistemul de coordonate observator.
Funcţia gluLookAt construieşte matricea transformării din sistemul de coordonate obiect în sistemul de coordonate observator şi o înmulţeşte la dreapta cu matricea curentă.
In OpenGL transformarea de modelare şi de vizualizare sunt exprimate printr-o singură matrice de transformare, care se obţine înmulţind matricile celor două transformări. Ordinea de înmulţire a celor două matrici trebuie să corespundă ordinei în care ar trebui aplicate cele două transformări : mai întâi transformarea de modelare apoi transformarea de vizualizare.
In OpenGL punctele 3D se reprezintă prin vectori coloană. Astfel, un punct (x,y,z) se reprezintă în coordonate omogene prin vectorul [xw yw zw w]T. Daca A, B şi C sunt 3 matrici de transformare care exprimă transformările de aplicat punctului în ordinea A, B, C, atunci secvenţa de transformări se exprimă matricial astfel :
[xw’ yw’ zw’ w’] T= C •B •A• [xw yw zw w]T
Transformarea de modelare şi vizualizare este o transformare compusă, reprezentată printr-o matrice VM, ce se obţine înmulţind matricea transformării de vizualizare cu matricea transformării de modelare. Fie V şi M aceste matrici. Atunci,
VM = V•MDacă coordonatele unui vârf în sistemul de coordonate obiect sunt reprezentate prin vectorul [xo yo zo wo]T, atunci coordonatele vârfului în sistemul de coordonate observator (“eye coordinates”) se obţin astfel:
[xe ye ze we]T = VM • [xo yo zo wo]T
Matricea VM este aplicată automat şi vectorilor normali.
II.9.3. Transformarea de proiecţie
Matricea de proiecţie este calculată în OpenGL în funcţie de tipul de proiecţie specificat de programator şi parametrii care definesc volumul de vizualizare. Matricea de proiecţie este matricea care transformă volumul vizual definit de programator într-un volum vizual canonic. Această transformare este aplicată vârfurilor care definesc primitivele geometrice în coordonate observator, rezultând coordonate normalizate. Primitivele reprezentate prin coordonate normalizate sunt apoi decupate la marginile volumului vizual canonic, de aceea coordonatele normalizate se mai numesc şi coordonate de decupare. Volumul vizual canonic este un cub cu latura de 2 unităti, centrat în originea sistemului coordonatelor de decupare. După aplicarea transformării de proiecţie, orice punct 3D (din volumul vizual canonic) se proiectează în fereastra 2D printr-o proiecţie ortografică (x’ =x, y’=y) condiţie necesară pentru aplicarea algoritmului z-buffer la producerea imaginii.
Dacă coordonatele unui vârf în sistemul de coordonate observator sunt reprezentate prin vectorul [xe ye ze we]T, iar P este matricea de proiecţie, atunci coordonatele de decupare ale vârfului (“clip coordinates”) se obţin astfel:
[xc yc zc wc]T = P • [xo yo zo wo]T
Prezentăm în continuare funcţiile OpenGL prin care pot fi definite proiecţiile şi volumul de vizualizare.
void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
Funcţia defineşte o proiecţie perspectivă cu centrul de proiecţie în poziţia observatorului (punctul de referinţă al vederii). Volumul de vizualizare (figura II.9) este delimitat prin « planul din faţă » şi « planul din spate », plane paralele cu planul de vizualizare, definite prin distanţele lor faţă de poziţia observatorului (planul de vizualizare). Planul din faţă va fi folosit ca plan de proiecţie. Lui i se ataşează un sistem de coordonate 2D având axa verticală (sus) orientată ca şi axa verticală a planului de vizualizare. Deschiderea camerei de luat vederi este determinată printr-o fereastră rectangulară, cu laturile paralele cu axele, definită în planul de proiecţie.
- (left, bottom) şi (right, top) reprezintă colţurile ferestrei din planul din faţă- znear, zfar reprezintă distanţele de la poziţia observatorului la planul din faţă,
respectiv spate. Ambele distanţe trebuie să fie pozitive.
Figura II.9. Volumul vizual perspectivă
Dacă left=right sau bottom=top sau znear=zfar sau znear<=0 sau zfar<=0 se semnalează eroare.Colţurile ferestrei 2D din planul de proiecţie, (left, bottom,-near) şi (right, top,-near) sunt mapate pe colţurile stânga-jos şi dreapta-sus ale ferestrei 2D din sistemul coordonatelor de decupare, adică (-1,-1,-1) şi (1,1,-1). Matricea de proiecţie este în acest caz :
unde
,
,
Funcţia glFrustum înmulţeşte matricea curentă cu matricea de proiecţie rezultată şi memorază rezultatul în matricea curentă. Astfel, dacă M este matricea curentă şi P este matricea proiecţiei, atunci glFrustum înlocuieşte matricea M cu M * P.
O altă funcţie care poate fi folosită pentru proiecţia perspectivă este gluPerspective :
void gluPerspective( GLdouble fovy, GLdouble aspect, GLdouble near, GLdouble far);
Funcţia gluPerspective creează un volum de vizualizare la fel ca şi funcţia glFrustum, dar în acest caz el este specificat în alt mod. În cazul funcţiei gluPerspective acesta se specifică prin unghiul de vizualizare în planul XOZ (deschiderea camerei de luat vederi) şi raportul dintre lăţimea şi înălţimea ferestrei definite în planul de aproape (pentru o fereastră pătrată acest raport este 1.0.)
- fovy reprezintă unghiul de vizualizare în planul XOZ, care trebuie să fie în intervalul [0.0,180.0].
- aspect reprezintă raportul lăţime / înălţime al laturilor ferestrei din planul de aproape; acest raport trebuie să corespundă raportului lăţime/înăţime asociat porţii de afişare. De exemplu, dacă aspect = 2.0 atunci unghiul de vizualizare este de două ori mai larg pe direcţia x decât pe y. Dacă poarta de afişare este de două ori mai lată decât înălţimea atunci imaginea va fi afişată nedistorsionată.
- near şi far reprezintă distanţele între observator şi planele de decupare de-a lungul axei z negative. Întotdeauna trebuie să fie pozitivi.
Proiecţia ortografică este specificată cu ajutorul funcţiei glOrtho :
void glOrtho( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
unde- (left, bottom) şi (right, top) reprezintă colţurile ferestrei din planul din faţă- near, far reprezintă distanţele de la poziţia observatorului la planul din faţă,
respectiv planul din spate
Volumul de vizualizare este în acest caz un paralelipiped dreptunghic delimitat de planul din faţa şi cel din spate, plane paralele cu planul de vizualizare (perpendiculare pe axa z), precum şi de planele sus, jos, dreapta , stânga. Direcţia de proiecţie este dată de axa Z a sistemului de coordonate ataşat planului de vizualizare.
Fereastra definită în planul din faţă, (left, bottom,-near) şi (right, top,-near), este mapată pe fereastra 2D din sistemul coordonatelor de decupare (-1,-1,-1) şi (1,1,-1). Matricea de proiecţie este în acest caz :
unde
, ,
Matricea curentă este înmulţită cu matricea de proiecţie rezultată, rezultatul fiind depus în matricea curentă.
Tot pentru proiecţia ortografică poate fi folosită şi funcţia gluOrtho2D care defineşte matricea de proiecţie ortografică în care near=-1 şi far=1.
void gluOrtho2D( Gldouble left, Gldouble right, Gldouble bottom, Gldouble top);
unde- (left, bottom) şi (right, top) reprezintă colţurile ferestrei din planul din faţă
II.9.4. Impartirea pespectivă
Prin această operaţie se obţin coordonate 3D în spaţiul coordonatelor de decupare, pornind de la coordonatele 3D omogene :
xd = xc/wc , yd = yc/wc , zd= zc/wc
Coordonatele (xd, yd, zd) sunt numite « coordonate dispozitiv normalizate ».Operaţia este denumită « împărţire perspectivă » deoarece numai în cazul unei proiecţii perspectivă wc este diferit de 1.
II.9.5. Transformarea în poarta de vizualizare
Această transformare se aplică coordonatelor dispozitiv normalizate pentru a se obţine coordonate raportate la sistemul de coordonate al fereastrei curente de afişare. Este o transformare fereastă-poartă, în care fereastra este fereastra 2D din sistemul coordonatelor normalizate, având colţurile în (-1, -1, -1)-(1, 1, -1), iar poarta este un dreptunghi din fereastra de afişare care poate fi definit prin apelul funcţiei glViewport.
void glViewport(GLint px, GLint Py, GLsizei width, GLsizei height );
- (px, py) reprezintă coordonatele în fereastră ale colţului stânga jos al porţii de afişare (în pixeli). Valorile implicite sunt (0,0).
- width, height reprezintă lăţimea, respectiv înălţimea porţii de afişare. Valorile implicite sunt date de lăţimea şi înălţimea ferestrei curente de afişare.
Fie (xd, yd, zd) coordonatele dispozitiv normalizate ale unui vârf şi (xw, yw, zw) coordonatele vârfului în fereastra de afişare. Transformarea în poarta de vizualizare este definită astfel :
xw = ox + (width/2)xdyw = oy + (height/2)ydzw = ((f-n)/2)zd + (n+f)/2
unde (ox, oy) reprezintă coordonatele centrului porţii de afişare.n şi f au valorile implicite 0.0 respectiv 1.0, dar pot fi modificate la valori cuprinse în intervalul [0,1] folosind funcţia DepthRange. Dacă n şi f au valorile implicite, atunci prin transformarea în poarta de vizualizare volumul vizual canonic se transformă în cubul cu colţurile de minim şi maxim în punctele (px, py, 0)-(px+width, py+height,1). Calculul coordonatei zw este necesară pentru comparaţiile de adâncime în algoritmul z-buffer.
Observaţie: în programul din fişierul exemplu5.c lăţimea şi înălţimea porţii de afişare sunt specificate folosind lăţimea şi înălţimea ferestrei curente a aplicaţiei.
II.9.6. Funcţii de operare cu matrici de transformare
Transformarea de modelare şi vizualizare şi transformarea de proiecţie sunt reprezentate prin matrici. Programatorul poate încărca una dintre aceste matrici cu o matrice proprie sau cu matricea unitate, sau o poate înmulţi cu alte matrici.
Înaintea specificării unei transformări trebuie selectată matricea curentă, care va fi modificată. Pentru aceasta se va apela funcţia glMatrixMode.
void glMatrixMode( GLenum mode );
Parametrul mode reprezintă matricea asupra căreia se vor efectua modificările ulterioare. El poate avea una dintre următoarele valori:
- GL_MODELVIEW – matricea curentă va fi matriciea de modelare şi vizualizare- GL_PROJECTION – matricea curentă va fi matricea de proiecţie.
- GL_TEXTURE – matricea curentă va fi matricea textură.
In programul din fişierul exemplu5.c, înaintea transformării de vizualizare, matricea curentă este setată la matricea identitate (de 4 linii şi 4 coloane) cu ajutorul functiei glLoadIdentity.
void glLoadIdentity(void);
Apelul funcţiei glLoadIdentity este necesar deoarece majoritatea funcţiilor de transformare înmulţesc matricea curentă cu matricea specificată şi rezultatul este depus în matricea curentă. Dacă matricea curentă nu este setată iniţial la matricea identitate, atunci se vor folosi matricile de transformare anterioare combinate cu cea furnizată în acel moment
Dacă se doreşte ca o anumită matrice să devină matricea curentă, atunci se va folosi funcţia glLoadMatrix*.
void glLoadMatrix{fd}(const TYPE *m);
Valorile conţinute în vectorul m se memorează în matricea curentă, în ordinea coloanelor :
Observaţie : Elementele unei matrici din limbajul C sunt memorate în ordinea liniilor.
Funcţia glMultMatrix* se poate folosi pentru a înmulţi matricea curentă cu matricea dată ca parametru funcţiei glMultMatrix*.
void glMultMatrix{fd}(const TYPE *m);
Funcţia înmulţeşte matricea specificată de parametrul m cu matricea curentă şi rezultatul înmulţirii este stocat în matricea curentă. Dacă C este matricea curentă, atunci rezultatul înmulţirii este C=C•m.
Pentru specificarea unei translaţii se poate folosi funcţia:
void glTranslatef(GLfloat x, GLfloat y, GLfloat z);
unde x, y şi z reprezintă componentele vectorului de translaţieMatricea curentă este înmulţită cu matricea de translaţie şi rezultatul înlocuieşte
matricea curentă. Dacă M este matricea curentă şi T este matricea de translaţie, atunci M este înlocuită cu M•T.
Daca matricea curenta este GL_MODELVIEW sau GL_PROJECTION, toate obiectele afişate după apelul functiei glTranslatef vor fi translatate.
Pentru rotaţii se poate folosi funcţia:
void glRotatef(GLfloat angle,GLfloat x,GLfloat y,GLfloat z);
- angle reprezintă unghiul de rotaţie exprimat în grade.- x, y, z reprezintă coeficienţii directori ai axei în jurul căreia se realizează rotaţia.
Funcţia efectuează o rotaţie în sens trigonometric cu unghiul specificat, în jurul vectorului ce uneşte originea cu punctul de coordonate (x, y, z). Matricea curentă este înmulţită cu matricea de rotaţie şi rezultatul înlocuieşte matricea curentă. Dacă M este matricea curentă şi R este matricea de rotaţie, atunci M este înlocuită cu M•R. Dacă matricea curentă este GL_MODELVIEW sau GL_PROJECTION, toate obiectele afişate după apelul funcţiei glRotatef vor fi rotite.
Pentru scalare se poate folosi funcţia:
void glScalef(GLfloat x, GLfloat y, GLfloat z);
unde x, y, z reprezintă factorii de scalare de-a lungul axelor x, y şi z.Matricea curentă este înmulţită cu matricea de scalare şi rezultatul înlocuieşte matricea curentă. Dacă M este matricea curentă şi S este matricea de translaţie, atunci M este înlocuită cu M*S.Dacă matricea curentă este GL_MODELVIEW sau GL_PROJECTION, atunci toate obiectele afişate după apelul funcţiei glScalef vor fi scalate.Folosirea funcţiei glScale* diminuează performanţele calculului iluminării, deoarece vectorii normală trebuie renormalizaţi după această transformare.
ExempluConsiderăm următoarea secvenţă care specifică o secvenţă de trei transformări:
glMatrixMode(GL_MODELVIEW);glLoadIdentity();glMultMatrixf(N); /* aplică transformarea N */glMultMatrixf(M); /* aplică transformarea M */glMultMatrixf(L); /* aplică transformarea L */glBegin(GL_POINTS); glVertex3f(v); /* afişează vârful v transformat*/
glEnd();
La execuţia secvenţei de mai sus, matricea de modelare şi vizualizare (MODELVIEW) va conţine succesiv : I, N, N•M, şi în final N•M•L, unde I reprezintă matricea identitate. Transformarea aplicată vârfului v va fi NML•v, unde NML = N•M•L.
II.9.7. Gestiunea stivelor de matrici
Un sistem OpenGL păstrează câte o stivă de matrici pentru fiecare mod matrice selectat cu ajutorul funcţiei MatrixMode. Astfel, există : Stiva matricilor Modelview (cel puţin 32 matrici 4 x 4); Stiva matricilor de proiecţie (cel puţin 2 matrici 4 x 4) ; Stiva matricilor textură(cel puţin 2 matrici 4 x 4).
Matricea curentă este întotdeauna matricea din vârful stivei corespunzătoare modului matrice curent.O stivă de matrici este folositoare pentru construirea modelelor ierarhice în care sunt construite obiecte complexe pornind de la obiecte simple. De exemplu, să presupunem că se desenează o maşină şi există o singură rutină care desenează o roată. Această rutină desenează roata într-o anumită poziţie şi orientare. Când se desenează maşina, rutina de afişare a roţii se va apela de 4 ori, aplicându-se diferite transformări pentru a poziţiona corect roţile. De exemplu, pentru desenarea primei roţi aceasta trebuie să fie translatată. La desenarea celei de a doua roţi trebuie aplicată o altă translaţie (dar faţă de poziţia iniţială – nu trebuie ţinut cont de prima translaţie) şi în acelaşi mod pentru desenarea celorlalte roţi.
Deoarece transformările sunt păstrate ca matrici, o stivă de matrici furnizează un mecanism util pentru efectuarea unei transformări ca apoi să se realizeze o altă transformare fără a se mai ţine cont de transformarea anterioară. Toate operaţiile cu matrici (glLoadMatrix, glMultMatrix, glLoadIdentity şi funcţiile care creează matrici de transformare specifice) lucrează cu matricea curentă sau cu matricea din vârful stivei.
Pentru lucrul cu stivele de matrici OpenGL pune la dispoziţie funcţiile glPushMatrix şi glPopMatrix.
void glPushMatrix(void);
Funcţia adaugă un nou element la stiva curentă şi memorează matricea curentă atât în elementul din vârful stivei cât şi în intrarea următoare. Stiva curentă este determinată de
ultimul apel al funcţiei glMatrixMode. Dacă prin adăugare se depaşeşte capacitatea stivei se generează eroare.
void glPopMatrix(void);
Funcţia elimină intrarea din vârful stivei şi înlocuieşte matricea curentă cu matricea care era memorată în a 2-a intrare a stivei. Dacă stiva avea o singură intrare, apelul funcţiei glPopMatrix generează eroare.
ExempluProgramul din fişierul exemplu5.c afişează un cub care este mai întâi scalat (transformarea de modelare). Transformarea de vizualizare constă dintr-o translaţie a poziţiei observatorului pe axa z, în poziţia (0,0,5). Observatorul priveşte spre origine iar direcţia axei sus este direcţia axei OY a sistemului de coordonate obiect.
/* exemplu5.c */#include "glut.h"
void init(void) { glClearColor (0.0, 0.0, 0.0, 0.0);}void display(void){ glClear (GL_COLOR_BUFFER_BIT); glColor3f (1.0, 1.0, 1.0); glLoadIdentity (); gluLookAt(0.0,0.0,5.0,0.0,0.0,0.0,0.0,1.0,0.0); glScalef (1.0, 2.0, 1.0); glutWireCube (1.0); glFlush ();}void reshape (int w, int h){ glViewport (0, 0, (GLsizei) w, (GLsizei) h); glMatrixMode (GL_PROJECTION); glLoadIdentity (); glFrustum (-1.0, 1.0, -1.0, 1.0, 1.5, 20.0); glMatrixMode (GL_MODELVIEW);}int main(int argc, char** argv)
{ glutInit(&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutInitWindowSize (500, 500); glutInitWindowPosition (100, 100); glutCreateWindow (argv[0]); init (); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMainLoop(); return 0;}
Transformarea de vizualizare şi modelare este creată în funcţia display unde este apelată şi funcţia de afişare a cubului, glutWireCube. Astfel funcţia display poate fi folosită în mod repetat pentru a afişa conţinutul ferestrei (de exemplu în cazul în care fereastra este mutată pe ecran).
Transformarea de proiecţie şi transformarea în poarta de vizualizare sunt specificate în funcţia Reshape, care este apelată de sistem ori de câte ori este redimensionată fereastra aplicaţiei.
Efectul obţinut prin mutarea observatorului în spatele cubului (folosind transformarea de vizualizare) se poate obţine şi prin deplasarea cubului, folosind o transformare de modelare.
II.10. Iluminarea
II.10.1. Specificarea culorilor pentru lumină şi materiale
In capitolul II.6 ne-am referit la modul uzual în care sunt specificate culorile de afişare a obiectelor. Funcţiile glColor3 specifică un triplet (R, G, B). Funcţiile glColor4 adaugă tripletului (R,G,B) o valoare de opacitate, numită valoarea alfa (A).
OpenGL permite utilizarea a două moduri de reprezentare a culorilor de afişare: modul RGBA: pentru fiecare pixel se memorează valorile R, G, B şi A. modul indexat: pentru fiecare pixel se memorează un număr, reprezentând un index
într-o tabela de culori. Valorile R, G, B şi A variază în domeniul [0, 1].
În modul RGBA, pentru selectarea culorii curente de desenare se folosesc funcţiile glColor*.
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, TYPE a); void glColor3{b s i f d ub us ui}v (const TYPE* v); void glColor4{b s i f d ub us ui}v (const TYPE* v);
Valoarea implicită a opacităţii este 1.0. Pentru versiunile funcţiei glColor* care acceptă valori de tip real, domeniul
acestora trebuie să fie în intervalul [0, 1]. Valorile din afara intervalului [0,1] sunt trunchiate la valori în intervalul [0,1] când sunt folosite ca parametri direcţi, dar nu sunt trunchiate dacă sunt folosite pentru a modifica parametrii de material şi iluminare.
Valorile parametrilor de culoare specificate prin numere întregi sunt convertite în valori reale, conform tabelulului II.4
Tip Valoarea minimă
Valoarea minimă se mapeaza la
Valoarea maximă
Valoarea maximă se mapează la
b 1-byte integer -128 -1.0 127 1.0s 2-byte integer -32,768 -1.0 32,767 1.0i 4-byte integer -2,147,483,648 -1.0 2,147,483,647 1.0ub unsigned 1-byte integer 0 0.0 255 1.0
us unsigned 2-byte integer 0 0.0 65,535 1.0
ui unsigned 4-byte integer 0 0.0 4,294,967,295 1.0
Tabelul II.4. Conversia valorilor de culoare la numere reale
Componentele culorilor specificate prin funcţiile glColor* au semnificaţii diferite după cum sunt folosite pentru lumisa emisă de o sursă sau lumina reflectată de o suprafaţă. Pentru o sursă de lumină, valorile componentelor R, G şi B corespund unui procent din intensitatea maximă pentru fiecare culoare. Dacă valorile R, G şi B sunt 1.0 atunci lumina este alb strălucitor. Dacă valorile sunt 0.5, culoarea este albă, iar la jumătate din intensitate apare gri. Dacă R=G=1 şi B=0 lumina apare galbenă.
Pentru proprietăţile de material, valorile componentelor R, G şi B corespund proporţiilor reflectate din acea culoare. Dacă R=1, G=0.5 şi B=0 pentru un material, atunci acel material va reflecta toată lumina incidentă roşie, jumătate din lumina incidentă verde şi nimic din lumina incidentă albastră. Cu alte cuvinte dacă o sursă de lumină are componentele (LR, LG, LB) şi un material are componentele corespunzătoare (MR, MG, MB) atunci ignorându-se toate celelalte efecte ale reflectivităţii, lumina ce este percepută de ochiul observatorului este dată de formula (LR*MR, LG*MG, LB*MB).
Analog dacă sunt două surse de lumină, cu componentele (R1, G1, B1) şi (R2, G2, B2), atunci lumina rezultată va fi dată de (R1+R2, G1+G2, B1+B2). Dacă oricare dintre componentele rezultate sunt mai mari ca 1, atunci componenta respectivă va fi trunchiată la 1.
II.10.2. Specificarea modelului de iluminare
Modelele de iluminare care pot fi folosite în OpenGL sunt modelul Lambert şi modelul Gouraud. Modelul de iluminare dorit se specifică cu ajutorul funcţiei glShadeModel.
void glShadeModel (GLenum mode);
- mode specifică modelul de iluminare ce va fi selectat; el poate avea valorile: GL_SMOOTH: este valoarea implicită, prin care se selectează modelul Gouraud GL_FLAT : selectează modelul Lambert.
În modelul Gouraud culoarea fiecărui vârf este tratată în mod individual. Pentru un segment de dreaptă culoarea se obţine prin interpolarea culorilor vârfului. Pentru un poligon, culorile punctelor interioare se obţin prin interpolare pe baza culorilor vârfurilor.
Exemplu : în fişierul exemplul6.c se afişează un triunghi folosind modelul de iluminare Gouraud.
/* fişierul exemplul6.c */#include "glut.h"
void init(void) { glClearColor (0.0, 0.0, 0.0, 0.0); glShadeModel (GL_SMOOTH);}
void triangle(void){ glBegin (GL_TRIANGLES); glColor3f (1.0, 0.0, 0.0); glVertex2f (5.0, 5.0);
glColor3f (0.0, 1.0, 0.0); glVertex2f (25.0, 5.0); glColor3f (0.0, 0.0, 1.0); glVertex2f (5.0, 25.0); glEnd();}
void display(void){ glClear (GL_COLOR_BUFFER_BIT); triangle (); glFlush ();}
void reshape (int w, int h){ glViewport (0, 0, (GLsizei) w, (GLsizei) h); glMatrixMode (GL_PROJECTION); glLoadIdentity (); if (w <= h) gluOrtho2D (0.0, 30.0, 0.0, 30.0*(GLfloat) h/(GLfloat) w); else gluOrtho2D (0.0, 30.0*(GLfloat) w/(GLfloat) h, 0.0, 30.0); glMatrixMode(GL_MODELVIEW);}
int main(int argc, char** argv){ glutInit(&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutInitWindowSize (500, 500); glutInitWindowPosition (100, 100); glutCreateWindow (argv[0]); init (); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMainLoop(); return 0;}
În modelul Lambert culoarea unei primitive este dată de culoarea unui singur vârf. Culoarea unui segment de dreaptă este dată de culoarea celui de-al doile vârf. Culoarea unui poligon este dată de culoarea unui vârf, conform tabelului II.5. Vârfurile poligoanelor sunt numerotate începând cu 1. Pentru a evita confuzii în ceea ce priveşte culoarea de desenare în modelul Lambert se va specifica o singură culoare pentru o primitivă.
Tipul poligonului Vârful folosit pentru selectarea culorii poligonului
Poligon singular 1triangle strip i+2triangle fan i+2independent triangle 3iquad strip 2i+2independent quad 4i
Tabelul II.5. Selectarea culorii pentru un poligon în modelul Lambert
II.10.3. Iluminarea unei scene 3D
Paşii de adăugare a iluminării într-o scenă sunt următorii :1) Definirea vectorilor normală pentru fiecare vârf al tuturor obiectelor. Aceste
normale determină orientarea relativă a obiectelor faţă de sursele de lumină ;2) Crearea, selectarea şi poziţionarea uneia sau a mai multor surse de lumină ;3) Crearea şi selectarea unui model de iluminare care defineşte nivelul luminii
globale, ambiante şi poziţia observatorului ;4) Definirea proprietăţilor de material pentru obiectele din scenă.
ExempluProgramul din fişierul exemplul7.c realizează afişarea unei sfere cu iluminare folosind o sursă de lumină. Normalele pentru sferă sunt definite de funcţia glutSolidSphere.
/* fişierul exemplul7.c */#include "glut.h"
void init(void) {
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat mat_shininess[] = { 50.0 }; GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 }; glClearColor (0.0, 0.0, 0.0, 0.0); glShadeModel (GL_SMOOTH);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_DEPTH_TEST);}
void display(void){ glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glutSolidSphere (1.0, 20, 16); glFlush ();}
void reshape (int w, int h){ glViewport (0, 0, (GLsizei) w, (GLsizei) h); glMatrixMode (GL_PROJECTION); glLoadIdentity(); if (w <= h) glOrtho (-1.5, 1.5, -1.5*(GLfloat)h/(GLfloat)w, 1.5*(GLfloat)h/(GLfloat)w, -10.0, 10.0); else glOrtho (-1.5*(GLfloat)w/(GLfloat)h, 1.5*(GLfloat)w/(GLfloat)h, -1.5, 1.5, -10.0, 10.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity();}
int main(int argc, char** argv){
glutInit(&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize (500, 500); glutInitWindowPosition (100, 100); glutCreateWindow (argv[0]); init (); glutDisplayFunc(display); glutReshapeFunc(reshape); glutMainLoop(); return 0;}
II.10.3.1. Crearea, poziţionarea şi activarea uneia sau a mai multor surse de lumină
În programul din fişierul exemplul7.c se foloseşte o singură sursă de lumină albă. Poziţia sa este specificată prin apelul funcţiei glLightfv. Acest exemplu foloseşte culoarea implicită pentru sursa de lumina 0 (GL_LIGHT0), care este albă ; dacă se doreşte o sursă de lumină având o altă culoare se va folosi funcţia glLight*. Într-o scenă pot fi incluse cel mult 8 surse de lumină diferite. Culoarea implicită a acestor surse de lumină este negru. Ele pot fi poziţionate la o distanţă finită sau infinită faţă de scenă. Sursele de lumină pot produce un fascicul de lumină mai îngust sau mai larg. După ce au fost definite caracteriticile surselor de lumină, acestea trebuie să fie activate folosind funcţia glEnable. De asemenea, trebuie apelată funcţia glEnable având ca parametru GL_LIGHTING pentru a activa efectuarea calculelor de iluminare (în mod implicit acestea sunt dezactivate).
Crearea surselor de lumină
Sursele de lumină au o serie de proprietăţi cum sunt culoarea, poziţia şi direcţia. Funcţia folosită pentru a specifica toate proprietăţile luminii este glLight*.
void glLight{if}(GLenum light, GLenum pname, TYPE param);void glLight{if}v(GLenum light, GLenum pname, TYPE *param);
Funcţia crează o sursă de lumină.- light specifică sursa de lumină creată; poate avea una din următoarele valori:
GL_LIGHT0, GL_LIGHT1, ... , sau GL_LIGHT7;
- pname specifică caracteristicile luminii, conform tabelului II.6;- param indică valorile caracteristicilor setate prin pname.
Valorile implicite ale parametrului param pentru valorile parametrului pname
pname Valoarea implicita ObservatieGL_AMBIENT (0.0, 0.0, 0.0, 1.0) Intensitatea ambiantă a luminii (RGBA)GL_DIFFUSE (1.0, 1.0, 1.0, 1.0) Intensitatea luminii difuze (RGBA)GL_SPECULAR (1.0, 1.0, 1.0, 1.0) Intensitatea luminii speculare (RGBA)GL_POSITION (0.0, 0.0, 1.0, 0.0) Poziţia (x, y, z, w) a luminiiGL_SPOT_DIRECTION (0.0, 0.0, -1.0) Direcţia (x, y, z) spotului de luminăGL_SPOT_EXPONENT 0.0 Exponentul spotului deluminăGL_SPOT_CUTOFF 180.0 Unghiul spotului de luminăGL_CONSTANT_ATTENUATION 1.0 Factor de atenuare constantGL_LINEAR_ATTENUATION 0.0 Factor de atenuare liniarGL_QUADRATIC_ATTENUATION 0.0 Factor de atenuare cuadric
Tabelul II.6.
Valorile implicite din tabelul de mai sus pentru GL_DIFFUSE şi GL_SPECULAR se aplică doar pentru sursa de lumină GL_LIGHT0. Pentru celelate surse de lumină, valoarea implicită este (0.0, 0.0, 0.0, 1.0) atât pentru GL_DIFFUSE cât şi pentru GL_SPECULAR.
Exemplu : definirea culorilor, poziţiei şi a factorilor de atenuare pentru o sursă de lumină
GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);glLightfv(GL_LIGHT0, GL_POSITION, light_position);glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 2.0);glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 1.0);glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.5);
O sursă de lumină poziţionată (care nu este plasată la infinit) poate acţiona ca un spot – lumina va fi emisă sub forma unui con. Pentru a specifica unghiul dintre axele
conului şi o rază de pe suprafaţa conului se foloseşte parametrul GL_SPOT_CUTOFF. Unghiul conului este de fapt dublul acestei valori după cum se arată în figura II.10:
Figura II.10.
Valoarea implicită a parametrului GL_SPOT_CUTOFF este 180.0 (caracteristica de lumină spot este dezactivată), adică lumina este emisă în toate direcţiile. Valoarea parametrului GL_SPOT_CUTOFF trebuie să fie situată în intervalul [0.0,90.0] (altfel are valoarea 180.0).
Exemplu : setarea parametrului GL_SPOT_CUTOFF la 45o
glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0);
De asemenea, trebuie specificată o direcţie a spotului care determină axele conului de lumină :
GLfloat spot_direction[] = { -1.0, -1.0, 0.0 };glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);
Direcţia este specificată în coordonate obiect omogene. Valoarea implicită a direcţiei este (0.0, 0.0, -1.0).
Surse de lumină multiple
Într-o scenă pot fi definite cel mult 8 surse de lumină. Constatele folosite pentru referirea celor 8 surse de lumină sunt : GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, GL_LIGHT3, …, GL_LIGHT7. Dacă se specifică o altă sursă de lumină trebuie să i se seteze parametrii corespunzători ca şi în cazul sursei de lumină GL_LIGHT0.
Exemplu : definirea unui spot de lumina albă :
GLfloat light1_ambient[] = { 0.2, 0.2, 0.2, 1.0 };GLfloat light1_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };GLfloat light1_specular[] = { 1.0, 1.0, 1.0, 1.0 };GLfloat light1_position[] = { -2.0, 2.0, 1.0, 1.0 };GLfloat spot_direction[] = { -1.0, -1.0, 0.0 };
glLightfv(GL_LIGHT1, GL_AMBIENT, light1_ambient);glLightfv(GL_LIGHT1, GL_DIFFUSE, light1_diffuse);glLightfv(GL_LIGHT1, GL_SPECULAR, light1_specular);glLightfv(GL_LIGHT1, GL_POSITION, light1_position);glLightf(GL_LIGHT1, GL_CONSTANT_ATTENUATION, 1.5);glLightf(GL_LIGHT1, GL_LINEAR_ATTENUATION, 0.5);glLightf(GL_LIGHT1, GL_QUADRATIC_ATTENUATION, 0.2);
glLightf(GL_LIGHT1, GL_SPOT_CUTOFF, 45.0);glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);glLightf(GL_LIGHT1, GL_SPOT_EXPONENT, 2.0);
glEnable(GL_LIGHT1);
Observaţie: Pentru a activa fiecare sursă de lumină se foloseşte funcţia glEnable cu argumentul GL_LIGHTING. Pentru a dezactiva iluminarea se apelează functia glDisable cu acelaşi parametru.
II.10.3.2. Specificarea parametrilor modelului de iluminare
Funcţia glLightModel* defineşte parametrii modelului de iluminare:
void glLightModel{fi}(GLenum pname, TYPE param);
- pname reprezintă parametrul modelului de iluminare. El poate avea una din
următoarele două valori: GL_LIGHT_MODEL_LOCAL_VIEWER, caz în care param specifică
modul de calcul al unghiului de reflexie. GL_LIGHT_MODEL_TWO_SIDE - specifică feţele pentru care se face
calculul de iluminare. Nu are nici un efect în calculele de lumină pentru
puncte, linii şi bitmap-uri. Dacă param este 0 , calculele de iluminare se fac numai pentru feţele faţă şi vor fi folosite numai proprietăţile de material ale feţelor faţă. Altfel vor fi considerate atât feţele faţă cât şi feţele spate. În acest caz vârfurile feţelor spate vor fi luminate folosind proprietăţile de material ale feţelor spate (normalele vor fi inversate înainte de a calcula iluminarea).
- param reprezintă valoarea ce va fi folosită în corelaţie cu pname.
void glLightModel{fi}v( GLenum pname, const TYPE *params );
- pname identifică modelul de iluminare. El poate avea una din următoarele două valori: GL_LIGHT_MODEL_AMBIENT, caz în care params va conţine
componentele RGBA ale luminii ambiante. GL_LIGHT_MODEL_LOCAL_VIEWER are aceeaşi semnificaţie ca
şi în cazul funcţiei glLightModel{fi}; în acest caz param devine params; GL_LIGHT_MODEL_TWO_SIDE are aceeaşi semnificaţie ca şi în
cazul funcţiei glLightModel{fi}; în acest caz param devine params;- param reprezintă valoarea ce va fi folosită în corelaţie cu pname.
În exemplul din fişierul exemplul7.c singurul element care este definit explicit pentru modelul de iluminare este lumina ambiantă globală. De asemenea, modelul de iluminare defineşte şi poziţia observatorului (la infinit sau la distanţă finită de scenă) precum şi modul de efectuare a calculelor de iluminare a feţelor faţă respectiv spate ale scenei. În programul din fişierul exemplul7.c se folosesc valorile implicite pentru acestea – observatorul este plasat la infinit şi calculele de iluminare sunt efectuate pentru feţele faţă. Folosirea unui observator local scenei creşte complexitatea calculelor deoarece sistemul OpenGL trebuie să calculeze unghiul dintre observator şi fiecare obiect. Folosind un observator plasat la infinit unghiul este ignorat şi rezultatele sunt ceva mai puţin realiste.
În OpenGL modelul de iluminare are trei componente : Intensitatea luminii ambiante globale Poziţionarea observatorului (local scenei sau la infinit) Diferenţierea calculării iluminării pentru feţele obiectelor faţă, respectiv spate.
Lumina ambianta globală
Pentru a specifica intensitatea luminii ambiante globale ca RGBA se va folosi parametrul GL_LIGHT_MODEL_AMBIENT după cum urmează :
GLfloat lmodel_ambient[] = { 0.2, 0.2, 0.2, 1.0 };glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
În exemplul de mai sus valorile folosite pentru lmodel_ambient sunt valorile implicite pentru GL_LIGHT_MODEL_AMBIENT.
Observator local sau plasat la infinit
Poziţia observatorului afectează calculele pentru strălucirea produsă de lumina speculară, mai precis intensitatea strălucirii într-un vârf depinde de normala în acel vârf, direcţia de la vârf la sursa de lumină şi direcţia de la vârf la observator.
Cu un observator plasat la infinit direcţia dintre observator şi orice vârf rămâne constantă. Un observator local va furniza rezultate mai aproape de realitate, dar direcţia va trebui calculată pentru fiecare vârf, astfel că în acest caz performaţele scad. În mod implicit observatorul este plasat la infinit.
Exemplu: definirea unui observator local în punctul de coordonate (0, 0, 0) (în coordonate observator):
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
Pentru a trece din nou la un observator plasat la infinit:
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);
Calculul luminii pentru ambele feţe
În programul din fişierul exemplul7.c numai feţele faţă sunt iluminate.
Exemplu: calcularea iluminării pentru ambele feţe :
glLightModeli(LIGHT_MODEL_TWO_SIDE, GL_TRUE);
Dacă se doreşte să se calculeze apoi iluminarea numai pentru feţele faţă se va apela:
glLightModeli(LIGHT_MODEL_TWO_SIDE, GL_FALSE);
II.10.3.3. Definirea proprietăţilor de material pentru obiectele din scenă
Proprietăţile de material ale unui obiect determină modul în care el reflectă lumina. Pentru proprietăţile unui material se pot specifica culorile ambiantă, difuză, speculară, stralucirea sa şi culoarea oricărei lumini emise. Acestea pot fi setate folosind funcţia glMaterialfv.
void glMaterial{if}[v](GLenum face, GLenum pname, TYPE param);
Funcţia specifică proprietăţile de material folosite în calculul iluminării.- face reprezintă faţa obiectului pe care se vor aplica proprietăţile de material; poate
avea una dintre valorile GL_FRONT, GL_BACK sau GL_FRONT_AND_BACK.
- pname indică proprietăţile de material;- param indică una dintre valorile proprietăţilor selectate în parametrul pname.
Valorile posibile pentru pname sunt date în tabelul II.7.
pname Valoare implicita ObservaţieGL_AMBIENT (0.2, 0.2, 0.2, 1.0) Culoarea ambiantă a materialuluiGL_DIFFUSE (0.8, 0.8, 0.8, 1.0) Culoarea difuză a materialuluiGL_AMBIENT_AND_DIFFUSE Culoarea ambiantă şi difuză a materialuluiGL_SPECULAR (0.0, 0.0, 0.0, 1.0) Culoarea speculară a materialuluiGL_SHININESS 0.0 Exponentul specular GL_EMISSION (0.0, 0.0, 0.0, 1.0) Culoarea emisă a materialuluiGL_COLOR_INDEXES (0,1,1) Indicii de culoare ambiantă, difuză şi speculară
Tabelul II.7.
Reflexia difuză şi ambiantă
Parametrii GL_DIFFUSE şi GL_AMBIENT setaţi cu funcţia glMaterial* afectează culoarea luminii ambiante şi difuze reflectate de un obiect.
Exemplu: asignarea simultană a aceleeaşi valori luminii reflectate difuze şi ambiante :
GLfloat mat_amb_diff[] = { 0.1, 0.5, 0.8, 1.0 };glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, mat_amb_diff);
Reflexia speculară
OpenGL permite setarea culorii RGBA a strălucirii speculare (folosind GL_SPECULAR) şi poate controla dimensiunea şi luminozitatea strălucirii (folosind GL_SHININESS). Parametrului GL_SHININESS i se poate asocia o valoare în domeniul [0.0, 128.0];cu cât valoarea este mai mare cu atât stălucirea este mai mică şi mai luminoasă.
Exemplu :
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };GLfloat low_shininess[] = { 5.0 };glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess);
Emisia
Prin asocierea unei culori RGBA parametrului GL_EMISSION se poate face ca un obiect să pară că furnizează lumină de culoarea selectată.
Exemplu:
GLfloat mat_emission[] = {0.3, 0.2, 0.2, 0.0};glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
Modificarea proprietăţilor de material
În fişierul din exemplul6.c toate vârfurile au asociate aceleaşi proprietăţi de material. Uneori se doreşte să se asocieze vârfurilor aceluiaşi obiect proprietăţi de material diferite. De obiecei într-o scenă sunt mai multe obiecte şi fiecare au asociate proprietăţi de material diferite.
Exemplu : desenarea a opt sfere fiecare având alte proprietăţi de material :
GLfloat no_mat[] = { 0.0, 0.0, 0.0, 1.0 };GLfloat mat_ambient[] = { 0.7, 0.7, 0.7, 1.0 };GLfloat mat_ambient_color[] = { 0.8, 0.8, 0.2, 1.0 };GLfloat mat_diffuse[] = { 0.1, 0.5, 0.8, 1.0 };GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };GLfloat no_shininess[] = { 0.0 };GLfloat low_shininess[] = { 5.0 };GLfloat high_shininess[] = { 100.0 };GLfloat mat_emission[] = {0.3, 0.2, 0.2, 0.0};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* reflexie difuză */glPushMatrix(); glTranslatef (-3.75, 3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat); glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat); glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, no_mat); glutSolidSphere();glPopMatrix();
/* reflexie difuză şi speculară; strălucire redusă */glPushMatrix(); glTranslatef (-1.25, 3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat); glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, low_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, no_mat); glutSolidSphere();glPopMatrix();
/* reflexie difuză şi speculară; stralucire ridicată */glPushMatrix(); glTranslatef (1.25, 3.0, 0.0);
glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat); glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, no_mat); glutSolidSphere();glPopMatrix();
/* reflexie difuză, emisie */glPushMatrix(); glTranslatef (3.75, 3.0, 0.0); glMaterialfv(GL_FRONT, GL_AMBIENT, no_mat); glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat); glMaterialfv(GL_FRONT, GL_SHININESS, no_shininess); glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission); glutSolidSphere();glPopMatrix();
Funcţia glMaterialfv este apelată în mod repetat pentru a seta proprietăţile de material pentru fiecare sferă. Ea este reapelată doar pentru acele proprietăţi care se modifică de la o sferă la alta (adică de la un obiect la altul). Deoarece funcţia glMaterial* are un cost de execuţie mare este bine să se minimizeze modificările proprietăţilor de material. O altă tehnică de a minimiza costurile asociate cu modificarile proprietăţilor de material este folosirea funcţiei glColorMaterial :
void glColorMaterial(GLenum face, GLenum mode);
Proprietăţile de material specificate de parametrul mode ale feţei specificate de parametrul face ia întotdeauna valoarea culorii curente. Astfel, o modificare asupra culorii curente (folosind funcţia glColor*) actualizează imediat proprietăţile de material specificate.
- face poate avea una dintre următoarele valori: GL_FRONT, GL_BACK sau GL_FRONT_AND_BACK (valoare implicită);
- mode poate avea una din următoarele valori: GL_AMBIENT, GL_DIFFUSE, GL_AMBIENT_AND_DIFFUSE (valoare implicită), GL_SPECULAR sau GL_EMISSION.
Observaţie: glColorMaterial actualizează proprietatea/proprietăţile de material specificate de parametrul mode ale faţei/feţelor specificate de parametrul face.
După apelul funcţiei glColorMaterial trebuie apelată funcţia glEnable având ca parametru GL_COLOR_MATERIAL. Apoi culoarea curentă poate fi modificată folosind funcţia glColor* (sau alte proprietăţi de material folosind funcţia glMaterial*).
Exemplu:
glColorMaterial(GL_FRONT, GL_DIFFUSE);glEnable(GL_COLOR_MATERIAL);glColor3f(0.2, 0.5, 0.8);/* afişează obiecte */glColor3f(0.9, 0.0, 0.2);/* afişează alte obiecte */glDisable(GL_COLOR_MATERIAL);
Funcţia glColorMaterial se va folosi de câte ori se doreşte modificarea unui singur parametru de material pentru majoritatea vârfurilor din scenă. Dacă se doreşte modificarea mai multor parametri de material se va folosi funcţia glMaterial*.
II.11. Liste de afişare
II.11.1. Utilizarea listelor de afişare
O listă de afişare reprezintă un grup de comenzi OpenGL stocate pentru o execuţie ulterioară. La invocarea unei liste de afişare comenzile din ea sunt executate în ordinea în care sunt întâlnite. Majoritatea comenzilor OpenGL pot fi stocate într-o listă de afişare sau pot apare în modul imediat (sunt executate imediat). Modul imediat de programare poate fi combinat cu listele de afişare.
Listele de afişare pot îmbunătăţi programul deoarece instrucţiunile sunt stocate pentru execuţii ulterioare. Este indicat să se folosească liste de afişare în cazul în care se redesenează de mai multe ori aceeaşi figură geometrică sau dacă trebuie aplicat de mai multe ori un set de modificări de stare.
O listă de afişare este un mod eficient şi convenabil de a combina un set de comenzi OpenGL.
Exemplu: desenarea unui cerc format din 100 de segmente. Codul corespunzător desenării cercului fără a folosi liste de afişare este următorul:
drawCircle(){ GLint i; GLfloat cosine, sine;
glBegin(GL_POLYGON);
for(i=0;i<100;i++){ cosine=cos(i*2*PI/100.0); sine=sin(i*2*PI/100.0); glVertex2f(cosine,sine); } glEnd();}
Această metodă este ineficientă deoarece calculele trigonometrice vor fi efectuate de fiecare dată când cercul va fi afişat. O altă modalitate este de a salva aceste coordonate într-un vector şi a le folosi ori de câte ori este nevoie :
drawCircle(){ GLint i; GLfloat cosine, sine; static GLfloat circoords[100][2]; static GLint inited=0;
if(inited==0){ inited=1; for(i=0;i<100;i++){ circcoords[i][0]=cos(i*2*PI/100.0); circcoords[i][1]=sin(i*2*PI/100.0); } } glBegin(GL_POLYGON); for(i=0;i<100;i++)
glVertex2fv(&circcoords[i][0]); glEnd();}
Şi această metodă prezintă dezavantajul incrementării şi testării variabilei i. Ceea ce se doreşte este să se deseneze o singură dată cercul şi să se cunoască modul de redesenare ulterior. Acest lucru este realizat prin folosirea listelor de afişare.
Exemplu: crearea unei liste de afişare
#define MY_CIRCLE_LIST 1
buildCircle(){ GLint i; GLfloat cosine, sine;
glNewList(MY_CIRCLE_LIST, GL_COMPILE); glBegin(GL_POLYGON); for(i=0;i<100;i++){ cosine=cos(i*2*PI/100.0); sine=sin(i*2*PI/100.0); glVertex2f(cosine,sine); } glEnd(); glEndList();}
Codul pentru desenarea cercului se află între apelurile glNewList şi glEndList. Aceste apeluri delimitează o listă de afişare. Argumentul MY_CIRCLE_LIST al funcţiei glNewList este un index întreg care identifică în mod unic lista de afişare. Ulterior lista de afişare poate fi executată folosind comanda glCallList:
glCallList(MY_CIRCLE_LIST);
Listele de afişare sunt un mod convenabil şi eficient de a vedea sub forma unui nume o secvenţă de comenzi OpenGL. O listă de afişare conţine numai apeluri OpenGL. Alte apeluri - ca în exemplul de mai sus, cum ar fi funcţiile C cos şi sin – nu sunt stocate în listele de afişare. Coordonatele şi celelalte variabile (cum ar fi conţinutul vectorului) sunt evaluate şi copiate în lista de afişare având valorile de la momentul compilării listei.
După compilarea listei aceste valori nu pot fi modificate. Lista de afişare poate fi ştearsă şi se poate crea una nouă, dar o listă de afişare existentă nu poate fi editată.
Exemplu Programul din fişierul exemplul8.c vizualizează unui tor din diferite unghiuri. Cel mai eficient mod de a face acest lucru este de a păstra torul într-o listă de afişare. Apoi, de câte ori se doreşte să se modifice poziţia observatorului se va modifica matricea ModelView şi se va executa lista de afişare pentru desenarea torului.
/* fişierul exemplul8.c */
#include "glut.h"
#include <stdio.h>#include <math.h>#include <stdlib.h>
#define M_PI 3.14
GLuint theTorus;
/* afişare tor */void torus(int numc, int numt){ int i, j, k; double s, t, x, y, z, twopi;
twopi = 2 * (double)M_PI; for (i = 0; i < numc; i++) { glBegin(GL_QUAD_STRIP); for (j = 0; j <= numt; j++) { for (k = 1; k >= 0; k--) { s = (i + k) % numc + 0.5; t = j % numt;
x = (1+.1*cos(s*twopi/numc))*cos(t*twopi/numt); y = (1+.1*cos(s*twopi/numc))*sin(t*twopi/numt); z = .1 * sin(s * twopi / numc);
glVertex3f(x, y, z); } } glEnd(); }}
/* creează lista de afişare pentru tor */void init(void){ theTorus = glGenLists (1); glNewList(theTorus, GL_COMPILE); torus(8, 25); glEndList();
glShadeModel(GL_FLAT); glClearColor(0.0, 0.0, 0.0, 0.0);}
void display(void){ glClear(GL_COLOR_BUFFER_BIT); glColor3f (1.0, 1.0, 1.0); glCallList(theTorus); glFlush();}
void reshape(int w, int h){ glViewport(0, 0, (GLsizei) w, (GLsizei) h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(30, (GLfloat) w/(GLfloat) h, 1.0, 100.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0);}
/* la apăsarea tastei 'x' - se roteşte în jurul axei x
la apăsarea tastei 'y' - se roteşte în jurul axei y la apăsarea tastei 'i' - se poziţioneaza torul în poziţia originală*/void keyboard(unsigned char key, int x, int y){ switch (key) { case 'x': case 'X': glRotatef(30.,1.0,0.0,0.0); glutPostRedisplay(); break; case 'y': case 'Y': glRotatef(30.,0.0,1.0,0.0); glutPostRedisplay(); break; case 'i': case 'I': glLoadIdentity(); gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0); glutPostRedisplay(); break; case 27: exit(0); break; }}
int main(int argc, char **argv){ glutInitWindowSize(200, 200); glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutCreateWindow(argv[0]); init(); glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutDisplayFunc(display);
glutMainLoop(); return 0;}
Utilizatorul poate roti torul în jurul axei OX sau OY prin apăsarea tastelor x respectiv y. De câte ori se întamplă acest lucru este apelată funcţia callback keyboard care înmulţeşte matricea de rotaţie de 30o în jurul axei x sau y cu matricea curentă ModelView, după care este apelată funcţia glutPostRedisplay, care face ca funcţia glutMainLoop să apeleze funcţia display şi să afişeze torul după prelucrarea altor evenimente. La apăsarea tastei ‘i’ funcţia keyboard reface matricea iniţială ModelView şi reafişează torul în poziţia sa iniţială. Funcţia display şterge fereastra şi apelează funcţia glCallList pentru a executa comenzile din lista de afişare.
Dacă nu s-ar fi folosit liste de afişare funcţia display ar fi trebuit să conţină comenzi de desenare a torului de fiecare dată când ar fi fost apelată.
O listă de afişare conţine numai comenzi OpenGL. În exemplul din fişierul exemplul8.c sunt stocate apelurile funcţiilor glBegin, glVertex şi glEnd. Parametrii apelurilor sunt evaluaţi şi valorile lor sunt copiate în lista de afişare la crearea sa. Toate calculele trigonometrice pentru crearea torului sunt făcute o singură dată ceea ce duce la creşterea performanţelor afişării.
Exemplu: aplicarea unor transformări unor obiecte geometrice şi apoi desenarea rezultatului:
glNewList(1, GL_COMPILE); afiseaza_obiectele_geometrice(); glEndList();
glLoadMatrix(M);glCallList(1);
Dacă obiectele sunt transformate de fiecare dată în acelaşi mod este bine să se păstreze matricea de transformare într-o listă de afişare.
ExempluIn unele implementări, se vor putea îmbunătaţi performanţele prin transformarea obiectelor în momentul definirii lor în loc de a le transforma de fiecare dată când sunt afişate :
glNewList(1, GL_COMPILE);glLoadMatrix(M);afiseaza_obiectele_geometrice(); glEndList();
glCallList(1);
Listele de afişare au şi dezavantaje. Listele foarte mici nu vor îmbunătăţi execuţia programului datorită overhead-ului execuţiei listei. Un alt dezavantaj constă din faptul că o listă de afişare nu poate fi modificată şi conţinutul său nu poate fi citit. Dacă aplicaţia necesită păstrarea datelor separat faţă de lista de afisare atunci va fi necesară memorie suplimentară.
II.11.2. Crearea şi executarea listelor de afişare
Funcţiile glNewList şi glEndList sunt folosite pentru delimitarea unei liste de afişare, care este executată prin apelul funcţiei glCallList având ca parametru identificatorul său. În fişierul exemplul9.c lista de afişare este creată în funcţia init. În funcţia display lista de afişare va fi apelată de 10 ori. Listele de afişare alocă memorie pentru a stoca comenzile şi valorile oricărei variabilele necesare. Funcţia glTranslatef din lista de afişare modifică poziţia următorului obiect ce va fi afişat. Apelul drawLine este de asemenea afectat de funcţia glTranslatef care o precede.
/* fişierul exemplul9.c */
#include “glut.h”#include <stdlib.h>
GLuint listName;
void init (void){ listName = glGenLists (1); glNewList (listName, GL_COMPILE); glColor3f (1.0, 0.0, 0.0); /* culoarea curentă roşu */ glBegin (GL_TRIANGLES); glVertex2f (0.0, 0.0); glVertex2f (1.0, 0.0);
glVertex2f (0.0, 1.0); glEnd (); glTranslatef (1.5, 0.0, 0.0); /* modificare poziţie */ glEndList (); glShadeModel (GL_FLAT);}void drawLine (void){ glBegin (GL_LINES); glVertex2f (0.0, 0.5); glVertex2f (15.0, 0.5); glEnd ();}
void display(void){ GLuint i;
glClear (GL_COLOR_BUFFER_BIT); glColor3f (0.0, 1.0, 0.0); /* culoarea curentă verde */ for (i = 0; i < 10; i++) /* afişare 10 triunghiuri */ glCallList (listName); drawLine (); glFlush ();}
void reshape(int w, int h){ glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); if (w <= h) gluOrtho2D (0.0, 2.0, -0.5 * (GLfloat) h/(GLfloat) w, 1.5 * (GLfloat) h/(GLfloat) w); else gluOrtho2D (0.0, 2.0*(GLfloat) w/(GLfloat) h, -0.5, 1.5); glMatrixMode(GL_MODELVIEW); glLoadIdentity();}
void keyboard(unsigned char key, int x, int y){ switch (key) { case 27: exit(0); }}
int main(int argc, char** argv){ glutInit(&argc, argv); glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(650, 50); glutCreateWindow(argv[0]); init (); glutReshapeFunc (reshape); glutKeyboardFunc (keyboard); glutDisplayFunc (display); glutMainLoop(); return 0;}
Observaţie: Dacă o listă de afişare conţine comenzi de transformare trebuie avut grijă care va fi efectul acestora în program mai târziu.
La un moment dat poate fi creată numai o singură lista de afişare. Cu alte cuvinte trebuie terminată crearea unei liste de afişare (glNewList şi glEndList) înainte de a crea o altă listă. Apelul funcţiei glEndList fără a fi apelat înainte funcţia glNewList va genera eroarea GL_INVALID_OPERATION.
II.11.2.1. Crearea unei liste de afişare
Fiecare listă de afişare este identificată printr-un index întreg. La crearea unei liste de afişare trebuie să se aibă grijă să nu se aleagă un index care este deja folosit, altfel se va şterge lista de afişare existentă. Pentru evitarea acestui lucru se va folosi funcţia glGenLists ce va genera unul sau mai mulţi indici nefolosiţi :
GLuint glGenLists(GLsizei range);
Funcţia alocă un domeniu de range numere continue nefolosite ca indici pentru liste de afişare. Valoarea întoarsă de funcţie reprezintă indicele de început a blocului de indici nefolosiţi. Indicii returnaţi vor fi marcaţi ca folosiţi astfel ca apelurile ulterioare ale funcţiei glGenLists nu vor întoarce aceşti indici până nu vor fi şterşi. Funcţia întoarce 0 dacă nu este disponibil numarul de indici ceruţi sau dacă range este 0.
Exemplu: alocarea unui singur index; dacă el este liber va fi folosit pentru a crea o nouă listă de afişare :
listIndex = glGenLists(1);if (listIndex != 0) { glNewList(listIndex,GL_COMPILE); ... glEndList();}
Observaţie: Indexul 0 nu reprezintă un index valid pentru lista de afişare.
void glNewList (GLuint list, GLenum mode);
Funcţia specifică începerea unei liste de afişare. Funcţiile OpenGL care vor fi apelate
(până la întâlnirea funcţiei glEndList care marchează sfârşitul listei de afişare) sunt
stocate în lista de afişare, excepţie facând câteva funcţii OpenGL care nu pot fi stocate.
Aceste funcţii restricţionate sunt executate imediat în timpul creării listei de afişare.
- list este un întreg pozitiv diferit de 0 care identifică în mod unic lista de afişare.
- valorile posibile ale parametrului mode sunt GL_COMPILE şi
GL_COMPILE_AND_EXECUTE. Se foloseşte valoarea GL_COMPILE dacă
nu se doreşte executarea comenzilor OpenGL la plasarea lor în lista de afişare;
pentru execuţia imediată în momentul plasării în lista de afişare, pentru folosiri
ulterioare, se va secifica parametrul GL_COMPILE_AND_EXECUTE.
void glEndList (void);
Funcţia marchează sfârşitul unei liste de afişare.
La crearea unei liste de afişare ea va fi stocată împreună cu contextul OenGL
curent. Astfel, dacă contextul va fi distrus, de asemenea lista de afişare va fi distrusă. În
unele sisteme este posibil ca listele de afişare să partajeze contexte multiple. În acest caz
lista de afişare va fi distrusă în momentul în care ultimul context din grup este distrus.
La crearea unei liste de afişare în ea vor fi stocate doar valorile expresiilor. Dacă
valorile dintr-un vector sunt modificate, valorile din lista de afişare nu vor fi modificate.
Exemplu
Se crează o listă de afişare ce conţine o comandă care setează culoarea curentă de afişare
negru
(0.0, 0.0, 0.0). Modificarea ulterioară a valorii vectorului color_vector în roşu (1.0, 0.0,
0.0) nu
are nici un efect asupra listei de afişare deoarece aceasta conţine valorile de la crearea sa.
GLfloat color_vector[3] = {0.0, 0.0, 0.0};glNewList(1, GL_COMPILE); glColor3fv(color_vector);glEndList();color_vector[0] = 1.0;
Într-o listă de afişare nu pot fi stocate şi executate orice comenzi OpenGL. În tabelul II.8 sunt prezentate comenzile care nu pot fi stocate într-o listă de afişare (apelul funcţiei glNewList în timpul creării unei liste de afişare generează eroare).
glColorPointer() glFlush() GlNormalPointer()glDeleteLists() glGenLists() GlPixelStore()glDisableClientState() glGet*() GlReadPixels()glEdgeFlagPointer() glIndexPointer() GlRenderMode()glEnableClientState() glInterleavedArrays() GlSelectBuffer()glFeedbackBuffer() glIsEnabled() GlTexCoordPointer()glFinish() glIsList() GlVertexPointer()
Tabelul II.8.
La folosirea unui sistem OpenGL în reţea, clientul poate rula pe o maşină şi server-ul pe alta. După crearea unei liste de afişare ea se va afla la server astfel că server-
ul nu se poate baza pe client pentru nici o informaţie relativă la lista de afişare. Astfel, orice comandă care întoarce o valoare nu poate fi stocată într-o listă de afişare. În plus, comenzile care modifică starea clientului cum ar fi glPixelStore, glSelectBuffer şi comenzile de definire a vectorilor de vârfuri nu pot fi stocate într-o listă de afişare.
Operaţiile unor comenzi OpenGL depind de starea clientului. De exemplu, funcţiile de specificare a vârfurilor vectorilor (cum ar fi glVertexPointer, glColorPointer şi glInterleavedArrays) setează pointeri de stare ai clientului şi nu pot fi stocate într-o listă de afişare. Funcţiile glArrayElement, glDrawArrays şi glDrawElements transmit date către server pentru a construi primitive din elementele vectorilor. Astfel de operaţii pot fi stocate într-o listă de afişare.
Vectorul de vârfuri stocat în lista de afişare este obţinut prin dereferenţierea datei din pointeri nu prin stocarea pointerilor . Astfel, modificările datelor din vectorul de vârfuri nu va afecta definirea primitivei în lista de afişare.
Funcţii cum ar fi glFlush şi glFinish nu pot fi stocate într-o listă de afişare deoarece depind de starea clientului în momentul execuţiei.
II.11.2.2. Execuţia unei liste de afişare
După crearea unei liste de afişare aceasta poate fi executată prin apelul funcţiei glCallList. O listă de afişare poate fi executată de mai multe ori şi de asemenea o listă de afişare poate fi executată îmbinându-se cu apeluri în modul imediat.
void glCallList (GLuint list);
Functia execută lista de afişare specificată de parametrul list. Comenzile din lista de afişare sunt executate în ordinea în care au fost salvate ca şi cum ar fi executate fără a se folosi o lista de afişare. Dacă lista nu a fost definită nu se va întâmpla nimic.
Funcţia glCallList poate fi apelată din orice punct al programului atâta timp cât contextul OpenGL care accesează lista de afişare este activ (contextul care a fost activ la crearea listei de afişare sau un context din acelaşi grup partajat). O listă de afişare poate fi creată într-o funcţie şi executată în altă funcţie atâta timp cât indexul său o identifică în mod unic. De asemenea, contextul unei liste de afişăre nu poate fi salvat într-un fişier şi nici nu se poate crea o listă de afişare dintr-un fişier. În acest sens o listă de afişare este proiectată pentru a fi folosită temporar.
II.11.3. Liste de afişare ierarhice
O listă de afişare ierarhică este o listă de afişare care execută o altă listă de afişare prin apelul funcţiei glCallList, între perechile de funcţii glNewList şi glEndList. O listă de afişare ierarhică este folositoare pentru un obiect alcătuit din componente, în mod special dacă aceste componente sunt folosite de mai multe ori.
Exemplu: o listă de afişare care desenează o bicicletă prin apelul altor liste de afişare pentru desenarea componentelor :
glNewList(listIndex,GL_COMPILE); glCallList(handlebars); glCallList(frame); glTranslatef(1.0,0.0,0.0); glCallList(wheel); glTranslatef(3.0,0.0,0.0); glCallList(wheel);glEndList();
Pentru evitarea recursivităţii infinite limita nivelului de imbricare al listelor de afişare este 64, dar această limită depinde de implementare. Pentru a determina limita specifică implementării OpenGL pe care se lucrează se apelează funcţia :
glGetIntegerv(GL_MAX_LIST_NESTING, GLint *data);
OpenGL permite crearea unei liste de afişare care să apeleze o altă listă de afişare care nu a fost încă creată. Nu va avea nici un efect dacă prima listă o apelează pe cea de a doua care încă nu a fost definită.
Exemplu : listă de afişare ierarhică
glNewList(1,GL_COMPILE); glVertex3f(v1); glEndList();glNewList(2,GL_COMPILE); glVertex3f(v2); glEndList();glNewList(3,GL_COMPILE); glVertex3f(v3); glEndList();
glNewList(4,GL_COMPILE); glBegin(GL_POLYGON); glCallList(1); glCallList(2); glCallList(3); glEnd();glEndList();
Pentru afişarea poligonului se apelează lista de afişare 4. Pentru a edita un vârf este necesar să se creeze din nou lista de afişare corespunzătoare vârfului respectiv. Deoarece un index identifică în mod unic lista de afişare, crearea unei alte liste cu acelaşi index o va şterge în mod automat pe cea existentă. Trebuie reţinut că această metodă nu foloseşte în mod optim memoria şi nu îmbunătăţeşte performanţele, dar este folositoare în unele cazuri.
II.11.4. Gestiunea listelor de afişare cu indici
Pentru a obţine un indice nefolosit care să identifice o nouă listă de afişare se poate folosi funcţia glGenLists. Dacă nu se doreşte folosirea acestei funcţii atunci se poate folosi glIsList pentru a determina dacă un anumit index este folosit.
GLboolean glIsList(GLuint list);
Funcţia întoarce GL_TRUE dacă indexul specificat de parametrul list este deja folosit pentru o listă de afişare şi GL_FALSE în caz contrar.
Pentru a şterge în mod explicit o listă de afişare sau un domeniu continuu de liste se foloseşte funcţia glDeleteLists. Folosirea funcţiei glDeleteLists eliberează indicii corespunzători listelor şterse să fie disponibili din nou.
void glDeleteLists(GLuint list, GLsizei range);
Funcţia şterge range liste de afişare începând de la indexul specificat de list. Este ignorată încercarea de a se şterge o listă de afişare care nu a fost creată.
II.11.5. Execuţia listelor de afişare multiple
OpenGL furnizează un mecanism eficient de a executa succesiv liste de afişare. Acest mecanism necesită introducerea indicilor listelor de afişare într-un vector si apoi apelarea funcţiei glCallLists. O utilizare pentru acest mecanism este acela de a crea un font şi fiecare indice de listă de afişare să corespundă valorii ASCII a caracterului din font. Pentru a avea mai multe fonturi este necesară stabilirea unui index iniţial diferit pentru fiecare font. Acest index iniţial poate fi specificat prin apelarea funcţiei glListBase înainte de a apela glCallLists.
void glListBase(GLuint base);
Funcţia specifică offset-ul care este adunat indicilor listelor de afişare în apelul funcţiei glCallLists pentru a obţine indicii listelor de afişare finali. Valoarea implicită a parametrului base este 0. Parametrul base nu are nici un efect asupra apelului glCallList, care execută o singură listă de afişare, sau asupra funcţiei glNewList.
void glCallLists(GLsizei n, GLenum type, const GLvoid *lists);
Functia execută n liste de afişare. Indicii listelor care vor fi executate vor fi calculaţi prin adunarea offset-ului indicat de baza curentă a listei de afişare (specificat cu ajutorul funcţiei glListBase la valorile întregi indicate de parametrul lists. Parametrul type specifică tipul valorilor din lists. El poate avea una din valorile GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT sau GL_FLOAT, adică ceea ce indică lists poate fi tratată ca un vector de bytes, unsigned bytes, shorts, unsigned shorts, integers, unsigned integers, sau floats. De asemenea parametrul type poate avea una din valorile GL_2_BYTES, GL_3_BYTES sau GL_4_BYTES caz în care succesiuni de 2, 3 sau 4 octeţi vor fi citiţi din lists şi apoi sunt deplasaţi şi adunaţi octet cu octet pentru a calcula offset-ul listei de afişare. Pentru aceasta este folosit următorul algoritm (byte[0] reprezintă începutul secvenţei de octeţi).
/* b = 2, 3 sau 4; octeţii sunt numerotaţi în vector 0,1,2,3 */ offset = 0; for (i = 0; i < b; i++) { offset = offset << 8; offset += byte[i]; } index = offset + listbase;
Exemplu Definirea listelor de afişare multiple: afişarea caracterelor dintr-un set de caractere vectorial
void initStrokedFont(void)/* setează indicii listelor de afişare pentru fiecare caracter corespunzător valorii lor ASCII */{ GLuint base;
base = glGenLists(128); glListBase(base);
glNewList(base+'A', GL_COMPILE); drawLetter(Adata); glEndList(); glNewList(base+'E', GL_COMPILE); drawLetter(Edata); glEndList(); glNewList(base+'P', GL_COMPILE); drawLetter(Pdata); glEndList(); glNewList(base+'R', GL_COMPILE); drawLetter(Rdata); glEndList(); glNewList(base+'S', GL_COMPILE); drawLetter(Sdata); glEndList(); glNewList(base+' ', GL_COMPILE); /* spaţiu */ glTranslatef(8.0, 0.0, 0.0); glEndList();}
Funcţia glGenLists alocă 128 de indici continui pentru listele de afişare. Primul indice alocat devine baza listei de afişare. Pentru fiecare caracter va fi creată câte o listă de afişare; fiecare index al listei de afişare este suma dintre indicele de bază şi valoarea ASCII a literei. În acest exemplu sunt create numai câteva litere şi caracterul ‘ ‘. După crearea listelor de afişare poate fi apelata funcţia glCallLists pentru a le executa.
ExempluApelul functiei printStrokedString având ca parametru un caracter :
void printStrokedString(GLbyte *s){ GLint len = strlen(s);
glCallLists(len, GL_BYTE, s);}
II.11.6. Gestiunea variabilelor de stare folosind liste de afişare
O listă de afişare poate conţine apeluri care să modifice valorile variabilelor de stare OpenGL. Aceste valori se modifică la execuţia listei de afişare (ca şi în modul imediat de execuţie al comenzilor) şi modificările se păstrează şi după execuţia completă a listei de afişare.
ExempluModificările culorii curente şi a matricii curente făcute în timpul execuţiei listei de afişare rămân vizibile şi după executarea sa:
glNewList(listIndex,GL_COMPILE); glColor3f(1.0, 0.0, 0.0); glBegin(GL_POLYGON); glVertex2f(0.0,0.0); glVertex2f(1.0,0.0); glVertex2f(0.0,1.0); glEnd(); glTranslatef(1.5,0.0,0.0);glEndList();
Dacă se va apela următoarea secvenţă de cod, segmentul de dreaptă desenat după lista de afişare va avea culoarea roşie (culoarea curentă) şi va fi translatat cu (1.5, 0.0, 0.0):
glCallList(listIndex);glBegin(GL_LINES); glVertex2f(2.0,-1.0); glVertex2f(1.0,0.0);glEnd();
Uneori este necesar ca modificările stării să fie păstrate, dar alteori după execuţia unei liste de afişare se doreşte să se revină la starea anterioară. Într-o listă de afişare nu poate fi folosită funcţia glGet*, aşa că trebuie folosit un alt mod de a interoga şi stoca valorile variabilelor de stare. În acest scop poate fi folosită funcţia glPushAttrib pentru a salva un grup de variabile de stare şi glPopAttrib pentru a reface variabilele.
Exemplu Refacerea variabilelor de stare din interiorul unei liste de afişare
glNewList(listIndex,GL_COMPILE); glPushMatrix(); glPushAttrib(GL_CURRENT_BIT); glColor3f(1.0, 0.0, 0.0); glBegin(GL_POLYGON); glVertex2f(0.0,0.0); glVertex2f(1.0,0.0); glVertex2f(0.0,1.0); glEnd();
glTranslatef(1.5,0.0,0.0); glPopAttrib(); glPopMatrix();glEndList();
ExempluDacă se foloseşte lista de afişare de mai sus atunci se va desena un segment de dreaptă de culoare verde şi netranslatat:
void display(void){ GLint i; glClear(GL_COLOR_BUFFER_BIT); glColor3f(0.0, 1.0, 0.0); /* setează culoarea curentă verde */ for (i = 0; i < 10; i++) glCallList(listIndex); /* lista de afişare apelată de 10 ori*/ drawLine(); /* unde şi cum apare această linie? */ glFlush();}
II.11.7. Încapsularea schimbărilor de mod
Listele de afişare pot fi folosite pentru a organiza şi stoca grupuri de comenzi, pentru a modifica diferite moduri sau a seta diferiţi parametri. Când se doreşte comutarea de la un grup de setări la un altul folosirea listelor de afişare poate fi mai eficientă decât apelarea directă.
Listele de afişare pot fi mai eficiente decât modul imediat pentru comutarea între diferite setări ale surselor de lumină, modelelor de iluminare şi parametrilor de material. De asemenea, listele de afişare se pot folosi şi pentru generarea liniilor cu şablon, precum şi pentru ecuaţiile planului de decupare. În general, execuţia listelor de afişare este cel puţin tot atât de rapidă ca şi apelul direct dar în cazul folosirii listelor de afişare este introdus un overhead.
ExempluFolosirea listelor de afişare pentru a comuta între trei şabloane diferite de linii. La început se apelează funcţia glGenLists pentru a aloca o listă de afişare pentru fiecare şablon. Apoi se foloseşte funcţia glCallList pentru a comuta între un şablon şi altul.
GLuint offset;offset = glGenLists(3);glNewList (offset, GL_COMPILE); glDisable (GL_LINE_STIPPLE);glEndList ();glNewList (offset+1, GL_COMPILE); glEnable (GL_LINE_STIPPLE); glLineStipple (1, 0x0F0F);glEndList ();glNewList (offset+2, GL_COMPILE); glEnable (GL_LINE_STIPPLE); glLineStipple (1, 0x1111);glEndList ();#define drawOneLine(x1,y1,x2,y2) glBegin(GL_LINES); \ glVertex2f ((x1),(y1)); glVertex2f ((x2),(y2)); glEnd();glCallList (offset);drawOneLine (50.0, 125.0, 350.0, 125.0);glCallList (offset+1);drawOneLine (50.0, 100.0, 350.0, 100.0);glCallList (offset+2);drawOneLine (50.0, 75.0, 350.0, 75.0);
Top Related