Il texture mapping è una tecnica molto utilizzata nel campo del rendering poichè è in grado elevare notevolmente il realismo della scena.
Sostanzialmente consiste nell'incollare un'immagine ad un oggetto composto da uno o più poligoni. Per esempio, supponendo di avere dei poligoni che rappesentino un muro e un'immagine che rappresenta dei mattoni possiamo applicare l'immagine ai poligoni ottenendo così l'effetto di un vero e proprio muro di mattoni. Inoltre, dato che il costo per la manipolazione dell'immagine non dipende da ciò che è rappresentato ma dalla risoluzione, possiamo utilizzare immagini ricche di particolari, infatti è ormai pratica comune utilizzare direttamente (o dopo piccoli ritocchi) le foto di una camera digitale.
Quando vogliamo texturizzare un oggetto dobbiamo creare creare una texture partendo dall'immagine che vogliamo applicare. La texture è un tipo di dato particolare che solitamente viene gestito direttamente dalla libreria grafica di basso livello (OpenGL) perchè bisogna eseguire diverse operazioni per adattare l'immagine all'oggetto.
Solitamente un'immagine viene visualizzata come un rettangolo la cui altezza e lunghezza dipendono dal numero di pixel impiegati, ma quando viene applicata a un poligono deve assumere la forma e la dimensione di quest'ultimo. Ciò significa che se il nostro oggetto è molto grande la nostra immagine dovrà essere "tirata", se invece il nostro oggetto è piccolo o lontano l'immagine sarà molto ridotta. Inoltre è necessario che durante le trasformazioni geometriche dell'oggetto (rotazioni etc.) l'immagine sia sempre visualizzata in modo corretto.
In OpenGL il Texture Mapping è validamente supportato, peraltro oltre al Texture Mapping tradizionale con immagini 2D è disponibile sia il mapping con texture 1D (cioè immagini con altezza uguale a 1) sia con texture 3D. Le Texture 3D (immagini che hanno una profondità) si possono utilizzare per realizzare effetti interessanti, ma esigono una grande quantità di memoria e sono attualmente poco usate.
Per abilitare o disabilitare un determinato tipo di Texture Mapping si utilizzano i parametri:
- GL_TEXTURE_2D Le normali texture 2D
- GL_TEXTURE_1D Le texture 1D
- GL_TEXTURE_3D Le texture 3D
I passi da seguire per creare una texture sono i seguenti:
- Generazione di un riferimento alla texture
- Attivazione del riferimento
- Settaggio dei parametri
- Creazione della Texture mediante l'immagine
Generazione di un riferimento alla texture
Si tratta di indicare ad OpenGL che vogliamo riservare spazio per una o più texture che utilizzeremo successivamente.
Per distinguere univocamente le diverse texture per ogni riferimento creato viene restituito un numero intero.
La funzione da utilizzare è:
void glGenTextures(GLsizei n, GLuint *textureNames);
n è il numero di Texture che vogliamo creare, textureNames è l'indirizzo di un intero o di un array di interi dove saranno memorizzati gli identificativi per ogni texture creata.
Attivazione del riferimento
Questa operazione consiste nel rendere attuale, cioè attiva, una delle texture create. OpenGL infatti in ogni momento può lavorare su una sola texture.
Per tale compito usiamo la funzione:
void glBindTexture(GLenum target, GLuint textureName);
target è quale tipo di texture attivare, textureName è l'identificativo della texture che abbiamo ottenuto con la chiamata a glGenTextures.
Settaggio dei parametri
Questa fase è più complicata poichè si devono impostare i parametri che andranno a influire sulla rappresentazione della texture.
I parametri più importanti che si devono modificare sono i filtri e precisamente il Magnification filter e il Minification filter.
Il Magnification filter entra in azione quando un pixel reale rappresenta solo una piccola parte di una texel, cioè di un pixel della texture. Questo succede quando la nostra texture è molto piccola e viene applicata su un oggetto molto grande, oppure quando l'oggetto è molto vicino alla vista dell'utente. Un esempio lampante dell'azione di un filtro di questo genere è quando in giochi FPS come Quake ci avviciniamo al muro, in questi casi il software tenta di nascondere l'immagine "pixellosa" attraverso una serie di interpolazioni.
Per modificare dei parametri della texture utilizziamo la funzione:
void glTexParameter{if}(GLenum target, GLenum pname, TYPE param);
target è il tipo di texture, pname è il parametro che vogliamo modificare, param e' il valore che assumera' il parametro.
In particolare per modificare il Magnification filter pname sarà uguale a GL_TEXTURE_MAG_FILTER e param indicherà il tipo di filtro da utilizzare.
Sono disponibili i seguenti filtri:
- GL_NEAREST Utilizza la texel più vicina al pixel che deve essere texturizzato
- GL_LINEAR Interpolazione lineare delle 4 texels più vicine al pixel che deve essere texturizzato
Naturalmente GL_NEAREST sarà più veloce mentre GL_LINEAR darà risultati migliori.
Il Minification filter è invece esattamente il contrario del Magnification, viene utilizzato quando la texture è più grande dell'oggetto su cui viene incollata o quando l'oggetto è lontano, quindi significa che una texel rappresenta più pixel reali.
Si utilizza la stessa funzione con pname uguale GL_TEXTURE_MIN_FILTER e sono a disposizione i medesimi parametri. Per il Minification filter si possono però utilizzare altre opzioni grazie alla tecnica del mipmapping che vedremo più tardi.
Altri settaggi importanti che in OpenGL vanno sotto il nome di Texture Functions sono il modo in cui i colori delle texture influiscono sugli oggetti. Si utilizza:
void glTexEnv{if}(GLenum target, GLenum pname, TYPE param);
target deve essere uguale a GL_TEXTURE_ENV, pname sarà uguale a GL_TEXTURE_ENV_MODE mentre param può assumere uno dei seguenti valori:
- GL_BLEND Il Colore della texture viene moltiplicato per il colore del pixel e infine moltiplicato per una costante.
- GL_DECAL Il Colore della texture sostituisce quello del colore
- GL_MODULATE Il colore della Texture viene moltiplicato per quello del pixel.
Creazione della Texture
Infine possiamo indicare ad OpenGL quale immagine vogliamo utilizzare per creare la nostra texture. La funzione per eseguire tale compito è:
void glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
target deve essere uguale a GL_TEXTURE_2D;
level indica se ci sono risoluzioni multiple (in caso negativo sarà uguale a 0);
internalFormat descrive il formato delle Texels (GL_RGB>, GL_RGBA, ma anche GL_LUMINACE, GL_LUMINANCE_ALPHA);
width e height sono la dimensione dell'immagine;
border indica se vogliamo che la texture abbia un bordo;
format indica il formato dati dell'immagine (GL_RGB, GL_RGBA...)
type indica il tipo di dato utilizzato (GL_UNSIGNED_BYTE, GL_SHORT...)
pixels (o texels) è il puntatore ai dati dell'immagine;
Rappresentazione delle Texture
Una volta creata la texture per utilizzarla dobbiamo attivarla e indicare le sue coordinate rispetto al poligono.
Per attivarla chiamiamo di nuovo glBindTexture passandogli l'identificativo.
Vengono utilizzate le coordinate (s,t) dove s indica il valore in x e t il valore in y all'interno dell'immagine. Ad ogni vertice del poligono che vogliamo texturizzare dobbiamo associare una coppia di coordinate s,t.
Per esempio in un quadrato definito dalle coordinate:
Vertice in basso a sinistra: -1.0, -1.0;
Vertice in basso a destra : 1.0, -1.0;
Vertice in alto a destra : 1.0, 1.0;
Vertice in alto a sinistra : -1.0, 1.0;
Le coordinate delle texture saranno:
Vertice in basso a sinistra: 0.0, 0.0;
Vertice in basso a destra: 1.0, 0.0;
Vertice in alto a destra: 1.0, 1.0;
Vertice in alto a sinistra: 0.0, 1.0;
-- Possibile Inserimento Immagine --
La funzione per indicare le coordinate è semplicemente:
void glTexCoord2{if}(TYPE coords);
Il codice per l'esempio del quadrato dimostra come prima del vertice devono essere specificate le coordinate texture associate.
glBegin(GL_QUADS); // Disegnamo un quadrato
glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, 0.0);
glTexCoord2f(1.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
glTexCoord2f(1.0, 1.0); glVertex3f(1.0, 1.0, 0.0);
glTexCoord2f(-1.0, 1.0); glVertex3f(-1.0, 1.0, 0.0);
glEnd(); // Fine Disegno
Repeating e Clamping
Può essere utile ripetere una determinata texture più volte all'interno dello stesso oggetto. Riprendendo l'esempio del muro di mattoni è probabile che il nostro muro sia molto grande e che la texture sia piuttosto piccola. Applicando semplicemente la texture al muro questa verrà "tirata". Per evitare questo problema possiamo settare il repeating della texture e indicare coordinate maggiori di 1.0.
Possiamo ottenere questo effetto attraverso glTexParameter, con pname uguale a GL_TEXTURE_WRAP_S per la coordinata s e GL_TEXTURE_WRAP_T per la coordinata t, indicando come param GL_REPEAT.
A questo punto quando la texture verrà visualizzata con coordinata finale (2.0, 2.0), vedremo che la texture è stata riprodotta 4 volte.
Settando invece il param a GL_CLAMP è possibile riempire solamente 1/4 dell'oggetto.
Clean Up
Per eliminare una texture si chiama semplicemente la funzione
void glDeleteTextures(GLsizei n, const GLuint *textureNames);
n è il numero di texture che vogliamo eliminare e textureNames è l'indirizzo degli identificativi delle texture da eliminare.
Mipmapping
Per migliorare la qualità di visualizzazione delle texture quando queste si presentano più piccole è possibile creare un certo numero di texture con la stessa immagine ma con dimensioni differenti, in questo modo a seconda della dimensione del poligono al quale deve essere applicata OpenGL utilizzerà la texture più adatta.
Questa operazione può essere fatta o attraverso chiamate multiple a glTexImage settando il parametro level con valori crescenti, oppure utilizzando una funzione di utilità della libreria glu:
int gluBuild2DMipmaps(GLenum target, GLint components, GLint width, GLint height, GLenum format, GLenum type, void *data);
target è il tipo di texture da create GL_TEXTURE_2D
components corrisponde all'internal format di glTexImage
width e height sono le dimensioni dell'immagine iniziale
format è il tipo di formato dati dell'immagine
type è il tipo di dato in cui sono contenuti i valori
Questa funzione costruirà automaticamente una serie di texture più piccole partendo da quella originale.
Se utilizziamo le mipmaps quando settiamo i parametri per il Minification filter dobbiamo utilizzare uno di questi valori:
- GL_NEAREST_MIPMAP_NEAREST usa la texture più vicina alla risoluzione del poligono e il filtro GL_NEAREST
- GL_NEAREST_MIPMAP_LINEAR usa la texture più vicina alla risoluzione del poligono e il filtro GL_LINEAR
- GL_LINEAR_MIPMAP_NEAREST usa interpolazione tra le 2 texture vicine alla risoluzione del poligono e il filtro GL_NEAREST
- GL_LINEAR_MIPMAP_LINEAR usa interpolazione tra le 2 texture vicine alla risoluzione del poligono e il filtro GL_LINEAR