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 queGL_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 parGL_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 pourGL_MIRRORED_REPEAT
pour une répétition de la texture, à quel point la coordonnée est fixée comme dansGL_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);
}