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.

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:

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: Funzionamento di una libreria grafica 3D:

Scopo = visualizzare oggetti geometrici 3D su frame buffer (finestra 2D dello schermo).

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:

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

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:

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:

Tipi di primitive

Nota su quadrilateri e poligoni

OpenGL garantisce trattamento corretto solo per poligoni (e quadrilateri come caso particolare) semplici e convessi. 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. Esempi di attributi:

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.

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

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:

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.

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.

Per il momento consideriamo una situazione semplificata: In tal modo non e' necessario definire trasformazioni. Vedremo le trasformazioni in seguito.

Disegnamento della scena

  1. Abilitare funzioni particolari di OpenGL che non siano di default, es: eliminazione superfici nascoste (depth test), illuminazione...: glEnable(GL_DEPTH_TEST);...
  2. 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.
  3. 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:

    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

    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: Negli esempi riportati nel seguito di queste dispense si usa a volte la sintassi C, a volte la sintassi Java.