Summary: A brief guide to texture mapping in Processing (in italian)
Nota: You are viewing an old version of this document. The latest version is available here.
Come visto in Graphic Composition in
Processing, si possono ottenere superfici come
collezioni di poligoni mediante definizione di vertici
all'interno della coppia beginShape() -
endShape(). E' possibile attribuire un colore a
uno o più vertici, in modo da rendere variazioni continue di
colore (gradiente). Ad esempio, si provi ad eseguire il codice
size(200,200,P3D);
beginShape(TRIANGLE_STRIP);
fill(240, 0, 0); vertex(20,31, 33);
fill(240, 150, 0); vertex(80, 40, 38);
fill(250, 250, 0); vertex(75, 88, 50);
vertex(49, 85, 74);
endShape();
per ottenere una sfumatura continua dal rosso al giallo nella strip di due triangoli.
Il sistema grafico opera una interpolazione dei valori di colore attribuiti ai vertici. Questo tipo di interpolazione bilineare è definita come segue:
Un esempio significativo di interpolazione di colori associati ai vertici di un cubo si trova negli esempi di Processing, nel codice RGB Cube.
Nella modellizzazione di scene complesse per composizione di elementi grafici semplici non si può superare una certa soglia di complessità. Si pensi ad esempio alla modellizzazione di una scena campestre nella quale si debbano rappresentare i singoli elementi di vegetazione, ivi compresi i fili d'erba del prato. E' impensabile svolgere manualmente questo compito. Potrebbe essere possibile collocare e controllare i fili d'erba per via algoritmica, ed è un strada simile che si segue, ad esempio, per rendere realistica la capigliatura o la pelle dei personaggi nei più sofisticati film di animazione (si veda ad esempio, the Incredibles). Oppure, in special modo se sono richieste prestazioni di grafica interattiva, bisogna ricorrere all'artificio delle texture. In sostanza, si tratta di immagini che rappresentano la tessitura visiva delle superfici e che vengono mappate sui poligoni che modellano gli oggetti della scena. Ai fini della resa qualitativa delle superfici ha senso limitare il livello di dettaglio a frammenti non più piccoli di un pixel, e quindi il texture mapping viene inserito nella catena di rendering a livello di rastering delle primitive grafiche, cioè laddove si passa da descrizioni geometriche in 3D a illuminazione di pixel sul display. E' a questo livello che avviene anche la rimozione delle facce nascoste, i soli frammenti di interesse essendo quelli visibili.
In Processing, una texture viene definita all'interno di un
blocco beginShape() - endShape()
mediante la funzione texture() che ha come unico
parametro una variabile di tipo PImage. Le
successive chiamate a vertex() possono contenere,
come ultima coppia di parametri, il punto della texture al
quale si vuole associare il vertice. Infatti, ogni immagine di
texture è parametrizzata in due variabili textureMode() con
parametro IMAGE o NORMALIZED.
Nel codice che segue il l'immagine di vetro rotto viene usata come texture e affiancata all'interpolazione dei colori, nonché all'illuminazione di default. Lo shading delle superfici, prodotto dall'illuminazione e dai colori, viene modulato in maniera moltiplicativa dai colori della texture.
size(400,400,P3D);
PImage a = loadImage("vetro.jpg");
lights();
textureMode(NORMALIZED);
beginShape(TRIANGLE_STRIP);
texture(a);
fill(240, 0, 0); vertex(40,61, 63, 0, 0);
fill(240, 150, 0); vertex(340, 80, 76, 0, 1);
fill(250, 250, 0); vertex(150, 176, 100, 1, 1);
vertex(110, 170, 180, 1, 0);
endShape();
E' evidente che l'operazione di mappatura da immagine texture
a superficie oggetto, di forma arbitraria, implica una
qualche forma di interpolazione. In modo analogo a quanto
avviene per i colori, soltanto i vertici che delimitano la
superficie sono messi in corrispondenza con punti esatti
dell'immagine texture. Quello che avviene per i punti
interni deve essere stabilito in qualche modo. In effetti,
Processing e OpenGL si comportano secondo quanto illustrato
in Sezione 2, cioè per interpolazione
bilineare: una prima interpolazione lineare su ogni segmento
del contorno è seguita da interpolazione lineare su scan
line. Se
Un problema che si verifica è che raramente un pixel su display corrisponde esattamente a un texel. Su un pixel possono essere mappati più texel o, viceversa, un texel può mapparsi su più pixel. Il primo caso corrisponde ad un sottocampionamento (o decimazione) che, come visto in Sampling and Quantization, può dare luogo ad aliasing. L'effetto dell'aliasing si può attenuare mediante filtraggio passa-basso dell'immagine texture. Il secondo caso corrisponde ad un sovracampionamento, interpretabile in frequenza come un allontanamento delle immagini spettrali.
Le texture, oltre che importate da immagini, possono anche essere generate per via algoritmica, ciò è particolarmente indicato quando si vogliano generare dei pattern regolari o pseudo casuali. Ad esempio, il pattern di una scacchiera si può generare con il codice
PImage textureImg = loadImage("vetro.jpg"); // dummy image
colorMode(RGB,1);
int biro = 0;
int bbiro = 0;
int scacco = 5;
for (int i=0; i<textureImg.width; i+=scacco) {
bbiro = (bbiro + 1)%2; biro = bbiro;
for (int j=0; j<textureImg.height; j+=scacco) {
for (int r=0; r<scacco; r++)
for (int s=0; s<scacco; s++)
textureImg.set(i+r,j+s, color(biro));
biro = (biro + 1)%2;
}
}
image(textureImg, 0, 0);
L'uso della funzione random, combinato con filtri di vario
tipo, consente una ampia versatilità nella produzione di
texture. Ad esempio, in Figura 1 è
rappresentato un pattern ottenuto da una variazione del codice
di generazione della scacchiera. In particolare, è stata
aggiunta l'istruzione scacco=floor(2+random(5));
all'interno del for più esterno, e si eseguito un
filtraggio di media.
| Pattern generato per via algoritmica |
|---|
![]() |
Come si modifica il codice di Esempio 1 in modo da rendere le fratture del vetro più grandi?
Basta considerare solo un pezzo della texture, con chiamate
del tipo vertex(150, 176, 0.3, 0.3);
Si esegua e si analizzi il codice seguente. Si provi inoltre a variare la dimensione dei quadratini e il tipo di filtraggio.
size(200, 100, P3D);
PImage textureImg = loadImage("vetro.jpg"); // dummy image
colorMode(RGB,1);
int biro = 0;
int bbiro = 0;
int scacco = 5;
for (int i=0; i<textureImg.width; i+=scacco) {
// scacco=floor(2+random(5));
bbiro = (bbiro + 1)%2; biro = bbiro;
for (int j=0; j<textureImg.height; j+=scacco) {
for (int r=0; r<scacco; r++)
for (int s=0; s<scacco; s++)
textureImg.set(i+r,j+s, color(biro));
biro = (biro + 1)%2;
}
}
image(textureImg, 0, 0);
textureMode(NORMALIZED);
beginShape(QUADS);
texture(textureImg);
vertex(20, 20, 0, 0);
vertex(80, 25, 0, 0.5);
vertex(90, 90, 0.5, 0.5);
vertex(20, 80, 0.5, 0);
endShape();
// ------ filtraggio -------
PImage tImg = loadImage("vetro.jpg"); // dummy image
float val = 1.0/9.0;
float[][] kernel = { {val, val, val},
{val, val, val},
{val, val, val} };
int n2 = 1;
int m2 = 1;
colorMode(RGB,255);
// Convolve the image
for(int y=0; y<textureImg.height; y++) {
for(int x=0; x<textureImg.width/2; x++) {
float sum = 0;
for(int k=-n2; k<=n2; k++) {
for(int j=-m2; j<=m2; j++) {
// Reflect x-j to not exceed array boundary
int xp = x-j;
int yp = y-k;
if (xp < 0) {
xp = xp + textureImg.width;
} else if (x-j >= textureImg.width) {
xp = xp - textureImg.width;
}
// Reflect y-k to not exceed array boundary
if (yp < 0) {
yp = yp + textureImg.height;
} else if (yp >= textureImg.height) {
yp = yp - textureImg.height;
}
sum = sum + kernel[j+m2][k+n2] * red(textureImg.get(xp, yp));
}
}
tImg.set(x,y, color(int(sum)));
}
}
translate(100, 0);
beginShape(QUADS);
texture(tImg);
vertex(20, 20, 0, 0);
vertex(80, 25, 0, 0.5);
vertex(90, 90, 0.5, 0.5);
vertex(20, 80, 0.5, 0);
endShape();