Summary: Elementi di composizione grafica 2D e 3D (curve incluse) per l'ambiente e linguaggio Processing
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.
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.
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.
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 beginShape(LINE_STRIP) i vertici,
presi uno dopo l'altro, definiscono una spezzata. Con
l'invocazione beginShape(LINE_LOOP) la spezzata
viene chiusa su se stessa mediante collegamento del primo e
dell'ultimo vertice. Notare che il poligono così ottenuto non
può essere manipolato, ad esempio mediante coloratura.
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, e quindi passa per tutti e quattro i punti.
curveVertex() per specificare
ciascun punto all'interno di un blocco delimitato da
beginShape(LINE_STRIP) e
endShape().
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:
stroke(255, 0, 0); line(93, 40, 10, 10); line(90, 90, 15, 80); stroke(0, 0, 0); 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(LINE_STRIP) 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.
| 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);
beginShape(LINE_STRIP);
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);
}
|
fill(), il quale prevede anche la possibilità di
impostare la trasparenza.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);
}
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.
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.
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.
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();
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. box() produce un cubo se invocata con un solo
parametro (lato), un parallelepipedo se invocata con tre
parametri (larghezza, altezza, profondità). 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.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.
push()
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);
}
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 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. ambient() (che agisce
sulla componente ambientale di luce) e specular()
(che agisce sulla componente speculare). Quest'ultimo metodo
prevede anche la possibilità di specificare, mediante un
parametro alpha, se la superficie debba essere di
tipo traslucido. 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.
ligthFalloff() consente di
specificare i parametri 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.
import processing.opengl.*;
float r;
float lightX, lightY, lightZ;
void setup() {
size(400, 400, OPENGL);
r = 0;
ambient(180, 90, 0);
specular(0, 0, 240, 0.99);
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;
}
![]() Figura 1 |
Proiezione di un'ombra![]() Figura 2 |
size(200, 200, P3D);
float centro = 100;
float yp = 70; //distanza pavimento dal centro
float yl = 40; //altezza luce dal centro
translate(centro, centro, 0); //centra tutto sul cubo
noFill();
box(yp*2); //disegna la stanza
pushMatrix();
fill(250); noStroke();
translate(0, -yl, 0); // sposta in alto (rispetto al centro)
// la lampadina
sphere(4); //disegna la lampadina;
stroke(10);
popMatrix();
pushMatrix(); //disegna il cubo wireframe
noFill();
rotateY(PI/4); rotateX(PI/3);
box(20);
popMatrix();
translate(0, -yl, 0); // riporta sorgente di luce e
// pavimento al loro posto
applyMatrix(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 1/(yp+yl), 0, 0); // proietta sul pavimento
// spostato in basso di yl
translate(0, yl, 0); // porta sorgente di luce al centro
// e pavimento in basso di yl
pushMatrix();
fill(120, 50);
noStroke();
rotateY(PI/4); rotateX(PI/3);
box(20);
popMatrix();
La pipeline di OpenGL![]() Figura 3 |
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.
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.
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:
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.
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. Il frustum di vista![]() Figura 4 |
resetMatrix(),
mediante moltiplicazioni matriciali eseguite con
applyMatrix().
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?
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);
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();
Comments, questions, feedback, criticisms?