Texture

Le texture sono un modo molto comodo per trasferire allo shader array e matrici (mappe) di grandi dimensioni. Infatti, quando si lavora con gli shader, le texture cessano di rappresentare solo immagini: è il programmatore che decide come il suo shader intepreta i dati che contengono.

In un certo senso, anche la gestione delle texture tradizionali è più semplice tramite gli shader, perché si può controllare direttamente l'algoritmo con cui esse sono applicate a ciascun frammento. In precedenza, era necessario impostare un gran numero di paramentri per istruire la Fixed Function Pipeline su come esse dovevano essere applicate.

Creazione di una texture

Per creare una texture, per prima cosa si deve creare un id valido per quella texture, con la funzione:

void glGenTextures(GLsizei n, GLuint * textures);

textures è un puntatore a un buffer di n interi. Quando la funzione ritorna, il buffer contiene una lista di identificativi liberi per una texture. Se si vuole una sola texture, basta porre n uguale a 1 e il puntatore è un puntatore a un singolo intero.

La funzione glBindTexture ha due effetti: specifica il tipo della texture, se non è ancora stato specificato, e indica ad OpenGL che le operazioni sulle texture che seguono sono da applicare a quella texture.

void glBindTexture(GLenum target, GLuint texture);

Il parametro texture indica l'id della texture. target indica il tipo della texture e può essere:

GL_TEXTURE_1D
GL_TEXTURE_2D
GL_TEXTURE_3D
GL_TEXTURE_CUBE_MAP

Il caricamento della texture può avvenire in diversi modi. Il modo più semplice consiste nel caricarla a partire da un buffer in memoria, con una di queste funzioni:

void glTexImage1D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum  type, const GLvoid * data);
void glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum  type, const GLvoid * data);
void glTexImage3D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum  type, const GLvoid * data);

Dove:

Mentre la texture è attiva, è possibile impostare alcuni parametri che influiscono sulle modalità di accesso alla texture.

void glTexParameterf(GLenum target, GLenum pname, GLfloat param);

Questa funzione permette (tra l'altro) di decidere come la texture si comporta se viene richiesto il valore di un texel all'esterno della sua area. Di solito, le coordinate delle texture sono sempre comprese tra 0.0 e 1.0.

void glTexParameteri(GLenum target, GLenum pname, GLint param);

Un altro utilizzo delle funzioni di questa famiglia è quello di impostare il comportamento della texture nel caso in cui il texel richiesto debba essere ottenuto come interpolazione dei vicini.

Attivazione

Prima di eseguire il rendering con una certa texture, la texture deve essere attivata. Molte schede video moderne supportano il multitexturing, cioè hanno diverse (qualche decina) texture unit con cui più texture possono essere attivate contemporaneamente. Le texture unit hanno normalmente nomi propri:

GL_TEXTURE0
GL_TEXTURE1
GL_TEXTURE2
  ...

Ma di solito si somma un numero intero a GL_TEXTURE0 per ottenere le unit successive: GL_TEXTURE0 + unitId. La funzione:

void glActiveTexture(GLenum texture);

attiva la unit per quella texture. Dopodichè si deve associare la texture desiderata all'interno di quella unit, aggiungendo immediatamente una chiamata a glBindTexture con l'id della texture.

A questo punto, bisogna informare lo shader dell'esistenza della texture. All'interno dello shader deve essere dichiarata come uniform una (o più) variabili dei tipi che servono per accedere alla texture.

uniform sampler1D tex1;
uniform sampler2D tex2;
uniform sampler3D tex3;
uniform samplerCube texcube;
uniform sampler1DShadow texshadow1;
uniform sampler2DShadow texshadow2;

Allora, l'inizializzazione avviene come se fosse una variabile intera, a cui si assegna l'id della unit che si vuole usare. Ad esempio:

int addr = glGetUniformLocation(programId,"tex2");
glUniform1i(addr,unitId);

Uso negli shader

All'interno di uno shader, qualunque texture viene vista come un array a precisione infinita, che può essere letta tramite indici float. I filtri di minification e magnification impostati in precedenza risolvono automaticamente quello che accade quando viene richiesto il valore di un punto che non corrisponde esattamente a un texel.

Ogni lettura di una texture ritorna un vettore di quattro float, indipendentemente dal numero di componenti con cui la texture è stata creata all'esterno dello shader. Tuttavia, di questo vettore solo i componenti inizializzati sono significativi (e il canale alpha, che di solito va per default a 1.0).

Per ottenere il valore di una texture in un punto, sono definite alcune funzioni GLSL. Tra le più importanti:

vec4 texture1D(in sampler1D tex,in float coord);
vec4 texture2D(in sampler2D tex,in vec2 coord);
vec4 texture3D(in sampler3D tex,in vec3 coord);
vec4 textureCube(in samplerCube tex,in vec3 coord);

Ciascuna di queste funzioni riceve in ingresso la variabile per l'accesso alle texture e le coordinate su quella texture. Per le Cube Map, la coordinata specifica la direzione del vettore che, dal centro del cubo, va ad intercettare la texture.

Le specifiche di GLSL impongono che gli accessi alla texture devono essere raggiunti da tutte le istanze degli shader attraverso lo stesso flusso di controllo. Questo significa che nessuna chiamata a una di queste funzioni può seguire o essere all'interno di un if o di un ciclo con numero di iterazioni variabile.
Ad esempio:

uniform sampler2D tex;

void main()
{
  vec4 v1 = texture2D(tex,gl_Position.xy);    // uso consentito
  
  if (...)
  {
    v1 = texture2D(tex,gl_Position.yx);       // uso non consentito (dentro un if)
  }

  vec4 v2 = texture2D(tex,vec2(0.7,0.5));     // uso non consentito (dopo un if)
}

Se si desidera condizionare l'accesso a una texture, si deve aggirare il problema in qualche modo. Ad esempio, si può accedere comunque a quella texture all'inizio dello shader e poi usare un costrutto di controllo per valutare se usare oppure no il valore ottenuto.

Cancellazione

Per completezza, si riporta brevemente anche la funzione per rimuovere una texture dal contesto OpenGL:

void glDeleteTextures(GLsizei n, const GLuint * textures);

textures è il puntatore a un array di n texture da eliminare.


Copyright © 2011 Riccardo Monica
Eccetto ove espressamente indicato altrimenti, il contenuto di questa pagina è disponibile secondo la licenza Creative Commons Attribuzione - Condividi allo stesso modo 3.0.