Skip to content Skip to navigation

OpenStax_CNX

You are here: Home » Content » Composizione Grafica in Processing

Navigation

Recently Viewed

This feature requires Javascript to be enabled.
 

Composizione Grafica in Processing

Module by: Davide Rocchesso. E-mail the author

Summary: Elementi di composizione grafica 2D e 3D (curve incluse) per l'ambiente e linguaggio Processing

Primitive grafiche

In Processing si possono disporre punti, linee, superfici, e volumi (cioè oggetti geometrici a 0, 1, 2, o 3 dimensioni) in uno spazio tridimensionale ovvero, laddove la cosa abbia un senso, nello spazio bidimensionale della finestra immagine. Il numero di parametri delle primitive corrispondenti determinerà se tali oggetti debbano essere collocati nello spazio X-Y o nello spazio X-Y-Z.

0D

Punti

La point() è la più semplice delle primitive grafiche. Se invocata con due coordinate, colloca un punto nello spazio X-Y. Se invocata con tre coordinate, colloca il punto nello spazio X-Y-Z. Un punto, in senso geometrico, non ha dimensione, ma ad esso può essere associata una occupazione in pixel ed un colore, mediante le funzioni strokeWeight() e stroke(), rispettivamente. Ad esempio, il semplicissimo sketch Processing


	  stroke(180,0,0);
	  strokeWeight(10);
	  point(60,60);
	
disegna un pallino nella finestra immagine.

Collezioni di punti

Un insieme di punti può essere raggruppato in un unico oggetto (nuvola di punti) mediante i due delimitatori beginShape(POINTS) e endShape(), tra i quali ogni punto va specificato con la funzione vertex(). Trasformazioni di rotazione, traslazione, o scalamento non si applicano internamente agli oggetti compositi descritti da beginShape() e endShape(), ma possono precedere la definizione di un composito e applicarsi all'insieme.

1D

Rette

La line() traccia un segmento di retta tra due punti del piano o dello spazio, con spessore e colore eventualmente impostati tramite strokeWeight() e stroke(), rispettivamente.

Collezioni di segmenti

Un insieme di segmenti può essere definito, analogamente a quanto visto per i punti, mediante i delimitatori beginShape() e endShape(), tra i quali si elencano gli estremi dei segmenti mediante la funzione vertex(). Usando la invocazione beginShape(LINES) i vertici vengono presi a coppie, ciascuna identificante un segmento, mentre con la invocazione senza argomenti beginShape() i vertici, presi uno dopo l'altro, definiscono una spezzata. Chiudendo con endShape(CLOSE) la spezzata viene chiusa su se stessa mediante collegamento del primo e dell'ultimo vertice. Il colore del poligono così ottenuto può essere impostato mediante la fill(), ovvero lasciato uguale a quello dello sfondo con la noFill().

Curve

La funzione curve(), invocata con otto parametri, traccia una curva sul piano immagine, con punto iniziale e finale determinati, rispettivamente, dalle seconda e dalla terza coppia di coordinate passate come argomenti. Le due coppie di coordinate iniziale e finale definiscono due punti di controllo della curva tracciata, la quale è una spline interpolante che passa per tutti e quattro i punti. In Processing, tuttavia, viene visualizzato solo il segmento di curva compreso tra i due punti intermedi.

Definition 1: Spline
Curva polinomiale a tratti, con polinomi connessi con continuità ai nodi (knot)
Nota:
Si veda Introduction to Splines e, per una introduzione al tipo specifico di spline (Catmull-Rom) usato in processing, la voce spline in Wikipedia.
Per avere un numero arbitrario di punti di controllo bisogna usare la funzione curveVertex() per specificare ciascun punto all'interno di un blocco delimitato da beginShape() e endShape().

A differenza della curve(), nella funzione bezier() i due punti di controllo specificati dai quattro parametri intermedi non sono punti attraversati dalla curva. Essi servono solo a definire la forma della curva approssimante di Bézier, la quale ha le seguenti proprietà notevoli:

  • è interamente contenuta nell'inviluppo convesso definito dai punti estremali e dai punti di controllo;
  • trasformazioni di traslazione, rotazione, o scalamento applicate ai punti estremali e di controllo determinano una identica trasformazione della curva.
Come si vede eseguendo il codice
stroke(255, 0, 0); 
line(93, 40, 10, 10); 
line(90, 90, 15, 80); 
stroke(0, 0, 0); 
noFill();
bezier(93, 40, 10, 10, 90, 90, 15, 80);
	
i punti di controllo stanno sulla tangente che passa per gli estremi della curva. Per avere un numero arbitrario di punti di controllo bisogna usare la funzione bezierVertex() per specificare ciascun punto all'interno di un blocco delimitato da beginShape() e endShape(). In questo modo si può tracciare una curva arbitrariamente involuta in uno spazio a tre dimensioni. In due dimensioni, la bezierVertex() ha sei parametri che corrispondono alle coordinate di due punti di controllo e di un punto di ancoraggio. La prima invocazione di bezierVertex() va preceduta da una invocazione di vertex() che stabilisce il primo punto di ancoraggio della curva.

Ci sono ulteriori metodi che consentono di leggere le coordinate o la direzione della tangente in un punto qualsiasi lungo la curva di Bézier o spline interpolante, stabilito mediante un parametro t t che può andare da 0 0 (primo estremo) a 1 1 (secondo estremo). E' anche possibile impostare la precisione di tracciamento di curve approssimanti e interpolanti in 3D. Per i dettagli si rimanda al manuale di riferimento di Processing.

Lo sketch processing di tabella illustra la differenza tra curva interpolante spline e curva di Bézier.

Nota:

Si veda la voce Bézier curve in Wikipedia.
Tabella 1
applet che confronta curva di Bézier (rossa) e spline interpolante (nera)

            
                
void setup() {
  c1x = 120;
  c1y = 110;
  c2x = 50;
  c2y = 70;
  background(200);
  stroke(0,0,0);
  size(200, 200);
}

int D1, D2;
int X, Y;
int c1x, c1y, c2x, c2y;

void draw() {
  if (mousePressed == true) {
    X = mouseX; Y = mouseY;
    // selezione del punto che si modifica
    D1 = (X - c1x)*(X - c1x) + (Y - c1y)*(Y - c1y); 
    D2 = (X - c2x)*(X - c2x) + (Y - c2y)*(Y - c2y);
    if (D1 < D2) {
    c1x = X; c1y = Y;
    }
    else {
     c2x = X; c2y = Y;
     }
    }
    background(200);
    stroke(0,0,0);
    strokeWeight(1);
    
    noFill();
    beginShape(); 
    curveVertex(10,  10); 
    curveVertex(10,  10); 
    curveVertex(c2x, c2y); 
    curveVertex(c1x, c1y); 
    curveVertex(190, 190); 
    curveVertex(190, 190); 
    endShape(); 
       
    stroke(255,30,0);
    bezier(10,10,c2x,c2y,c1x,c1y,190,190);
    strokeWeight(4);
    point(c1x,c1y);
    point(c2x,c2y);
}
                   	      
	    

2D

Nota:

Le figure in due o tre dimensioni assumono un colore che può essere determinato dalla illuminazione, come spiegato nella Sezione 7, oppure stabilito mediante il metodo fill(), il quale prevede anche la possibilità di impostare la trasparenza.

Triangoli

Il triangolo è l'elemento costruttivo fondamentale per la grafica 3D, in quanto mediante giustapposizione di triangoli si approssimano superfici continue. In Processing, però, i triangoli sono elementi specificati nel 2D mediante la primitiva triangle(), la quale ha sei parametri corrispondenti alle coordinate dei tre vertici nella finestra immagine. Pur essendo definito nel 2D, ogni triangolo può essere soggetto a trasformazioni di rotazione e traslazione all'interno dello spazio 3D, come avviene nello sketch Processing

 
	void setup(){ 
	    size(200, 200, P3D); 
	    fill(210, 20, 20);
	    }

	float angle = 0;

	void draw(){
	    background(200); // clear image
	    stroke(0,0,0);
	    angle += 0.005;
	    rotateX(angle);
	    triangle(10,10,30,70,80,80);
	}	    

	

Collezioni di triangoli

Un insieme di triangoli può essere definito, analogamente a quanto visto per i punti e segmenti, mediante i delimitatori beginShape() e endShape(), tra i quali si elencano i vertici dei triangoli mediante la funzione vertex(). Usando la invocazione beginShape(TRIANGLES) i vertici vengono presi a terne, ciascuna identificante un triangolo, mentre con la invocazione beginShape(TRIANGLE_STRIP) i vertici, presi uno dopo l'altro, definiscono una striscia di superficie a facce triangolari. Se le vertex() hanno tre argomenti, i vertici vengono collocati nello spazio 3D e i corrispondenti triangoli individuano superfici planari nello spazio.

Quadrilateri

I rettangoli si definiscono, in Processing, mediante la funzione rect() di quattro parametri, in cui i primi due specificano, per default, la posizione nel piano 2D dell'angolo in alto a sinistra, e il terzo e quarto specificano, rispettivamente, la larghezza e la altezza. Il significato dei primi due parametri si può cambiare con la funzione rectMode(): rectMode(CORNER) corrisponde al posizionamento di default; rectMode(CENTER) corrisponde al posizionamento del centro del rettangolo nel punto specificato dalle prime due coordinate; rectMode(CORNERS) fa in modo che i quattro parametri vengano interpretati come le coordinate del vertice in alto a sinistra e del vertice in basso a destra. Un quadrilatero generico si definisce mediante le coordinate dei suoi quattro vertici, passate come parametri alla funzione quad(). E' importante notare che in 3D, mentre un triangolo rimane in ogni caso planare, una quadrupla di punti può dare luogo ad una superficie curva. Viceversa, i quadrilateri definiti mediante roto-traslazioni 3D di quadruple di vertici 2D rimangono planari. Processing permette di passare solo otto parametri alla quad(), in tal modo forzando la definizione del quadrilatero mediante una quadrupla di vertici 2D.

Collezioni di quadrilateri

Un insieme di quadrilateri può essere definito, analogamente a quanto visto per i triangoli, mediante i delimitatori beginShape() e endShape(), tra i quali si elencano i vertici dei quadrilateri mediante la funzione vertex(). Usando la invocazione beginShape(QUADS) i vertici vengono presi a quadruple, ciascuna identificante un quadrilatero, mentre con la invocazione beginShape(QUAD_STRIP) i vertici, presi uno dopo l'altro, definiscono una striscia di superficie a facce quadrilatere. Se i vertex() usati hanno tre parametri, non è garantita la planarità delle facce risultanti, e quindi la resa grafica può essere fuorviante. Ad esempio, eseguendo il codice


	    size(200,200,P3D);
	    lights();
	    beginShape(QUADS);
	    vertex(20,31, 33);
	    vertex(80, 40, 38);
	    vertex(75, 88, 50);
	    vertex(49, 85, 74);
	    endShape();
	  
ci si rende conto che il quadrilatero viene reso come giustapposizione di due triangoli giacenti su piani diversi.

Poligoni

Un poligono generico è definito da una successione di vertici, ed è un oggetto dotato di una superficie che può essere colorata. In Processing tali vertici si elencano dentro a una coppia beginShape(POLYGON); - endShape(); In realtà il poligono è da intendersi in senso generalizzato, essendo possibile usare le bezierVertex() e curveVertex() per specificare profili curvi. Ad esempio, si provi a disegnare la luna:


	    fill(246, 168, 20);
	    beginShape(POLYGON); 
	    vertex(30, 20); 
	    bezierVertex(80, 10, 80, 75, 30, 75); 
	    bezierVertex(50, 70, 60, 25, 30, 20); 
	    endShape(); 
	  

Ellissi

La funzione ellipse() traccia una ellisse nel piano 2D. I quattro parametri sono interpretati, come nel caso della rect(), come posizione seguita da larghezza e altezza. La posizione può essere regolata mediante la chiamata ellipseMode(), il cui parametro può assumere valori CORNER, CORNERS, CENTER, CENTER_RADIUS. I primi due di questi quattro possibili valori vanno riferiti al rettangolo circoscritto alla ellisse.

3D

Processing offre un repertorio assai limitato di primitive di oggetti tridimensionali, essenzialmente solo sfere e parallelepipedi.

Scatole

La funzione box() produce un cubo se invocata con un solo parametro (lato), un parallelepipedo se invocata con tre parametri (larghezza, altezza, profondità).

Palle

La funzione sphere() produce, mediante un poliedro approssimante, una sfera il cui raggio è specificato come parametro. La funzione sphereDetail() può essere usata per specificare il numero di vertici del poliedro che approssima la sfera ideale.

Lo stack delle trasformazioni

Una rotazione o una traslazione possono essere pensate come operazioni che ruotano o traslano il sistema di riferimento cartesiano. In altri termini, dopo una rotate() o una translate() le successive operazioni di posizionamento di oggetti grafici avranno un nuovo sistema di assi coordinati. Quando si posizionano diversi oggetti in vario modo nello spazio, è utile tenere traccia dei diversi sistemi di assi coordinati che via via si vanno ad impostare. La struttura dati atta a contenere tali sistemi è il cosiddetto stack (o pila) delle trasformazioni (matrix stack). Con la funzione pushMatrix() il sistema di coordinate corrente viene impilato in testa allo stack. Invece, per recuperare il sistema di coordinate precedente alla ultima trasformazione operata, bisogna operare una popMatrix(). Di fatto, lo stack contiene le matrici di trasformazione affine, secondo quanto prescritto da OpenGL e descritto in Sezione 14.

Esempio 1

In questo esempio vengono collocati due oggetti, uno quadrato planare ed un cubo, nello spazio 3D. Il primo pushMatrix() salva sullo stack il sistema di coordinate, quindi vengono applicate alcune trasformazioni che precedono il disegno del quadrato. Per tornare al sistema di coordinate precedente, e quindi operare nuove trasformazioni per collocare il cubo, si applica una popMatrix(). Di fatto, le pushMatrix() e popMatrix() delimitano lo scope relativo al posizionamento geometrico di un oggetto.

float angle;

	void setup(){
          size(100, 100, P3D); 
	  angle = 0;
	}

	void draw(){
	  background(200);
	  angle += 0.003;
	  pushMatrix();
	  translate(25,50);
	  rotateZ(angle);
	  rotateY(angle);
	  rectMode(CENTER);
	  rect(0,0,20,20);
	  popMatrix();
	  translate(75,50,-25);
	  rotateX(angle);
	  box(20);
	}

	

Illuminazione

Il modello di illuminazione di Processing ricalca direttamente quello di OpenGL, cioè il modello di riflessione di Phong. Tale modello non è giustificato fisicamente, ma è particolarmente efficiente. OpenGL considera illuminato ogni poligono la cui normale forma un angolo acuto con la direzione di provenienza della luce. Questo a prescindere dalla presenza di oggetti mascheranti, e quindi le ombre non vengono rese. Si dice che OpenGL adotta un modello locale di illuminazione, in quanto le riflessioni multiple tra le superfici e le proiezioni di ombre non vengono automaticamente elaborate.

E' disponibile una sorgente di luce di ambiente, cioè non proveniente da alcuna direzione particolare, il cui colore viene specificato mediante i parametri della chiamata di attivazione ambientLight(). Una sorgente di luce direzionale viene impostata con directionalLight(), i cui parametri specificano colore e direzione di provenienza. Il metodo lights() attiva una combinazione di default di luce ambientale grigia e luce direzionale, anch'essa grigia, proveniente dalla direzione frontale. E' possibile impostare una sorgente luminosa puntiforme in una data posizione dello spazio, mediante la chiamata pointLight(). Infine, il metodo spotLight() attiva un fascio di luce di cui, oltre a colore, posizione e direzione di irraggiamento, è possibile controllare l'apertura dell'angolo e la concentrazione intorno all'asse, mediante l'esponente e e che regola la rapidità di decadimento all'allontanarsi dall'asse:

coseφ φ e
(1)

Considerata una superficie piana, una luce direzionale che incida su di essa produce luce riflessa secondo varie direzioni, in maniera dipendente dalle proprietà superficiali. Nel caso di riflessione perfettamente diffusiva (o lambertiana), l'irraggiamento è prodotto in tutte le direzioni allo stesso modo, con una intensità tanto maggiore quanto più la direzione di incidenza è vicina alla direzione ortogonale (o normale) alla superficie. Viceversa, se la superficie è perfettamente riflettente, la luce viene riflessa solo lungo la direzione simmetrica, rispetto alla normale, alla direzione di incidenza. In OpenGL, per avere la massima flessibilità nella definizione della illuminazione, ogni sorgente racchiude in sé le tre componenti di illuminazione ambientale, lambertiana, e speculare. Queste tre componenti sono definibili separatamente, e interagiscono con le rispettive tre componenti che definiscono le proprietà superficiali degli oggetti. I colori definiti nei parametri dei metodi directionalLight(), pointLight(), e spotLight() definiscono la componente lambertiana di illuminazione. La lightSpecular() specifica il colore della componente di luce incidente che viene trattata con riflessione speculare.

In Processing è possibile controllare le proprietà delle superfici mediante i metodi ambient() (che agisce sulla componente ambientale di luce) e specular() (che agisce sulla componente speculare). La shininess() controlla la concentrazione del fascio riflesso specularmente, mediante un coefficiente che agisce in modo analogo all'esponente della Equazione 1. Gli oggetti rappresentati possono anche essere considerati sorgenti di luce, e quindi si può assegnare loro una luce di emissione, mediante la emmissive(). Tuttavia, le sorgenti definite in questo modo non producono illuminazione sugli altri oggetti della scena.

In OpenGL, le luci puntiformi, a spot, e ambientali subiscono una attenuazione dipendente dalla distanza, secondo il modello

attenuation=1a+bd+cd2 attenuation 1 a b d c d 2
(2)
Il metodo ligthFalloff() consente di specificare i parametri a a, b b, and c c

Esempio 2

Qui un cubo e una QUAD_STRIP vengono collocati nello spazio ed illuminati da una sorgente rotante. Inoltre, viene impostate una tenue luce fissa. Si noti l'assenza di ombre e la apparente planarità delle superfici della QUAD_STRIP.

float r;
float lightX, lightY, lightZ;

void setup() {
  size(400, 400, P3D);
  r = 0;
  ambient(180, 90, 0);
  specular(0, 0, 240);
  lightSpecular(200, 200, 200);   
  shininess(5);
}
  
void draw() {
  lightX = 100*sin(r/3) + width/2;
  lightY = 100*cos(r/3) + height/2;
  lightZ = 100*cos(r);
  background(0,0,0);
  noStroke(); 
  ambientLight(153, 102, 0);
  
  lightSpecular(0, 100, 200);
  pointLight(100, 180, 180, 
             lightX, lightY, lightZ);
  pushMatrix();
    translate(lightX, lightY, lightZ);
    emissive(100, 180, 180);
    sphere(4); //Put a little sphere where the light is
    emissive(0,0,0);
  popMatrix(); 
  pushMatrix();
    translate(width/2, height/2, 0);
    rotateX(PI/4);
    rotateY(PI/4); 
    box(100);
  popMatrix();
  pushMatrix();
    translate(width/4, height/2, 0);
    beginShape(QUAD_STRIP);
    vertex(10,13,8);
    vertex(13,90,13);
    vertex(65,76,44);
    vertex(95,106,44);
    vertex(97,20,70);
    vertex(109,70,80);
   endShape();
  popMatrix();
  r+=0.05;
}
	

Proiezioni

Proiezione prospettica

Una proiezione prospettica è caratterizzata da un centro di proiezione e da un piano di proiezione. I raggi proiettori collegano i punti della scena con il centro di proiezione, individuando i corrispondenti punti proiettati sul piano di proiezione. La Figura 1 mostra una vista in sezione in cui il piano di proiezione è individuato da una retta di ascissa d d , e il centro di proiezione è collocato nell'origine.

Figura 1
Figura 1 (perspective2D.png)
Mediante similitudine dei due triangoli rettangoli, è facile rendersi conto che il punto di ordinata y y viene proiettato sul piano nel punto di ordinata y p =ydz y p y d z .

Più in generale, la proiezione di un punto di coordinate omogenee xyz1 x y z 1 su un piano perpendicolare all'asse z z e secante tale asse in posizione d d si effettua, in coordinate omogenee, per moltiplicazione con la matrice ( 1000 0100 0010 001d0 ) 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 d 0 . Il punto proiettato diventa xyzzd x y z z d , il quale può essere normalizzato moltiplicando tutti i suoi elementi per dz d z . Si ottiene quindi xdzydzd1 x d z y d z d 1

Viste parallele

Le viste parallele sono quelle che si ottengono facendo retrocedere a infinito ( ) il centro di proiezione. In questo modo, i raggi proiettori sono tutti paralleli.

Proiezione ortografica

La proiezione ortografica produce una classe di viste parallele mediante conduzione di proiettori perpendicolari al piano di proiezione. Se il piano di proiezione viene posto perpendicolare all'asse z z e passante per l'origine, la matrice di proiezione risulta particolarmente semplice: ( 1000 0100 0000 0001 ) 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 . Tra le proiezioni ortografiche, le assonometrie si basano sulla possibilità di descrivere l'oggetto da proiettare secondo tre assi ortogonali, e sull'orientazione del piano di proiezione rispetto a questi assi. In particolare la assonometria isometrica è tale che le proiezioni degli assi formano tra loro angoli di 120° 120 ° . Essa ha la proprietà che lunghezze uguali lungo i tre assi rimangono tra loro uguali lungo le proiezioni degli stessi. Per ottenere una assonometria isometrica di un oggetto i cui assi principali sono inizialmente paralleli agli assi coordinati, si può procedere mediante una prima rotazione di 45 ° 45 ° intorno all'asse y y, seguita da una seconda rotazione di arctan1 2 =35.264 ° 1 2 35.264 ° intorno all'asse x x

Proiezione obliqua

Si parla di proiezione obliqua ogniqualvolta i raggi proiettori sono obliqui (non perpendicolari) rispetto al piano di proiezione. Per introdurre una deviazione dei raggi proiettori dalla perpendicolare secondo angoli θ θ e φ φ si deve usare una matrice di proiezione ( 10tanθ0 01tanφ0 0000 0001 ) 1 0 θ 0 0 1 φ 0 0 0 0 0 0 0 0 1

Proiezione di ombre

Come abbiamo visto, Processing offre un modello di illuminazione locale, e quindi non è possibile proiettare ombre (shadow casting) per via diretta. Tuttavia, la manipolazione delle matrici di trasformazione affine ci consente di proiettare abbastanza agevolmente ombre su piani. La tecnica che si usa è detta flashing in the eye, con questo termine intendendo che si sposta il centro ottico della scena nel punto in cui è collocata la sorgente luminosa, e si procede con una trasformazione prospettica con piano di proiezione coincidente con quello su cui si vuole proiettare l'ombra.

Esempio 3

Il codice seguente proietta sul pavimento l'ombra generata da una sorgente di luce collocata sull'asse y y. Il risultato è mostrato in Figura 2

Figura 2
Proiezione di un'ombra
Proiezione di un'ombra (projectedShadow.png)
size(200, 200, P3D);
float centro = 100; 
float yp = 70; // distanza del piano di proiezione (il pavimento) dal centro
float yl = 40; // altezza del centro di proiezione (la sorgente di luce) 
               // rispetto al centro

translate(centro, centro, 0); // spostamento dell'origine 
                              // al centro della finestra
noFill();
box(yp*2); //disegno del cubo-stanza
pushMatrix();
  fill(250); noStroke();
  translate(0, -yl, 0); // spostamento in alto (rispetto al centro)
                        // della "lampadina"
  sphere(4); // disegno della sfera che rappresenta la lampadina
  stroke(10);
popMatrix();

pushMatrix(); // disegno del cubo wireframe
  noFill();
  rotateY(PI/4); rotateX(PI/3);
  box(20);
popMatrix();

// PROIEZIONE DELL'OMBRA MEDIANTE
// COMPOSIZIONE DI TRE TRASFORMAZIONI (la terza è applicata per prima)

translate(0, -yl, 0); // riporta sorgente di luce e pavimento 
                      // nelle rispettive posizioni (vedi la traslazione
                      // sotto che viene applicata per prima all'ombra) 

applyMatrix(1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 1/(yp+yl), 0, 0); // proiezione sul pavimento
                                 // spostato in basso di yl
 
translate(0, yl, 0); // spostamento della sorgente di luce  
                     // (il centro di proiezione) al centro 
                     // della stanza (ovvero nell'origine) 
                     // e, solidalmente, del "pavimento" in basso di yl

pushMatrix();        // disegno del cubo grigio che genera l'ombra
  fill(120, 50);     // mediante le trasformazioni di cui sopra
  noStroke();
  rotateY(PI/4); rotateX(PI/3);
  box(20);
popMatrix(); 
	    

Pillole di OpenGL

OpenGL è un insieme di funzioni che consentono al programmatore di accedere al sistema grafico. In gergo tecnico è una Application Programming Interface (API). Principalmente essa si occupa della resa grafica (rendering) di una scena popolata di oggetti tridimensionali e luci, da un certo punto di vista. Per quanto riguarda il programmatore, OpenGL consente di descrivere oggetti geometrici ed alcune loro proprietà, nonché di stabilire come tali oggetti debbano essere illuminati e visti. Dal punto di vista realizzativo, OpenGL è basato su una pipeline grafica composta di moduli, come riportato in Figura 3. Un ottimo libro di grafica interattiva in OpenGL è quello di Angel.

Figura 3
La pipeline di OpenGL
La pipeline di OpenGL (pipelineOpenGL.png)

In Processing (ed in OpenGL), l'utente specifica gli oggetti mediante coordinate mondo (standard coordinates). La model-view matrix è la matrice di trasformazione usata per passare da coordinate mondo a uno spazio associato alla camera da presa. Ciò consente di cambiare dinamicamente il punto di vista e l'orientazione della camera. In OpenGL ciò avviene mediante la funzione gluLookAt(), la quale è riprodotta in Processing dalla camera(). I primi tre parametri identificano la posizione, in coordinate mondo, del centro ottico della camera (eye point). La seconda terna di parametri identifica un punto al quale è rivolta la camera (center of the scene). La terza terna di coordinate identifica un vettore atto a specificare la verticale di ripresa. Ad esempio, il codice

  
	void setup() {
           size(100, 100, P3D); 
           noFill(); 
           frameRate(20);
        }

        void draw() {
           background(204); 
           camera(70.0, 35.0, 120.0, 50.0, 50.0, 0.0, 
             (float)mouseX /width, (float)mouseY /height, 0.0); 
           translate(50, 50, 0); 
           rotateX(-PI/6); 
           rotateY(PI/3); 
           box(45); 
        }
        
	
traccia il wireframe di un cubo e permette la rotazione della camera da presa.

La projection matrix si occupa di fare la proiezione sulla finestra di vista, proiezione che può essere parallela (o ortografica) o prospettica. La proiezione ortografica si può attivare con la chiamata ortho(). La proiezione prospettica è quella di default, ma si può esplicitamente attivare con perspective(). Proiezioni particolari, quali quelle oblique, possono essere ottenute per distorsione degli oggetti mediante applicazione della applyMatrix(). C'è inoltre la texture matrix, di cui ci si occupa in un altro modulo.

Per ogni tipo di matrice, OpenGL mantiene uno stack di matrici, la matrice corrente essendo quella in cima. Il meccanismo dello stack consente di salvare uno stato (mediante pushMatrix()) prima di operare nuove trasformazioni, o di rimuovere lo stato corrente evidenziando uno stato precedente (mediante popMatrix()). Ciò si riflette nelle operazioni Processing descritte in Sezione 6. Le trasformazioni in OpenGL sono applicate secondo lo schema:

  1. Push sullo stack;
  2. Applica tutte le trasformazioni desiderate mediante moltiplicazione con la matrice in cima allo stack;
  3. Disegna l'oggetto (che risulterà affetto dalle trasformazioni);
  4. Pop dallo stack.

Un viewport è un'area rettangolare della finestra di display. Il passaggio dal piano di proiezione prospettica al viewport avviene in due passi: (i) trasformazione in una finestra 2 x 2 centrata nell'origine ( normalized device coordinates ) (ii) mappatura della finestra normalizzata sul viewport. Usando la rappresentazione intermedia a coordinate normalizzate, l'operazione di clipping, cioè di eliminazione degli oggetti o loro parti non visibili attraverso la finestra, diventa banale. In Processing, le funzioni screenX(), screenY(), e screenZ() consentono di ottenere le coordinate X-Y prodotte dalla trasformazione di viewport e dagli elementi precedenti della catena di Figura 3.

Il frustum di vista è l'angolo solido attraverso il quale si attua la proiezione prospettica, come indicato in Figura 4. Vengono visualizzati gli oggetti (o le loro parti) presenti nel volume di vista, le rimanenti parti essendo soggette a clipping. In Processing (e in OpenGL) il frustum si può definire mediante posizionamento dei sei piani che lo individuano (frustum()), oppure mediante specificazione di angolo verticale, aspect ratio, posizione dei piani anteriore e posteriore (perspective()). Ci si può chiedere come avvenga la rimozione delle facce nascoste, cioè di quelle facce che sono mascherate da altre facce presenti nel volume di vista. OpenGL usa l'algoritmo dello z-buffer, che è supportato dalle schede grafiche. La scheda tiene un'area di memoria bidimensionale (lo z-buffer) corrispondente ai pixel della finestra di vista e contenente valori di profondità. Prima di proiettare un poligono sulla finestra di vista la scheda va a controllare che i pixel affetti da tale poligono non abbiano già un valore di profondità inferiore rispetto a quello del poligono in oggetto. In questo caso significherebbe che c'è un oggetto grafico che maschera quello che si sta disegnando.

Figura 4
Il frustum di vista
Il frustum di vista (frustum.png)

Elaborazioni geometriche sofisticate si possono ottenere manipolando direttamente le matrici di proiezione e model-view. Ciò è possibile, in Processing, a partire dalla matrice unitaria, caricata con resetMatrix(), mediante moltiplicazioni matriciali eseguite con applyMatrix().

Exercise 1

Si esegua e si analizzi il codice Processing


	      size(200, 200, P3D);
	      println("Default matrix:"); printMatrix();
	      noFill(); 
	      ortho(-width/2, width/2, -height/2, height/2, -100, 100); 
	      translate(100, 100, 0); 
	      println("After translation:"); printMatrix();
	      rotateX(atan(1/sqrt(2))); 
	      println("After about-X rotation:"); printMatrix();
	      rotateY(PI/4); 
	      println("After about-Y rotation:"); printMatrix();
	      box(100); 
	    
Cosa visualizza e con che tipo di proiezione? Come si interpretano le matrici stampate nella consolle? Posso invertire l'ordine delle rotazioni?

Solution

Il wireframe di un cubo viene visualizzato in assonometria isometrica. Le ultime tre matrici rappresentano, accumulandole una dopo l'altra, le tre operazioni di traslazione (per allineare il cubo al centro della finestra), rotazione intorno all'asse x x, e rotazione intorno all'asse y y. Una successione di due rotazioni corrisponde al prodotto di due matrici di rotazione, e il risultato non è indipendente dall'ordine delle matrici (il prodotto non è commutativo). Il prodotto di due matrici di rotazione R x R y R x R y corrisponde ad eseguire prima la rotazione intorno a y y, e poi la rotazione intorno a x x.

Exercise 2

Si scriva un codice Processing che produca la proiezione obliqua di un cubo.

Solution

Ad esempio:


	    size(200, 200, P3D);
	    float theta = PI/6; 
	    float phi = PI/12;
	    noFill(); 
	    ortho(-width/2, width/2, -height/2, height/2, -100, 100); 
	    translate(100, 100, 0);
	    applyMatrix(1, 0, - tan(theta), 0,
	                0, 1, - tan(phi), 0,
                        0, 0, 0, 0,
                        0, 0, 0, 1);
	    box(100); 
	    

Exercise 3

Si visualizzi un cubo che proietti la sua ombra sul suolo, assumendo che la sorgente luminosa sia a distanza infinita (quale si può considerare, in pratica, la distanza del sole).

Solution

Si opera in modo simile a quanto visto in Esempio 3, ma la trasformazione proiettiva è di tipo ortografico:

size(200, 200, P3D);
	      noFill();
	      translate(100, 100, 0);
	      pushMatrix();
	        rotateY(PI/4); rotateX(PI/3);
	        box(30);
	      popMatrix();
	      translate(0, 60, 0); //proietta un'ombra da infinito (sole)
	      applyMatrix(1, 0, 0, 0,
	                  0, 0, 0, 0,
                          0, 0, 1, 0,
                          0, 0, 0, 1); 
	      fill(150);
	      pushMatrix();
	      noStroke();
	      rotateY(PI/4); rotateX(PI/3);
	      box(30);
	      popMatrix(); 
	    

References

  1. Edward Angel. (2001). Interactive Computer Graphics: A Top-Down Approach With OPENGL primer package-2nd Edition. Prentice-Hall, Inc.

Content actions

Download module as:

PDF | EPUB (?)

What is an EPUB file?

EPUB is an electronic book format that can be read on a variety of mobile devices.

Downloading to a reading device

For detailed instructions on how to download this content's EPUB to your specific device, click the "(?)" link.

| More downloads ...

Add module to:

My Favorites (?)

'My Favorites' is a special kind of lens which you can use to bookmark modules and collections. 'My Favorites' can only be seen by you, and collections saved in 'My Favorites' can remember the last module you were on. You need an account to use 'My Favorites'.

| A lens I own (?)

Definition of a lens

Lenses

A lens is a custom view of the content in the repository. You can think of it as a fancy kind of list that will let you see content through the eyes of organizations and people you trust.

What is in a lens?

Lens makers point to materials (modules and collections), creating a guide that includes their own comments and descriptive tags about the content.

Who can create a lens?

Any individual member, a community, or a respected organization.

What are tags? tag icon

Tags are descriptors added by lens makers to help label content, attaching a vocabulary that is meaningful in the context of the lens.

| External bookmarks