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: 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.

Per modificare la matrice corrente, OpenGL fornisce funzioni che permettono di definire una trasformazione complessa come composizione di trasformazioni elementari. Le trasformazioni elementari sono: 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:
  1. traslo di (-xC,-yC,-zC) per portare C nell'origine del nuovo riferimento
  2. ruoto dell'angolo alpha voluto attorno a origine
  3. traslo di (xC,yC,zC) per portare C alla posizione originale
Nel codice:

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. 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:

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:

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

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:

Esempio

La finestra iniziale ha dimensione 400x400 pixel, le trasformazioni iniziali sono: Aspect ratio = 1.0 perche' finestra quadrata.

Se la finestra viene deformata a 400x200

Disegnamento della scena OpenGL

(Vedere il file terra.c).
  1. Abilitare le funzioni di OpenGL non di default con glEnable(...)
  2. Pulire il foglio con glClear(...)
  3. Definizione delle trasformazioni di proiezione:
  4. Definizione delle trasformazioni di vista:
  5. Disegno:

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).

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

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
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:

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.

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);
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
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