ULTIME COSE SU OPENGL ===================== - display list - traformazioni geometriche - illuminazione DISPLAY LIST In OpenGL una primitiva grafica e' definita da una lista di vertici racchiusi tra glBegin() e glEnd(), piu' eventualmente altre istruzioni (normali, colore...). In condizioni normali, una primitiva viene mandata dal client al server, che la visualizza immediatamente, e poi la "dimentica" (ovvero non la memorizza). Se il client vuole ridisegnare la stessa primitiva una seconda volta, deve riinviarla, con spesa di tempo. Una display list e' una serie di primitive di 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 primitive "fuori" da display list). Successivamente, il client puo' chiedere al server di ridisegnare le primitive contenute nella display list semplicemente inviando il nome della display list (un solo numero intero), risparmiando cosi' tempo. Una display list viene visualizzata usando i parametri dello 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) usando un'unica display list che viene scalata/traslata/colorata in modo diverso. Vederlo nell'esempio cubi.c 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); glEndList(); delimitano una definizione di display list. list_name e' il nome della lista che sto definendo. all'interno trovano posto le primitive OpenGL che compongono la lista. modo e' GL_COMPILE (la display list e' solo memorizzata, non visualizzata) oppure GL_COMPILE_AND_EXECUTE (e' memorizzata e visualizzata direttamente) glCallList(list_name); chiama la display list indicata (ovvero ne visualizza il contenuto). l'effetto della chiamata risente dello stato corrente OpenGL. Nota: Una display list viene visualizzata sotto l'influsso dello stato corrente al momento in cui chiamo glCallList(). Questo non vieta di modificare lo stato corrente dentro la display list stessa. Tenere pero' presente che all'uscita dalla display list trovero' lo stato modificato. Molto meglio cambiare lo stato localmente alla display list, usando glPush... e glPop... Vederlo nell'esempio cubi.c TRASFORMAZIONI GEOMETRICHE OpenGL compie questa sequenza (pipeline) di trasformazioni geometriche su ogni primitiva: +------------------------+ |[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 della viewport con glViewport 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: - MODELVIEW per le trasf. di modellazione e di vista (posizionare gli oggetti nella scena e la telecamera) - PROJECTION per la trasf. di proiezione (scegliere tipo di proiezione parallela 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: primitiva--> matr. MODELVIEW Mm--> matr. PROJECTION Mp--> visualizzazione (punti P) (Mm P) (Mp (Mm P) ) Vi sono primitive 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 avente coefficienti direttivi (rx,ry,rz), ang e' angolo di rotazione misurato in gradi glScalef(sx,sy,sz) -------> moltiplica con matrice di scalatura dei fattori 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. Esempi: 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: glTranslatef(xC,yC,zC); -3- glRotatef(ang,rx,ry,rz); -2- glTranslatef(-xC,-yC,-zC); -1- Le matrici MODELVIEW e PROJECTION fanno parte dello stato corrente. Cambiare la matrice corrente 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. glMatrixMode(GL_MODELVIEW); glPushMatrix(); <---- salva la matrice corrente su stack ... modifiche alla matrice ... ... disegnamento 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 ... 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 le corrispondenti funzioni OpenGL sono invocate PRIMA quelle per la trasformazione di vista e DOPO quelle per le trasformazioni di modellazione (in 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. Dimensiono (glScale), oriento (glRotate), posiziono (glTranslate) i miei oggetti per comporre la scena. Vederlo nell'esempio cubi.c TRASFORMAZIONE DI VISTA Agisce sull'intera scena per posizionarla davanti alla telecamera. Definita agendo sulla matrice MODELVIEW, senza Push e Pop. Oriento (glRotate), posiziono (glTranslate) la scena davanti alla telecamera (o la telecamera davanti alla scena). Oppure: uso la funzione di utilita' gluLookAt, che mi permette di definire la trasformazione di vista in modo piu' intuitivo. gluLookAt(eyex,eyey,eyez,centerx,centery,centerz,upx,upy,upz) porta la telecamera davanti alla scena, dove (eyex,eyey,eyez) = punto dove voglio collocare l'occhio, (centerx,centery,centerz) = centro della scena, verso il quale l'occhio viene rivolto, (upx,upy,upz) = direzione che definisce "l'alto" della telecamera. E' implementata internamente facendo uso di rotazioni e traslazioni. 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 (proiezione parallela). il volume di vista e' il parallelepipedo di diagonale (left,bottom,-near) - (right,top,-far). notare che per avere un volume di vista giacente davanti all'occhio, near e far devono essere positivi 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). near e far devono essere positivi. Nota: 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 dallo occhio (valori positivi) Nota: le trasformazioni di proiezione OpenGL (glOrtho e glFrustum) permettono di definire volumi di vista non simmetrici rispetto alla direzione dell'occhio (left e right possono essere diversi tra loro e idem top e bottom --> posso definire volumi di vista distorti), mentre gluPerspective definisce solo volumi di vista simmetrici. 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 (finestra sullo schermo). Di default viene usata 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 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); - o adeguo la trasformazione di proiezione: gluPerspective(fov,2.0,near,far); glViewport(0,0,400,200); [aspect ratio = 2.0 perche' dimensione x e' il doppio di quella y] ILLUMINAZIONE A luci disabilitate, ogni vertice ha il suo colore (assegnato con glColor), e ogni punto facente parte di una primitiva 1D (GL_LINES,...) o 2D (GL_TRIANGLES, GL_POLYGON,...) ha colore ottenuto interpolando quello dei vertici della primitiva. Ne segue che una primitiva ha un unico colore uniforme se i suoi vertici hanno tutti lo stesso colore. Quindi in 3D se tutte le facce di un solido hanno stesso colore, il solido appare come un'unica macchia di colore senza effetto di tridimensionalita'. Per questo negli esempi della volta scorsa (senza luci) usavamo colori diversi per facce diverse. A luce abilitata, ogni punto e' colorato con: - un colore che dipende dalla luce che lo colpisce (proprieta' della sorgente luminosa che emette la luce) e dalla capacita' di risposta alla luce della primitiva di cui fa parte (proprieta' del materiale di cui e' fatta la primitiva) - un'intensita' che dipende dalle due cose di cui sopra, ed eventualmente dall'inclinazione con cui la luce lo colpisce (angolo tra direzione della luce e direzione della normale alla primitiva in quel punto) Fattori che entrano in gioco: - Dalla parte delle primitive: Proprieta' di materiale (glMaterial) e normali (glNormal) specificate mentre definisco la primitiva - Dalla parte delle luci: Proprieta' delle sorgenti luminose (glLight) e modello di illuminazione (glLightModel) TIPI DI LUCE Luce ambiente: proviene da tutte le direzioni, viene riflessa da una primitiva in tutte le direzioni allo stesso modo. Provenendo da tutte le direzioni, colpisce con la stessa intensita' ogni punto indipendentemente dalla sua normale. Non utile a creare effetto 3D Luce diffusa: proviene da una direzione in particolare, viene riflessa in tutte le direzioni. Colpisce ogni punto con intensita' che dipende dall'angolo formato dalla direzione della luce con la normale in quel punto. E' quella che crea effetto 3D Luce speculare: proviene da una direzione in particolare, viene riflessa in una direzione in particolare. Crea effetti di "luccichio" tipo metalli o vetro Ogni sorgente luminosa contiene queste tre componenti di luce, e ciascuna componente puo' avere colore e intensita' diversa. Inoltre e' prevista una luce ambiente "di sfondo", indipendente dalle sorgenti luminose (attiva anche a sorgenti luminose spente). Ogni materiale ha una sensibilita' particolare a ciascuna delle tre componenti della luce. Inoltre esistono materiali in grado di emettere luce propria (luce emessa). 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 specificandole nella primitiva stessa. Assegnazione della normale corrente: glNormal3f() oppure glNormal3fv() ...varie forme analogamente a quanto visto per glVertex... Va chiamata tra glBegin e glEnd e influisce tutti i vertici che seguono fino a che la normale corrente non viene modificata di nuovo. 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' la normale e' uno di quegli attributi OpenGL che valgono "vertice per vertice" (come anche il colore). Posso 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 --> il poligono appare come una faccia piana - normali diverse --> il poligono assume 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). Normalizzazione automatica delle normali (funzionalita' di OpenGL): glEnable(GL_NORMALIZE); - consente di passare a glNormal vettori di norma non necessariamnete = 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 PROPRIETA' DI MATERIALE glMaterialf(,,); e glMateriali, glMaterialfv, glMaterialiv, dove f/i --> e' un float / un intero v --> e' un array di float / interi = GL_FRONT, GL_BACK o GL_FRONT_AND_BACK per assegnare proprieta' alle front faces, le back faces, o a entrambe e sono illustrati dalla seguente tabella: (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 RGB ------------------------------------------------------------------------- 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 ------------------------------------------------------------------------- OpenGL prevede primitive che "brillano di luce propria" definita dalla proprieta' GL_EMISSION. Nota: componenti RGB = 0,0,0 implicano luce "nera", ovvero nessuna luce (o nessuna componente di quel tipo nella luce). PROPRIETA' DELLE SORGENTI LUMINOSE Posso definire una o piu' sorgenti luminose e, dopo averle definite, abilitarle/disabilitarle (per default disabilitate). TIPI DI SORGENTI: - All'infinito (raggi luminosi paralleli): posizionate in punto (x,y,z,w) con w=0 - In un punto (raggi che escono dal punto in tutte le direzioni): posizionate in punto (x,y,z,w) con w=1 - Spot light (in un punto, con solo un cono di luce uscente): in un punto (x,y,z,w) con w=1, e con "angolo di apertura" limitato [noi NON ci occupiamo di spot light, chi fosse interessato puo' consultare il manuale] Nota: La posizione della luce e' soggetta alle trasformazioni come tutte le primitive geometriche, in particolare e' soggetta alla matrice 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. Definizione sorgenti: glLightf(,,); e glLighti, glLightfv, glLightiv, dove f/i,v con stesso funzionamento che in glMaterial. = GL_LIGHT0, GL_LIGHT1,... (OpenGL fornisce almento 7 luci) e sono illustrati dalla seguente tabella: (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) ------------------------------------------------------------------------- ... altri parametri per le luci spot (di cui non ci occupiamo) ... ------------------------------------------------------------------------- ... altri parametri per specificare come la luce e' attenuata con l'aumentare della distanza dalla sorgente (non ce ne occupiamo) ... ------------------------------------------------------------------------- MODELLO DI ILLUMINAZIONE glLightModelf(,) oppure glLightModeli, glLightModelfv, glLightModeliv ------------------------------------------------------------------------- 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) ------------------------------------------------------------------------- NOTA BENE In presenza di luci, il colore apparente di on oggetto e' determinato dal colore della luce che riflette, e apparentemente glColor non ha effetto. Si puo' usare l'istruzione glColorMaterial(,); per far si' che i paramatri di materiale seguano il valore attuale del colore assegnato con glColor ABILITAZIONI glEnable(GL_LIGHTING); per abilitare il calcolo delle luci glEnable(GL_LIGHTi); per abilitare l'i-esima luce glEnable(GL_COLOR_MATERIAL); per abilitare che il colore del materiale segua in ogni momento il colore corrente (se serve) COSE DA FARE PER OTTENERE UNA SCENA ILLUMINATA - Abilitare l'illuminazione e tutte le luci che si intendono usare, usando glEnable(GL_LIGHTING) e glEnable(GL_LIGHTi) per i=0... - Definire eventualmente i parametri modello di illuminazione, usando glLightModel (se non vanno bene quelli di default) - Definire i parametri di ciascuna luce che si intende usare, usando glLight(GL_LIGHTi,....) - Definire il materiale di ciascuna primitiva che si vuole tracciare, usando glMaterial(....). Nota: il materiale e' parte dello stato corrente di OpenGL e ha effetto su tutte le primitive che seguono a partire dal punto dove viene assegnato. Inoltre e' di quegli attributi che POSSONO essere assegnati tra glBegin e glEnd (e' proprieta' del singolo vertice).