LA LIBRERIA GRAFICA OPENGL - I Parte
Libreria grafica:
Pacchetto software per lo sviluppo di applicazioni grafiche.
API (Application Program Interface) composta da un insieme
di funzioni e procedure delle quali esiste una precisa specifica.
- indipendenza da periferiche di input e di output
- portabilita' simile a quella dei linguaggi di programmazione
Note storiche
Librerie per grafica bidimensionale
Prime librerie grafiche erano bidimensionali.
Modello concettuale alla base: modello del pen plotter.
Un pen plotter produce immagini muovendo la penna sul foglio
in due direzioni ortogonali.
La penna puo' essere alzata (e allora non scrive mentre la muovo)
o abbassata (e allora scrive) per creare l'immagine desiderata.
Esempi: LOGO, GKS, PostScript.
In questi sistemi grafici si hanno due funzioni primitive:
- MOVETO(x,y) : muove la penna alla locazione (x,y)
senza lasciare un segno
- LINETO(x,y) : muove la penna alla locazione (x,y)
e traccia una linea dalla vecchia locazione ad (x,y)
Esempio:
MOVETO(0,0)
LINETO(1,0)
LINETO(1,1)
LINETO(0,1)
LINETO(0,0)

Posso anche tracciare archi di curve, chiudere linee e riempire la
regione delimitata...
Tali sistemi vanno bene per applicazioni specifiche (descrizione
del layout di una pagina: PostScript).
Diventano complessi quando si voglia disegnare un oggetto
tridimensionale.
Nota: grafica 2D e' la grafica di X Window (in Xlib), e la
grafica disponibile in Java.
Librerie per grafica tridimensionale
Forniscono funzionalita' relative a:
- primitive geometriche:
definiscono le entita' atomiche che il sistema puo' visualizzare
(punti, segmenti, poligoni, pixel...)
- attributi delle primitive:
definiscono come appaiono le diverse primitive (es: colore,
tipo di riempimento per un poligono)
- trasformazioni geometriche:
permettono di eseguire trasformazioni sugli oggetti
(rotazioni, traslazioni, scalature)
- visualizzazione (viewing):
consentono di descrivere la posizione, l'orientamento e gli
altri parametri della telecamera
- input:
permettono all'utente di interagire con le diverse forme di input
(da tastiera, mouse e tavoletta)
- controllo:
per inizializzazione e trattamento degli errori, per dialogo
con il sistema a finestre
Funzionamento di una libreria grafica 3D:

Scopo = visualizzare oggetti geometrici 3D su frame buffer
(finestra 2D dello schermo).
-
Mediante le primitive grafiche si definisce
la geometria degli oggetti che compongono la scena.
-
Gli oggetti vengono posizionati nello spazio tridimensionale e si
sceglie l'inquadratura con cui visualizzare la scena.
-
Per ogni primitiva si calcolano le informazioni necessarie a
stabilire la colorazione: si possono considerare o meno
illuminazione, shading, trasparenza, tessitura ecc.
-
Gli oggetti vengono proiettati su uno schermo bidimensionale e
frammentati in pixel.
Il colore di ogni pixel e' stabilito utilizzando le informazioni su
geometria e colorazione della scena.
OPEN GL (Open Graphics Library)
Interfaccia software verso l'hardware grafico che consiste di un
insieme di diverse centinaia di funzioni e procedure.
Progettata per rendering in tempo reale e sviluppata (GL) per
Silicon Graphics.
Organizzazione della libreria per sistema X:

- GL: libreria di base, contiene codice per oggetti geometrici
primitivi, attributi, trasformazioni, colore, illuminazione...
- GLU (Graphics Library Utilities):
contiene codice per oggetti non primitivi di uso comune
(es. sfere), ed altre funzioni di utilita', implementate
su GL
- GLX: estensione OpenGL al sistema X,
fornisce un mezzo per creare un contesto OpenGL
e associarlo con una finestra per una macchina che usa il
sistema X (interfaccia a basso livello con il
sistema X)
- GLUT (Graphics Library Utility Toolkit):
funzionalita' minimali per dotare un programma che fa
grafica in OpenGL di un'interfaccia utente
in un ambiente a finestre
(= interfaccia con il sistema a finestre)
Una organizzazione simile vale per l'ambiente Microsoft Windows.
Le funzioni GL / GLU / GLX / GLUT iniziano con "gl" / "glu" / "glx" /
"glut". Noi non vedremo ne' GLX ne' Glut perche' useremo invece
OpenGL da Java.
Concetti di base
- Primitive geometriche = quello che viene disegnato
(es: punti, linee, poligoni)
- Attributi = con che aspetto viene disegnato
(es: dimensione di punto, spessore di linea, colore)
- Trasformazioni geometriche
- di modellazione = come le primitive geometriche
si collocano in uno spazio comune per formare una scena
- di vista = da che punto di vista si guarda la scena
- di proiezione = con che tipo di obiettivo la si guarda
(es: proiezione parallela o prospettica)
- Funzionalita' = operazioni aggiuntive per realizzare
immagini realistiche (es: eliminazione delle parti nascoste
di una scena, illuminazione...)
Primitive geometriche
Libreria di base (GL) contiene un insieme ristretto di primitive
geometriche.
Libreria GLU contiene un insieme piu' ricco composto a
partire dalle primitive semplici di GL.
Primitive = oggetti geometrici da visualizzare.
Una primitiva e' specificata da:
- lista di vertici, che sono l'informazione geometrica
- tipo di primitiva che stabilisce come tali vertici
devono essere connessi per formare l'oggetto (informazione topologica)
Ciascun vertice e' specificato dandone le coordinate
in un sistema di riferimento locale alla primitiva.
Come si definisce una primitiva
glBegin(tipo primitiva);
glVertex(...) per ogni vertice che forma la primitiva
glEnd();
La funzione glVertex ha varie forme per permettere di utilizzare
argomenti di vario tipo, es:
-
glvertex2f(due float); glvertex2fv(array di due float);
per vertici specificati in 2D (implicitamente z=0)
-
glvertex3f(tre float); glvertex3fv(array di tre float);
per vertici specificati in 3D
Tipi di primitive
- GL_POINTS
L'oggetto e' un insieme di punti sparsi.
Ogni vertice e' preso singolarmente.
- GL_LINES
L'oggetto e' un insieme di segmenti di retta. Ogni
coppia di vertici consecutivi della lista definisce un segmento.
- GL_LINE_STRIP
L'oggetto e' una spezzata poligonale aperta.
I vertici della lista sono connessi in catena.
- GL_LINE_LOOP
L'oggetto e' una spezzata poligonale chiusa.
Come sopra ma l'ultimo vertice e' connesso al primo.
- GL_TRIANGLES
L'oggetto e' un insieme di triangoli.
Ogni tre vertici consecutivi della lista definiscono un triangolo.
- GL_TRIANGLE_FAN
L'oggetto e' un ventaglio di triangoli che condividono un
vertice. I primi tre vertici formano il primo triangolo, ogni
nuovo vertice forma un triangolo con il vertice precedente ed il
primo vertice della lista.
- GL_TRIANGLE_STRIP
L'oggetto e' una striscia di triangoli dove due triangoli
consecutivi hanno in comune un lato. I primi tre vertici
formano il primo triangolo, ogni nuovo vertice forma un
triangolo con i due vertici immediatamente precedenti.
- GL_QUADS
L'oggetto e' un insieme di quadrilateri. Ogni
quattro vertici consecutivi della lista definiscono un quadrilatero.
- GL_QUAD_STRIP
L'oggetto e' una striscia di quadrilateri
ciascuno dei quali ha un lato in comune con il quadrilatero precedente.
I primi 4 vertici formano il primo quadrilatero, ogni due nuovi vertici
formano un quadrilatero coi precedenti due.

- GL_POLYGON
L'oggetto e' un poligono. La lista di vertici specifica
la spezzata poligonale di contorno del poligono.
In un poligono i lati sono gli stessi che avrei con GL_LINE_LOOP.
Nota su quadrilateri e poligoni
OpenGL garantisce trattamento corretto solo per poligoni (e quadrilateri
come caso particolare) semplici e convessi.
-
Un poligono e' detto semplice se i suoi lati si intersecano
al piu' nei vertici estremi.
-
Un poligono e' detto convesso se dati due punti interni
qualunque P1 e P2 il segmento P1P2 e' interno al poligono.
Alcune configurazioni di vertici per GL_QUADS, GL_QUAD_STRIP e
GL_POLYGON possono dare luogo a poligoni intrecciati o non convessi
e quindi a risultati errati.
Esempio
Questo definisce un triangolo giacente sul piano z=0:
glBegin(GL_TRIANGLES);
glVertex2f(-1.0,0.0);
glVertex2f(1.0,0.0);
glVertex2f(0.0,1.0);
glEnd();
Primitive per oggetti curvi
Le primitive geometriche di base (appena viste)
sono contenute nella libreria GL.
Con tali primitive si possono creare oggetti a facce piane.
Oggetti a facce curve si realizzano approssimandoli mediante oggetti piani.
La libreria GLU contiene primitive corrispondenti ad oggetti
piu' complessi, anche a facce curve (es: sfera, cilindro), che sono
implementate internamente sulla base delle primitive di GL.
Esempio:
gluSphere(aux ,raggio, numero_fette, numero_strati)
dove aux e' un puntatore ad un oggetto ausiliario
usato da OpenGL,
la sfera viene approssimata mediante poligoni dividendola
verticalmente in fette ed orizzontalmente in strati,
controllo la precisione
dell'approssimazione specificando quante fette e strati voglio usare.
GLU fornisce inoltre supporto alla definizione di oggetti a facce curve
(es: NURBS) che saranno automaticamente trasformati in oggetti
a facce piane per la visualizzazione.
Attributi delle primitive geometriche
Attributi = parametri che influenzano il
modo in cui le primitive sono rese.
- primitiva dice che cosa visualizzare
- attributi dicono come visualizzarlo
Esempi di attributi:
-
Per i punti:
POINT SIZE = grandezza
-
Per le linee:
LINE WIDTH = spessore
-
Per i poligoni e le altre primitive con area:
POLYGON MODE = modalita' di rendering (disegnare l'area piena,
solo i lati di contorno, solo i vertici)
Nello spazio un poligono ha due facce (front e back), posso assegnare
una modalita' di visualizzazione diversa nelle due facce.
-
Per tutte le primitive: colore, materiale, vettore normale
(usati per determinare la reazione della primitiva alla luce)
Attributi come parametri di stato
OpenGL ha uno stato corrente che contiene i valori
degli attributi.
L'aspetto di una primitiva geometrica dipende dal valore
corrente degli attributi al momento in cui viene visualizzata.
I valori degli attributi nello stato corrente si possono
impostare tramite comandi.
Una volta che e' stato impostato un certo valore per un attributo,
questo influisce sul modo in cui sono
disegnate tutte le primitive che seguono fino a che
il valore non viene nuovamente cambiato.
Esempio: se si imposta il colore rosso, si continua a
disegnare in rosso finche' lo stato non cambia.
Le primitive precedenti non sono influenzate.
Attributi delle primitive e attributi dei vertici
Attributi delle primitive
Attributi che influiscono su una primitiva nel suo complesso
(point size, line width, poligon mode).
Vanno assegnati al di fuori della primitiva (= fuori da glBegin/glEnd).
Vanno assegnati prima della primitiva,
in modo che al momento in cui si incontra la primitiva i
valori siano quelli voluti.
-
glPointSize(valore);
assegna grandezza dei punti in pixel, default = 1.0.
-
glLineWidth(valore);
assegna spessore delle linee in pixel, default = 1.0.
-
glPolygonMode(faccia,modo);
assegna il modo in cui sono disegnate le primitive con area
(triangoli, strisce e ventagli di triangoli, quadrilateri,
strisce di quadrilateri, poligoni).
Il modo e' uno tra:
- GL_POINT: vengono disegnati solo i vertici
- GL_LINE: vengono disegnati solo i lati
- GL_FILL: viene disegnata tutta l'area
La faccia indica a quale delle due facce definite dalla
primitiva si applica il modo indicato:
- GL_FRONT = la faccia al "dritto"
- GL_BACK = la faccia al "rovescio"
- GL_FRONT_AND_BACK = entrambe
La faccia "al dritto" di un poligono e' quella per la quale i vertici
del poligono sono dati in senso antiorario.
Attributi dei vertici
Attributi che agiscono "vertice per vertice" anche all'interno della
stessa primitiva (colore, materiale, normale...).
Possono essere cambiati all'interno di una coppia glBegin/glEnd.
Possono anche essere assegnati fuori (prima della primitiva) se
l'attributo assume lo stesso valore per tutti i vertici della primitiva.
Se i vertici di una primitiva hanno valori diversi dell'attributo,
il valore dell'attributo in un punto interno alla primitiva viene
interpolato ed assume un valore ottenuto come media dei valori
nei vertici, pesata rispetto alla distanza del punto dai vertici.
Esempio: un segmento con un vertice rosso e uno giallo assumera'
al suo interno toni di arancio degradanti dal rosso verso il giallo).
Definizione di primitiva con attributi
Si devono scrivere nell'ordine:
- comandi per assegnare attributi globali della primitiva
- glBegin con tipo della primitiva
- lista di vertici ed attributi vertice-per-vertice
- glEnd
Esempio che disegna un triangolo con colore rosso, verde, blu nei tre
vertici, e sfumato di conseguenza all'interno:
glBegin(GL_TRIANGLES);
glColor3f(1.0, 0.0, 0.0); /* assegna colore corrente = rosso */
glVertex2f(-1.0,0.0);
glColor3f(0.0, 1.0, 0.0); /* assegna colore corrente = verde */
glVertex2f(1.0,0.0);
glColor3f(0.0, 0.0, 1.0); /* assegna colore corrente = blu */
glVertex2f(0.0,1.0);
glEnd();
Stack degli attributi
Esiste il modo di cambiare temporaneamente il valore di un
attributo e poi ripristinare lo stato precedente.
Esempio: cambiare il colore corrente in rosso,
disegnare qualcosa in rosso e poi ripristinare il colore precedente
qualunque esso sia.
Se voglio modificare il valore di un attributo momentaneamente per
disegnare una certa primitiva, salvando il valore precedente per poi
ripristinarlo, uso glPushAttrib e glPopAttrib.
Esempio che disegna un triangolo in rosso, e anche tutto cio' che disegno
in seguito sara' rosso (se non cambio colore nel frattempo):
glColor3f(1.0, 0.0, 0.0); /* assegna colore corrente = rosso */
glBegin(GL_TRIANGLES);
glVertex2f(-1.0,0.0);
glVertex2f(1.0,0.0);
glVertex2f(0.0,1.0);
glEnd();
Esempio che disegna un triangolo in rosso e poi ripristina il colore
precedente (cio' che disegno dopo sara' del
colore che avevo prima di assegnare il rosso):
glPushAttrib(GL_CURRENT_BIT); /* salva colore corrente su stack */
glColor3f(1.0, 0.0, 0.0); /* assegna colore corrente = rosso */
glBegin(GL_TRIANGLES);
glVertex2f(-1.0,0.0);
glVertex2f(1.0,0.0);
glVertex2f(0.0,1.0);
glEnd();
glPopAttrib(); /* ripristina da stack il colore precedente */
Colore
Il colore in computer graphics e' basato sulla
teoria dei tre colori.
Modello R G B
Modello di colore additivo in cui si considerano tre colori primari
(rosso, verde, blu) che sono mischiati per formare il colore desiderato.
Un colore C e' definito da C = T1 R + T2 G + T3 B dove
R = red, G = green, B = blue, e Ti = intensita' di colore.
Possiamo vedere un colore come un punto nel cubo dei colori:

Tutti i colori sulla diagonale = toni di grigio.
Per definire un colore si
specificano le componenti di R, G e B,
in OpenGL come numeri fra 0.0 e 1.0, dove
- 0.0 denota assenza del colore primario corrispondente
- 1.0 denota massima intensita' del colore primario corrispondente
Esempi: la terna (1.0,0.0,0.0) denota il rosso, (1.0,1.0,0.0) il giallo.
In altri sistemi le componenti R G B sono date come numeri fra 0 e 255.
Modello R G B A
Modello a 4 colori (RGBA) estensione del modello a tre colori.
A e' detto alpha channel ed il suo valore
corrisponde a livello di opacita' o trasparenza:
- 0.0 = trasparente
- 1.0 = opaco
Un oggetto opaco non ha passare la luce, un oggetto trasparente
fa passare tutta la luce.
Modalita' color-index
Specificare un colore fornendo, anziche' una terna RGB, un indice intero
in una "tavolozza" (look-up table del colore)
messa a disposizione dal window management system.
Ogni indice corrisponde ad una riga della tabella che contiene
una terna RGB predefinita.
Quando e' utile
Supponiamo che la profondita' del frame buffer sia 3k bit per pixel.
In modalita' RGB posso specificare 2^k rossi, 2^k verdi, 2^k blu, e per
combinazione si ottengono 2^(3k) colori diversi.
Questi colori sono "distribuiti uniformemente" sulla gamma di colori
possibili.
Supponiamo che un'applicazione usi solo una certa sotto-gamma
di colori (es: un tramonto usa colori con molto rosso, qualche
verde, poco blu), ma su quella gamma voglia una precisione maggiore
di quella offerta dai k bit di profondita' che sono
disponibili per ogni colore.
In modalita' color-index, i 3k bit di profondita' del frame buffer
vengono usati come indici
nella tabella (look-up table) e non come valori di colore RGB.
Con 3k bit per il colore, posso indirizzare una tabella
di 2^(3k) colori diversi, scelti a seconda delle mie necessita',
invece che uniformemente distribuiti per componenti R,G e B.
Importante nel caso il frame buffer abbia una profondita' limitata.
Supponiamo che la profondita' del frame buffer sia K bit per pixel
(attenzione: rispetto al k di prima, qui K=3k).
Posso indirizzare una look-up table di 2^K campi righe, ciascuna
contenente la specifica di un colore in formato RGB.
Supponiamo di poter visualizzare colori con accuratezza di m bit.
Disponiamo di 2^m rossi, 2^m verdi e 2^m blu.
Possiamo produrre 2^3m colori diversi sul display.
Di questi, 2^K sono scelti per essere messi nella tavolozza e
associati agli indici.
La lookup table ha 2^K righe e 3 colonne ciascuna di m bit.
Dimensione totale 3m 2^K bit.
Esempio: con K=8, m=0 si hanno 256 righe nella look-up table con
possibilita' di scegliere 256 fra 2^24 (= circa 16 mila) colori.
In modalita' RGB avremmo bisogno di 24 bit per pixel.
Per inizializzare e modificare il contenuto della look-up table
bisogna interagire con il sistema a finestre.
Testo
OpenGL supporta due forme: STROKE e RASTER.
-
STROKE: caratteri sono costruiti con primitive grafiche.
Puo' essere definito al livello di dettaglio di qualsiasi oggetto.
Puo' essere manipolato con le trasformazioni geometriche e visualizzato
come le primitive geometriche.
-
RASTER: caratteri sono definiti da rettangoli di bit detti bit-blocks.
Un carattere raster viene messo direttamente nel frame buffer mediante
una operazione di bit-block-transfer (bitblkt), che muove il
blocco di bit usando una singola istruzione.
Semplice e veloce.
Trasformazioni geometriche
Le primitive OpenGL sono specificate come oggetti solidi 3D immersi
in un loro proprio sistema di coordinate 3D.
Queste primitive saranno visualizzate come immagine 2D nel sistema di
coordinate 2D della canvas.
Il passaggio da 3D a 2D viene fatto da OpenGL attraverso
una serie di trasformazioni di coordinate.
-
Trasformazioni di modellazione e di vista.
Per comporre la scena (collocare i vari oggetti,
definiti in un sistema di riferimento locale,
inserendoli in un sistema di riferimento comune),
e per porre la scena davanti alla telecamera
-
Trasformazioni di proiezione.
Definiscono il tipo di proiezione (parallela o prospettica)
e il volume di vista (quanta parte di spazio viene inquadrata
dalla telecamera)
-
Trasformazioni di viewport.
Per mappare il tutto nella finestra 2D
Per il momento consideriamo una situazione semplificata:
- Come trasformazioni di vista e proiezione
usiamo quelle di default, ovvero:
- proiezione e' parallela, punto di vista sta sull'asse z e
guarda in giu' lungo l'asse z
- volume di vista e' un cubo con x,y,z tra -1 e +1
- il tutto viene mappato nell'intera finestra grafica (cio' implica
una deformazione se la finestra non e' quadrata)
- Evitiamo le trasformazioni di modellazione, usando
una scena composta di oggetti
gia' definiti in uno spazio di riferimento comune, e contenuti
nel volume di vista di default (cioe' il cubo -1 +1)
In tal modo non e' necessario definire trasformazioni. Vedremo le
trasformazioni in seguito.
Disegnamento della scena
- Abilitare funzioni particolari di OpenGL che non siano di default,
es: eliminazione superfici nascoste (depth test), illuminazione...:
glEnable(GL_DEPTH_TEST);...
- Pulire il foglio prima di mettersi a disegnare:
glClear(GL_COLOR_BUFFER_BIT);
oppure
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
se ho abilitato depth test.
- Disegnare:
definizioni di primitive tra glBegin/glEnd, assegnazioni di attributi
Il punto 1 in genere puo' essere fatto una volta sola all'inizio.
I punti 2 e 3 vanno ripetuti ogni volta che occorre ridisegnare la finestra.
Usare OpenGL in Java
Magician e' un'interfaccia Java verso OpenGL, ossia un insieme
di classi Java che consentono di usare le librerie OpenGL
(scritte in C) all'interno di un programma Java.
Java ha un meccanismo che permette l'uso di librerie
scritte in C e compilate per la particolare macchina
(dette "librerie native").
I programmi Java risultanti possono essere eseguiti su
qualsiasi macchina purche' su quella macchina siano presenti
le librerie native:
- sotto Windows devo avere le librerie native windows
- sotto Linux devo avere le librerie native linux
Le librerie OpenGL native eseguono il lavoro.
Magician offre una serie di classi che inscatolano
tali librerie: nascondono la loro natura C e danno
loro un'apparenza Java.
Architettura
+-------------------------+
| Classi Java |
|com.hermetica.magician.* |
+------------+------------+
|
+-------------+-------------+
| strato SW di collegamento |
+-------------+-------------+
|
+------------+------------+
| librerie OpenGL native |
+-------------------------+
Classi Magician
-
CoreGL, CoreGLU
contengono nella loro interfaccia le funzioni OpenGL
(GL e GLU) e le implementano mediante chiamate a librerie
OpenGL native della macchina.
-
glComponent
componente di interfaccia che realizza una finestra grafica
OpenGL
-
ad una glComponent devo associare
GLCapabilities che ne specificano le proprieta'
(es. numero bit usati per color buffer, depth buffer,
presenza o meno di double buffer)
-
Ad una glComponent devo associare un GLEventListener
come la finestra si deve comportare per:
- inizializzarsi
- disegnare il suo contenuto
- aggiustare il contenuto quando vengono modificate le sue dimensioni
Tutto il codice OpenGL trova posto dentro le funzioni del
GLEventListener.
Esempio di programma Java che usa OpenGL
Il programma BasicGL.java realizza un frame con
dentro una glcomponent che non disegna nulla.
Vediamo la struttura:
Differenze sintattiche
La sintassi di OpenGL usato in Java tramite Magician e' molto
simile a quella di OpenGL usato in C.
Differenze principali:
- funzioni di GL si invocano tramite un oggetto di classe
CoreGL, esempio:
chiamata C glBegin(...) diventa
chiamata Java gl.glBegin(...) se gl e' il
mio oggetto di classe CoreGL, ed analogo per
tutte le altre funzioni
- analogamente funzioni di GLU si invocano tramite un oggetto di classe
CoreGLU
- costanti GL sono costanti della classe
GL, esempio: GL_POINTS diventa
GL.GL_POINTS
Negli esempi riportati nel seguito di queste dispense si usa a volte
la sintassi C, a volte la sintassi Java.