Texturation

Bases de la texturation

Une texture est une forme de stockage de données qui permet un accès pratique non seulement à des entrées de données particulières, mais également à des points d’échantillonnage mélangeant (interpolant) plusieurs entrées ensemble.

Dans OpenGL, les textures peuvent être utilisées pour beaucoup de choses, mais le plus souvent, il s’agit de mapper une image sur un polygone (par exemple un triangle). Afin de mapper la texture sur un triangle (ou un autre polygone), nous devons indiquer à chaque sommet à quelle partie de la texture il correspond. Nous attribuons une coordonnée de texture à chaque sommet d’un polygone et elle sera ensuite interpolée entre tous les fragments de ce polygone. Les coordonnées de texture vont généralement de 0 à 1 sur les axes x et y, comme indiqué dans l’image ci-dessous :

[![coordonnées de texture][1]][1]

Les coordonnées de texture de ce triangle ressembleraient à ceci :

GLfloat texCoords[] = {
    0.0f, 0.0f,  // Lower-left corner  
    1.0f, 0.0f,  // Lower-right corner
    0.5f, 1.0f   // Top-center corner
};

Mettez ces coordonnées dans VBO (vertex buffer object) et créez un nouvel attribut pour le shader. Vous devriez déjà avoir au moins un attribut pour les positions des sommets, alors créez-en un autre pour les coordonnées de texture.


Générer des textures

La première chose à faire sera de générer un objet texture qui sera référencé par un ID qui sera stocké dans un int non signé texture :

GLuint texture;
glGenTextures(1, &texture); 

Après cela, il doit être lié afin que toutes les commandes de texture suivantes configurent cette texture :

glBindTexture(GL_TEXTURE_2D, texture); 

Chargement de l’image

Pour charger une image, vous pouvez créer votre propre chargeur d’image ou vous pouvez utiliser une bibliothèque de chargement d’image telle que [SOIL][2] (Simple OpenGL Image Library) en c++ ou [TWL’s PNGDecoder][3] en java.

Un exemple de chargement d’image avec SOIL serait :

int width, height;
unsigned char* image = SOIL_load_image("image.png", &width, &height, 0, SOIL_LOAD_RGB); 

Vous pouvez maintenant affecter cette image à l’objet texture :

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);

Après cela, vous devez dissocier l’objet texture :

glBindTexture(GL_TEXTURE_2D, 0); 

Paramètre Wrap pour les coordonnées de texture

Comme vu ci-dessus, le coin inférieur gauche de la texture a les coordonnées UV (st) (0, 0) et le coin supérieur droit de la texture a les coordonnées (1, 1), mais les coordonnées de texture d’un maillage peuvent être en n’importe quelle gamme. Pour gérer cela, il doit être défini comment les coordonnées de texture sont enveloppées dans la texture.

Le paramètre d’enveloppement pour la coordonnée de texture peut être défini avec glTextureParameter en utilisant GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T et GL_TEXTURE_WRAP_R .

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

Les paramètres possibles sont :

  • GL_CLAMP_TO_EDGE provoque le serrage des coordonnées de texture dans la plage [1/2N, 1 - 1/2N], où N est la taille de la texture dans la direction.

  • GL_CLAMP_TO_BORDER fait la même chose que GL_CLAMP_TO_EDGE, mais dans les cas de serrage, les données de texel récupérées sont remplacées par la couleur spécifiée par GL_TEXTURE_BORDER_COLOR.

  • GL_REPEAT provoque l’ignorance de la partie entière de la coordonnée de texture. La texture est carrelée.

[![répéter la texture][4]][4]

  • GL_MIRRORED_REPEAT : si la partie entière de la coordonnée de texture est paire, alors elle est ignorée. Contrairement à, si la partie entière de la coordonnée de texture est impaire, alors la coordonnée de texture est définie sur 1 - frac(s). fract(s) est la partie fractionnaire de la coordonnée de texture. Cela provoque la mise en miroir de la texture toutes les 2 fois.

[![texture miroir][5]][5]

  • GL_MIRROR_CLAMP_TO_EDGE provoque la répétition de la coordonnée du texte comme pour GL_MIRRORED_REPEAT pour une répétition de la texture, à quel point la coordonnée est fixée comme dans GL_CLAMP_TO_EDGE.

Notez que la valeur par défaut pour GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T et GL_TEXTURE_WRAP_R est GL_REPEAT.


Appliquer des textures

La dernière chose à faire est de lier la texture avant l’appel au dessin :

glBindTexture(GL_TEXTURE_2D, texture);

[1] : http://i.stack.imgur.com/88Nw6.png [2] : http://www.lonesock.net/soil.html [3] : http://wiki.lwjgl.org/wiki/Loading_PNG_images_with_TWL’s_PNGDecoder [4] : https://i.stack.imgur.com/uI0Jt.png [5] : https://i.stack.imgur.com/6ZyNw.png

Texture et Framebuffer

Vous pouvez attacher une image dans une texture à un framebuffer, de sorte que vous puissiez effectuer le rendu directement sur cette texture.

glGenFramebuffers (1, &framebuffer);
glBindFramebuffer (GL_FRAMEBUFFER, framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER,
                       GL_COLOR_ATTACHMENT0,
                       GL_TEXTURE_2D,
                       texture,
                       0);

Remarque : vous ne pouvez pas lire et écrire à partir de la même texture dans la même tâche de rendu, car cela appelle un comportement indéfini. Mais vous pouvez utiliser : glTextureBarrier() entre les appels de rendu pour cela.

Lire les données de texture

Vous pouvez lire les données de texture avec la fonction glGetTexImage :

char *outBuffer = malloc(buf_size);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);

glGetTexImage(GL_TEXTURE_2D,
              0,
              GL_RGBA,
              GL_UNSIGNED_BYTE,
              outBuffer);

Remarque : le type et le format de texture ne sont donnés qu’à titre d’exemple et peuvent être différents.

Utiliser des textures dans les shaders GLSL

Le vertex shader n’accepte que les coordonnées de texture comme attribut de vertex et transmet les coordonnées au fragment shader. Par défaut, il garantira également que le fragment recevra la coordonnée correctement interpolée en fonction de sa position dans un triangle :

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texCoordIn;

out vec2 texCoordOut;

void main()
{
    gl_Position = vec4(position, 1.0f);
    texCoordOut = texCoordIn;
}

Le fragment shader accepte alors la variable de sortie texCoord comme variable d’entrée. Vous pouvez ensuite ajouter une texture au fragment shader en déclarant un uniform sampler2D. Pour échantillonner un fragment de la texture, nous utilisons une fonction intégrée “texture” qui a deux paramètres. La première est la texture à partir de laquelle nous voulons échantillonner et la seconde est la coordonnée de cette texture :

in vec2 texCoordOut;

out vec4 color;

uniform sampler2D image;

void main()
{
    color = texture(image, texCoordOut);
}

Notez que image n’est pas l’identifiant direct de la texture ici. C’est l’identifiant de l’unité de texture qui sera échantillonnée. À leur tour, les textures ne sont pas directement liées aux programmes ; ils sont liés à des unités de texture. Ceci est réalisé en rendant d’abord l’unité de texture active avec glActiveTexture, puis en appelant glBindTexture affectera cette unité de texture particulière. Cependant, puisque l’unité de texture par défaut est l’unité de texture “0”, les programmes utilisant une texture peuvent être simplifiés en omettant cet appel.

Utiliser les PBO

Si vous liez un tampon à GL_PIXEL_UNPACK_BUFFER alors le paramètre data dans glTexImage2D est un décalage dans ce tampon.

Cela signifie que glTexImage2D n’a pas besoin d’attendre que toutes les données soient copiées de la mémoire de l’application avant de pouvoir revenir, ce qui réduit la surcharge du thread principal.

glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glBufferData(GL_PIXEL_UNPACK_BUFFER, width*height*3, NULL, GL_STREAM_DRAW);
void* mappedBuffer = glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);

//write data into the mapped buffer, possibly in another thread.
int width, height;
unsigned char* image = SOIL_load_image("image.png", &width, &height, 0, SOIL_LOAD_RGB);
memcpy(mappedBuffer, image, width*height*3);
SOIL_free_image(image);

// after reading is complete back on the main thread
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);

Mais là où les PBO brillent vraiment, c’est lorsque vous devez lire le résultat d’un rendu dans la mémoire de l’application. Pour lire des données de pixel dans un tampon, liez-le à GL_PIXEL_PACK_BUFFER, puis le paramètre de données de glGetTexImage sera un décalage dans ce tampon :

glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, buf_size, NULL, GL_STREAM_COPY);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);

glGetTexImage(GL_TEXTURE_2D,
              0,
              GL_RGBA,
              GL_UNSIGNED_BYTE,
              null);
//ensure we don't try and read data before the transfer is complete
GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

// then regularly check for completion
GLint result;
glGetSynciv(sync, GL_SYNC_STATUS, sizeof(result), NULL, &result);
if(result == GL_SIGNALED){
    glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
    void* mappedBuffer = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);

    //now mapped buffer contains the pixel data

    glUnmapBuffer(GL_PIXEL_PACK_BUFFER);

}