LA LIBRERIA GRAFICA OPENGL - II Parte
Traformazioni geometriche
OpenGL compie questa sequenza (pipeline) di trasformazioni geometriche
su ogni primitiva (sequenza tipica di ogni sistema grafico):
+------------------------+
|[3D] Object coordinates | sistema di riferimento locale in cui e'
|(o modeling coordinates)| definito un singolo oggetto della scena
+------------------------+
TRASFORMAZIONI DI| collocano il singolo oggetto all'interno della scena
MODELLAZIONE | (in generale una trasf. diversa per ogni oggetto)
V
+------------------------+ sistema di riferimento globale di tutta
|[3D] World coordinates | la scena
+------------------------+
TRASFORMAZIONE DI| posiziona la scena davanti alla telecamera
VISTA |
V
+------------------------+ sistema di riferimento solidale con la
|[3D] View coordinates | telecamera: occhio in (0,0,0), che guarda verso
| (o eye coordinates) | la direzione negativa dell'asse z
+------------------------+
TRASFORMAZIONE DI| definisce tipo di trasformazione (parallela o
PROIEZIONE | prospettica) e volume di vista
V
+------------------------+ sistema di riferimento normalizzato nel cubo
|[3D] Normalized | con x,y,z tra -1 e +1, contiene (deformato
| projection coordinates | dalla trasf. di proiezione) il volume di vista
+------------------------+
TRASFORMAZIONE DI| fatta automaticamente da OpenGL, il programma controlla
VIEWPORT | l'estensione dell'area di output su schermo (viewport)
V
+------------------------+
|[2D] Device coordinates | sistema di riferimento della finestra sullo
|(o viewport coordinates)| schermo (coordinate 2D espresse in pixel)
+------------------------+
Come si specificano le trasformazioni
OpenGL tiene 2 matrici di trasformazione nel suo stato corrente:
- GL_MODELVIEW per le trasformazioni di modellazione e di vista
(posizionare gli oggetti nella scena e la telecamera)
- GL_PROJECTION per la trasformazioni di proiezione (scegliere
tipo di proiezione ortogonale o prospettica e volume di vista)
Sono matrici 4x4 (matrici in coordinate omogenee 3D),
a queste matrici vanno soggette tutte le primitive che vengono rese:

Vi sono istruzioni per modificare le matrici correnti. Sono le stesse su
entrambe le matrici. Bisogna dichiarare esplicitamente su che matrice si
vuole agire.
-
glMatrixMode(matrice);
specifica su che matrice si lavorera' d'ora in poi,
dove matrice = GL_MODELVIEW oppure GL_PROJECTION
Per modificare la matrice corrente, OpenGL fornisce funzioni che permettono
di definire una trasformazione complessa come composizione di trasformazioni
elementari. Le trasformazioni elementari sono:
-
glLoadIdentity();
assegna la matrice corrente come la matrice identica
-
glTranslatef(dx,dy,dz); moltiplica la matrice corrente
con matrice di traslazione, (dx,dy,dz) e' il vettore traslazione
-
glRotatef(ang,rx,ry,rz); moltiplica con matrice di rotazione
attorno a retta passante per origine e parallela al
vettore (rx,ry,rz), ang e' angolo di rotazione misurato in gradi
-
glScalef(sx,sy,sz); moltiplica con matrice di scalatura
con fattori di scala sx,sy,sz sui tre assi, il punto fermo nella
scalatura e' l'origine
Posso ottenere qualsiasi trasformazione componendo queste.
Importante: la composizione delle matrici avviene moltiplicando la nuova
matrice a destra di quella corrente, quindi PRIMA viene eseguita la
trasformazione corrispondente alla NUOVA matrice e DOPO la trasformazione
corrispondente alla VECCHIA matrice.
In pratica le trasformazioni sono eseguite IN ORDINE INVERSO a come le
specifico nel codice.
Esempio:
glLoadIdentity(); matrice identita' I
glTranslate(...); matrice = I T = T dove T = matrice di traslazione
glRotate(...); matrice = T R dove R = matrice di rotazione
Risultato: nell'ordine prima ruoto poi traslo i punti
Esempio di trasfomazione composta
Rotazione attorno a un punto C = (xC,yC,zC) diverso da origine:
- traslo di (-xC,-yC,-zC) per portare C nell'origine del nuovo riferimento
- ruoto dell'angolo alpha voluto attorno a origine
- traslo di (xC,yC,zC) per portare C alla posizione originale
Nel codice:
3. glTranslatef(xC,yC,zC);
2. glRotatef(ang,rx,ry,rz);
1. glTranslatef(-xC,-yC,-zC);
Stack delle matrici
Le matrici MODELVIEW e PROJECTION fanno parte dello stato corrente.
Modificarle influisce su tutte le primitive da quel momento in poi.
Se voglio modificarla momentaneamente per disegnare un certo
gruppo di primitive (es. quelle che formano un oggetto), e poi ripristinarla,
devo usare glPushMatrix e glPopMatrix. Tipicamente questo succede per
le trasformazioni di modellazione con la MODELVIEW.
Esempio:
glMatrixMode(GL_MODELVIEW);
glPushMatrix(); /* salva la matrice corrente su stack */
... modifiche alla matrice ...
... disegno primitive sotto influsso della matrice modificata ...
glPopMatrix(); /* rispristina la matrice precedente */
... le primitive disegnate qui sono soggette alla matrice
senza le modifiche fatte tra push e pop ...
Il problema non si pone per trasformazioni di vista e di proiezione
che devono agire globalmente su TUTTA la scena.
Trasformazioni di modellazione e di vista
In OpenGL sono controllate agendo sulla stessa matrice, la MODELVIEW.
- PRIMA devono essere eseguite quelle di modellazione
- POI quelle di vista.
Cio' significa che nel codice sono invocate PRIMA le funzioni OpenGL
per la trasformazione di vista e DOPO quelle per le
trasformazioni di modellazione (ordine inverso!).
Trasformazioni di modellazione
Agiscono su ogni singolo oggetto separatamente per collocarli all'interno
della scena.
Definite agendo sulla matrice MODELVIEW, tipicamente
tra glPushMatrix e glPopMatrix affinche' la trasformazione
agisca solo sull'oggetto in questione.
Nell'ordine: dimensiono (glScale), oriento (glRotate), posiziono (glTranslate)
ciascuno degli oggetti per comporre la scena.
Trasformazione di vista
Agisce sull'intera scena per posizionarla davanti alla telecamera.
Definita agendo sulla matrice MODELVIEW, senza Push e Pop.
Due modi di definirla:
- A mano:
oriento (glRotate), posiziono (glTranslate) il
sistema di coordinate di modellazione per portarlo a coincidere
con il sistema di coordinate di vista
- Tramite la funzione di utilita' gluLookAt della libreria GLU
(implementata internamente mediante rotazioni e traslazioni):
gluLookAt(eyex,eyey,eyez,centerx,centery,centerz,upx,upy,upz);
dove
- (eyex,eyey,eyez) = coordinate di modellazione del punto V dove va
collocato l'occhio: sara' l'origine delle coordinate di vista
- (centerx,centery,centerz) = coordinate di modellazione del
centro C della scena, verso il quale l'occhio va rivolto:
sara' l'asse z negativo delle coordinate di vista
- (upx,upy,upz) = vettore (up-vector) la cui componente
perpendicolare a VC determina la
direzione dell'alto della telecamera: sara' l'asse y del
sistema di vista
manca un asse (l'asse x) del sistema di vista, che il OpenGL calcola
automaticamente come prodotto vettoriale dei due assi noti
Quando conviene usare l'uno o l'altro modo
In generale si preferisce gluLookAt perche' e' difficile costruire
a mano la trasformazione.
Puo' essere facile in casi particolari. Esempio:
Nel programma terra.c
posso collocare il punto di vista attorno alla scena in base a due angoli
(latitudine, longitudine) immessi da linea di comando.
La scena in coordinate del mondo e' centrata nell'origine.
E' costituita da una sfera verde (la terra) attraversata da un asse
rosso (congiungente i poli) e da un quadrato blu (che segna l'equatore).
La trasformazione di vista consiste in:
rotazione attorno all'asse z (di angolo longitudine), rotazione
attorno all'asse x (di angolo latitudine), traslazione per portare
il punto di vista ad una certa distanza dalla scena.
Qui usare gluLookAt sarebbe
piu' complicato perche' dovrei calcolare la posizione del punto
di vista in coordinate del mondo con funzioni trigonometriche.
Trasformazione di proiezione
Definisce il tipo di proiezione e il volume di vista.
Agisco sulla matrice PROJECTION con una fra:
-
glOrtho(left,right,bottom,top,near,far);
specifica matrice ortografica.
- Il volume di vista e' il parallelepipedo di diagonale
(left,bottom,-near) - (right,top,-far).
-
glFrustum(left,right,bottom,top,near,far);
specifica matrice prospettica.
-
Il volume di vista e' un tronco di piramide con l'apice in (0,0,0)
(l'occhio), base minore il rettangolo di diagonale
(left,bottom) - (right,top) giacente sul piano z=-near,
e base maggiore sul piano z=-far (ottenuta effettuando una proiezione
da (0,0,0) della base minore sul piano z=-far).
-
I valori di near e far devono essere positivi.
-
Al crescere del rapporto far/near si perde precisione nella
proiezione, quindi e' meglio evitare di definire volumi di vista
troppo profondi, e casomai scalare prima la scena.
-
In alternativa a glFrustum, posso usare la funzione di utilita'
gluPerspective, che accetta parametri piu' intuitivi.
gluPerspective(fovy,aspect,znear,zfar);
definisce trasformazione prospettica, dove
- fov = field of view = angolo di apertura della piramide in gradi,
- aspect = deformazione largezza/altezza della base del tronco di piramide
(in generale la si fa coincidere con quella della viewport),
- znear, zfar = distanza delle due basi del tronco di piramide
dall'occhio (valori positivi)
Trasformazione di viewport
Il contenuto del volume di vista viene mappato (deformandolo) nel cubo
normalizzato delle coordinate di proiezione.
Questo cubo viene poi appiattito schiacciandolo nella direzione z e mappato
nelle coordinate 2D della viewport grafica (area di rendering su schermo).
Di default OpenGL usa l'intera area della finestra. Posso invece
specificare esplicitamente una viewport (es. piu' piccola) con
- glViewport(x1,y1,x2,y2);
dove x1,y1 e x2,y2 sono le coordinate dell'angolo in basso a sx e
in alto a dx della viewport, in pixel.
Conservazione dell'aspect ratio
Se l'aspect ratio (rapporto tra dimensioni x e y) della viewport e quella
del volume di vista non sono uguali, l'immagine della scena viene
deformata allungandola in verticale o in orizzontale.
Per preservare nell'immagine le proporzioni della scena, devo mantenere
le due aspect ratio uguali tra loro:
-
o adeguando la viewport al volume di vista, eventualmente selezionando
solo una sottoparte della finestra attuale per il rendering (es. usare solo
una sottoparte quadrata se il volume di vista e' a base quadrata),
in questo caso agisco con glViewport.
-
oppure adeguando il volume di vista alla viewport (inquadrando piu' o meno
della scena a seconda della forma della finestra di rendering),
in questo caso modifico la trasformazione di proiezione.
Esempio
La finestra iniziale ha dimensione 400x400 pixel, le trasformazioni
iniziali sono:
- gluPerspective(fov,1.0,near,far);
- glViewport(0,0,400,400);
Aspect ratio = 1.0 perche' finestra quadrata.
Se la finestra viene deformata a 400x200
- o adeguo la viewport: glViewport(0,0,200,200);
(uso solo una sottoparte quadrata della viewport)
- o adeguo la trasformazione di proiezione:
gluPerspective(fov,2.0,near,far);
glViewport(0,0,400,200);
(inquadratura piu' larga, con aspect ratio = 2.0, perche' nella
viewport la dimensione x e' il doppio di quella y)
Disegnamento della scena OpenGL
(Vedere il file terra.c).
-
Abilitare le funzioni di OpenGL non di default con glEnable(...)
-
Pulire il foglio con glClear(...)
-
Definizione delle trasformazioni di proiezione:
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glOrtho(...); oppure glFrustum(...); oppure gluPerspective(...);
-
Definizione delle trasformazioni di vista:
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
- gluLookAt(...); oppure chiamate esplicite a glTranslate e glRotate
-
Disegno:
- alternanza di definizioni di primitive e impostazioni di attributi
e impostazioni di trasformazioni di modellazione
ottenute chiamando glTranslate, glScale, glRotate
sulla matrice GL_MODELVIEW
- per ottenere un effetto locale (temporaneo),
le impostazioni di attributi possono essere racchiuse tra
glPushAttrib / glPopAttrib e le impostazioni di trasformazioni
fra glPushMatrix e glPopMatrix
Display list
Una scena in OpenGL e' definita da una sequenza di istruzioni
che specificano primitive e attributi.
In condizioni normali, questa sequenza di istruzioni viene inviata
dal client al server, il quale la esegue e poi la "dimentica".
Se il client vuole rieseguire la stessa sequenza una
seconda volta, deve reinviarla, con spesa di tempo.
Display list = serie di istruzioni OpenGL a cui e' stato
assegnato un nome (identificatore numerico).
-
Quando il client invia al server una display list, il server la memorizza
(al contrario di quanto avviene per istruzioni non facenti parte di
display list).
-
Successivamente, il client puo' chiedere al server di rieseguire le
istruzioni contenute nella display list semplicemente inviando il nome
della display list (un solo numero intero), risparmiando cosi' tempo.
Una display list viene eseguita nello stato corrente di OpenGL.
Cosi' la stessa primitiva, inserita in una display list, puo'
essere chiamata con stato corrente modificato, dando luogo a un effetto
diverso.
Per esempio, tra una chiamata e l'altra della display list posso cambiare
colori, modalita' di visualizzazione wireframe/solid, spessore di linea,
trasformazioni geometriche.
Posso disegnare un'intera scena (es. composta da parallelepipedi di
dimensioni e colori diversi) eseguendo piu' volte un'unica display list
(es. che disegna un cubo) che viene
scalata/traslata/colorata in modo diverso.
Si puo' modificare lo stato corrente dentro la display list stessa.
In questo caso,
dopo aver eseguito la display list trovero' lo stato modificato.
Molto meglio cambiare lo stato localmente alla display list, usando
glPush... e glPop...
Istruzioni per display list
- first_list = glGenLists(num_list);
Crea num_list "nomi" di display list (indici interi) consecutivi,
e ritorna in first_list il primo di essi.
Dopo, posso "riempire" le display list ai nomi
first_list, first_list+1, ... first_list+num_list-1.
-
glNewList(list_name,modo);
...istruzioni che compongono la lista...
glEndList();
Delimitano una definizione di display list. list_name
e' il nome della lista che sto definendo.
modo e' GL_COMPILE
(la display list e' solo memorizzata, non eseguita) oppure
GL_COMPILE_AND_EXECUTE
(e' memorizzata ed eseguita direttamente)
- glCallList(list_name);
Chiama la display list indicata (ovvero la esegue).
L'effetto della chiamata risente dello stato corrente OpenGL.
Esempio
/* definisce display list contenente un quadrato */
mylist = glGenLists(1);
glNewList(mylist,GL_COMPILE);
glBegin(GL_QUADS);
glVertex2f(0.0,0.0);
glVertex2f(1.0,0.0);
glVertex2f(1.0,1.0);
glVertex2f(0.0,1.0);
glEnd();
glEndList();
...
/* disegna piu' quadrati chiamando la display list varie volte */
glMatrixMode(GL_MODELVIEW);
for (i=0; i<5.0; i++)
{
glPushMatrix();
glTranslatef(0.2*i,0.0,0.0);
glScalef(0.1,0.1,1.0);
glCallList(mylist);
glPopMatrix();
}
Quest'esempio e' contenuto nel file quadrati.c.
Illuminazione
Proprieta' di materiale
Sono assegnate con
glMaterial(faccia, nome_parametro, valore_parametro);
La funzione ha 4 varianti:
glMaterialf, glMateriali, glMaterialfv, glMaterialiv,
dove
- f/i a seconda che valore_parametro sia un float / un intero
- v a seconda che valore_parametro sia un array di float / interi
- faccia = GL_FRONT, GL_BACK o GL_FRONT_AND_BACK per assegnare proprieta'
alle front faces, le back faces, o a entrambe
- nome_parametro e valore sono illustrati dalla seguente tabella:
nome_parametro valore (scalare o vettore)
-------------------------------------------------------------------------
GL_AMBIENT vettore di componenti RGBA, default (0.2,0.2,0.2,1)
frazione di luce ambiente ricevuta che la primitiva
riflette, per ogni colore
-------------------------------------------------------------------------
GL_DIFFUSE vettore di componenti RGBA, default (0.8,0.8,0.8,1)
frazione di luce diffusa ricevuta che la primitiva
riflette, per ogni colore
-------------------------------------------------------------------------
GL_SPECULAR vettore di componenti RGBA, default (0,0,0,1)
frazione di luce speculare ricevuta che la primitiva
riflette, per ogni colore
-------------------------------------------------------------------------
GL_AMBIENT_AND_DIFFUSE per assegnare in un colpo GL_AMBIENT e GL_DIFFUSE
con lo stesso vettore RGBA
-------------------------------------------------------------------------
GL_EMISSION vettore di componenti RGBA, default (0,0,0,1)
componenti della luce emessa dalla primitiva stessa
-------------------------------------------------------------------------
GL_SHININESS intero tra 0 e 128
intensita' della luce speculare riflessa
-------------------------------------------------------------------------
Nota: componenti RGB = 0,0,0 implicano luce "nera",
ovvero nessuna luce (o nessuna componente di quel tipo nella luce).
Normali
Nel caso di luce diffusa, l'intensita' dell'illuminazione dipende dall'angolo
formato dalla direzione della luce con la normale alla superficie.
OpenGL NON calcola da solo le normali, bisogna dargliele da programma.
La normale e' un attributo presente nello stato corrente di OpenGL.
La normale corrente si assegna con la funzione glNormal(...),
che ha varie forme analogamente a quanto visto per glVertex:
glNormal3f(tre float) oppure
glNormal3fv(vettore di tre float).
Tipicamente:
- prima di elencare i vertici di una faccia assegno la normale
corrispondente a quella faccia
- elenco i vertici della faccia
- assegno la normale della prossima faccia, ecc.
In realta' posso anche
assegnare una normale diversa a ogni vertice della stessa faccia.
La normale per ciascun punto interno della faccia e' allora interpolata,
cosicche' la normale cambia gradualmente all'interno della faccia.
-
stessa normale per tutti i vertici di un poligono fa si' che il poligono
appaia come una faccia piana
-
normali diverse fanno si' che il poligono assuma un'apparenza curva
(noi pero' NON ci occupiamo di questo caso).
Per default la normale corrente e' (0,0,0) e gli oggetti appaiono "piatti"
(colorati di colore uniforme anche in presenza di luce diffusa, nonostante
l'inclinazione diversa delle facce).
Sorgenti luminose
Posso definire una o piu' sorgenti luminose e, dopo averle definite,
abilitarle/disabilitarle (per default disabilitate).
Per definire una sorgente:
glLight(luce, nome_parametro, valore_parametro);
-
La funzione ha 4 varianti glLightf glLighti, glLightfv, glLightiv,
dove f/i,v con stesso funzionamento che in glMaterial.
-
luce = GL_LIGHT0, GL_LIGHT1,... (OpenGL fornisce almento 7 luci)
-
nome_parametro e valore sono illustrati dalla seguente tabella
(riportiamo solo quelli principali):
nome_parametro valore (scalare o vettore)
-------------------------------------------------------------------------
GL_AMBIENT vettore di componenti RGBA, default (0,0,0,1)
valori di luce ambiente emessa dalla sorgente
-------------------------------------------------------------------------
GL_DIFFUSE vettore di componenti RGBA, default (1,1,1,1) per
GL_LIGHT0 e (0,0,0,1) per le altre
valori di luce diffusa emessa dalla sorgente
-------------------------------------------------------------------------
GL_SPECULAR vettore di componenti RGBA, default (1,1,1,1) per
GL_LIGHT0 e (0,0,0,1) per le altre
valori di luce speculare emessa dalla sorgente
-------------------------------------------------------------------------
GL_POSITION vettore di coordinate (x,y,z,w)
posizione della luce, w=1 per posizione in un punto
(x,y,z) al finito, w=0 per posizione all'infinito
nella direzione del vettore (x,y,z)
-------------------------------------------------------------------------
NOTA:
La posizione della luce e' soggetta alle trasformazioni come tutte le
primitive geometriche, in particolare e' soggetta alla matrice
GL_MODELVIEW corrente.
E' possibile ottenere luci che si spostano facendo precedere la
definizione della loro posizione da una qualche trasformazione.
Invece, trasformazioni poste dopo la definizione della luce
(ma prima della definizione degli oggetti della scena) non cambiano la
posizione della luce ma muovono solo la scena a luce ferma.
Modello di illuminazione
Il modello di illuminazione controlla
- una luce ambiente "di sfondo" presente indipendentemente dalle
sorgenti
- precisione/efficienza dell'algoritmo usato per calcolare
l'illuminazione: alcune funzionalita' sofisticate, necessarie per
illuminazione realistica di qualita', sono costose
computazionalmente e percio' sono disabilitate di default,
posso abilitarle nei casi in cui le ritengo necessarie
glLightModel(nome_parametro, valore_ parametro)
Anche questa funzione ha 4 versioni: glLightModelf,
glLightModeli, glLightModelfv, glLightModeliv.
nome_parametro valore
-------------------------------------------------------------------------
GL_LIGHT_MODEL_AMBIENT vettore di componenti RGBA della luce ambiente
"di sfondo" (default 0.2,0.2,0.2,1)
-------------------------------------------------------------------------
GL_LIGHT_MODEL_TWO_SIDE 0 oppure 1 (default = 0)
0 --> illumina solo front faces (in oggetti "chiusi" le back faces
non si vedono...)
-------------------------------------------------------------------------
GL_LIGHT_MODEL_LOCAL_VIEWER 0 oppure 1 (default = 0)
0 --> non tiene conto della posizione del punto di vista nel
calcolo delle riflessioni speculari (meno accurato ma piu' veloce)
-------------------------------------------------------------------------
Disegnamento di una scena illuminata
-
Abilitare l'illuminazione e tutte le luci che si intendono usare:
- glEnable(GL_LIGHTING); abilita il calcolo delle luci
- glEnable(GL_LIGHTi); abilita l'i-esima luce
Inoltre in presenza di illuminazione con luce diffusa puo' essere utile:
- glEnable(GL_NORMALIZE);
abilita normalizzazione automatica delle normali.
Consente di passare a glNormal vettori di norma non necessariamente = 1.
Utile se le primitive subiscono trasformazioni di scalatura dopo essere
state definite. Infatti anche le normali, come le ccordinate dei
vertici, sono "geometria", e subiscono le trasformazioni geometriche;
quindi, possono risultare non piu' "normali" dopo la trasformazione
-
Definire eventualmente i parametri modello di illuminazione
(se non vanno bene quelli di default):
glLightModel(...)
-
Definire i parametri di ciascuna luce che si intende usare:
glLight(GL_LIGHTi,....)
-
Definire il materiale di ciascuna primitiva che si vuole tracciare:
glMaterial(....)