Programmation Graphique Temps Réel (Semestre I)
Project Foxenstein
Introduction à l'utilisation des textures sous OpenGL.
Ce document contient le strict minimum pour commencer à manipuler les textures sous OpenGL.
Introduction à l'utilisation des textures avec OpenGL.
On peut trouver toutes sortes de tutoriaux concernant le Texture Mapping sous OpenGL un peu partout sur le Web. Mais comme je suis sympa je vais vous filer quelques tuyaux en français dans le texte.
Le Texture Mapping est une opération qui consiste à venir modifier les fragments générés par OpenGL en fonction de données calculées à partir de celles contenues dans un tableau à 2 dimensions (enfin en général, car il est également possible d'utiliser des tableaux à 1 et à 3 dimensions).
Vous vous demandez peut-être pourquoi je parle de tableau à N dimensions plutôt que de parler d'image. La réponse est simple, toutes les textures que vous serez éventuellement amenés à utiliser ne sont pas forcément issues d'images. Certaines de ces textures correspondront par exemple à des normales, que vous utiliserez par exemple pour faire du Bump Mapping où encore contiendront des valeurs précalculées d'éclairage vous permettant de réaliser ce que l'on appelle du Light Mapping, etc...
Ces quelques mots d'introduction ayant maintenant fini le trajet entrant par l'une de vos oreilles et sortant par l'autre, il est temps de parler un peu technique. Il y a deux grandes étapes à distinguer lors de l'utilisation de textures sous OpenGL:
- le chargement des textures,
- l'application des textures.
1. Chargement d'une texture.
Le fragment de code ci-dessous donne l'ensemble des instructions
nécessaires pour charger une texture à partir d'un tableau
à deux dimensions de taille imageWidth
par
imageHeight
dont les données sont des triplets
d'unsigned char
situés les uns après les autres en
mémoire à l'adresse imageData
:
unsigned int uploadTexture(int imageWidth, int imageHeight, unsigned char imageData[]) { unsigned int texID; glGenTextures(1, &texID); glBindTexture(GL_TEXTURE_2D, texID); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, imageWidth, imageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData); return texID; }
Quelques explications s'imposent:
-
glGenTextures(1, &texID);
Si vous désirez utiliser plusieurs textures il est nécessaire d'assigner un identifiant à chacune d'entres elles. Ces identifiants sont de simples
unsigned int
non-nuls. En théorie, rien ne vous empêche de les assigner à la main, mais cela peut se révéler pénible à la longue. OpenGL propose donc un mécanisme permettant d'automatiser cette opération sous la forme de la fonctionglGenTextures
. Cette fonction prend en paramètres un nombre d'identifiants à générer et un tableau d'identifiants qui doit être alloué de façon à pouvoir contenir au moins le nombre d'identifiants demandés. Dans notre cas on ne désire allouer qu'un seul identifiant, donc on se contente de passer l'adresse d'une variable de typeunsigned int
. -
glBindTexture(GL_TEXTURE_2D, texID);
Indique que toutes les opérations de textures suivants devront être appliqués sauvegardés avec l'identifiant
texID
. -
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
Cette instruction indique que les triplets de données sont situés les uns directement après les autres.
-
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
Nous considérerons ces quatre instructions comme des opérations magiques pour l'instant. Si vous tenez vraiment à en savoir plus, consultez soit le manuel soit l'OpenGL Programming Guide. Sachez néanmoins qu'elles définissent des paramètres indispensables au chargement de la texture, donc ne les oubliez pas.
-
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, imageWidth, imageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, imageData);
Cet appel charge effectivement les données de la texture. Nous ne nous intéresserons pas aux divers paramètres de cette fonction pour l'instant. Si vous désirez en savoir plus, il sera nécessaire une fois de plus d'aller vous documenter.
Notez bien que les dimensions du tableau
2D passé en paramètre à la fonction glTexImage2D
doivent
être des puissances de 2 (1, 2, 4, 8, 16, 32, etc...).
2. Placage d'une texture sur un triangle.
Tout d'abord, si l'on souhaite utiliser des textures 2D il faut
les activer en utilisant glEnable
avec l'argument
GL_TEXTURE_2D
:
glEnable(GL_TEXTURE_2D);
De même pour désactiver l'utilisation des textures on utilisera
la fonction glDisable
:
glDisable(GL_TEXTURE_2D);
Considérons un triangle dont les trois sommets sont A, B et C.
Comment faire pour spécifier quelle partie d'une texture on doit
appliquer à ce triangle ? Il faut d'abord connaître les coordonnées
de cette partie dans l'espace de l'image. La surface à texturer
étant un triangle, la zone utilisée dans la texture doit également
délimiter un triangle. On aura donc pour chacun des sommets de notre
triangle un point correspondant dans l'espace de notre texture.
On pourra alors spécifier ces coordonnées en utilisant la fonction
glTexCoord2f(float s, float t)
avant de soumettre
chacun des sommets comme le montre le fragment de code suivant:
// pA, pB, et pC sont 3 tableaux de 2 valeurs contenant // les positions des sommets A, B, et C. float pA[2], pB[2], pC[2]; // tA, tB, et tC sont 3 tableaux de 2 valeurs contenant // les coordonnées des points de l'espace de texture // correspondant au sommets A, B, et C. float tA[2], tB[2], tC[2]; glBegin(GL_TRIANGLES); glTexCoord2f(tA[0], tA[1]); glVertex2f(pA[0], pA[1]); glTexCoord2f(tB[0], tB[1]); glVertex2f(pB[0], pB[1]); glTexCoord2f(tC[0], tC[1]); glVertex2f(pC[0], pC[1]); glEnd();
Notez bien que les coordonnées de texture sont normalisées, c'est-à-dire que le bord gauche de la texture correspond aux points dans l'espace texture tels que s=0 et que le bord droit correspond aux points tels que s=1. N'essayez donc PAS d'utiliser des coordonnées de texture dont les valeurs correspondrait aux positions en pixels dans l'image de départ.
On sait maintenant charger une texture et définir la portion de
texture à appliquer à une primitive. Il est maintenant temps d'apprendre
à spécifier la texture que l'on souhaite utiliser. C'est extrêmement simple.
Pour utiliser la texture dont l'identifiant est texID
, il
suffit d'utiliser le fragment de code suivant:
glBindTexture(GL_TEXTURE_2D, texID);
Notez bien que la fonction glBindTexture
ne PEUT pas être appelée à l'intérieur d'un glBegin
/glEnd
.
Enfin, avant de commencer à dessiner une quelconque primitive, il faut spécifier la façon dont on veut interagir avec les fragments sortant de l'étage de colorization/éclairage.
Si l'on veut tout simplement remplacer la couleur venant de cet étage par la couleur de la texture alors on emploiera le code suivant:
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
Si l'on veut au contraire que la couleur du fragment soit multipliée par la valeur de la texture (par exemple si on utilise OpenGL pour calculer l'illumination et que l'on veuille combiner le résultat avec une texture de surface) alors on utilisera:
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
Deux autres modes sont disponibles par défaut: les modes GL_BLEND
et GL_DECAL
. Pour en savoir plus sur ces deux modes, reportez vous à
la documentation.
L'image ci-dessous montre:
- en haut, à gauche, un cube non-texturé, éclairé par une source de lumière,
- en haut à droite, le même cube, non-éclairé, texturé, avec un env mode de
GL_REPLACE
, - en bas à gauche, le cube, éclairé, avec un env mode de
GL_REPLACE
, - en bas à droite, le cube, éclairé, avec un env mode de
GL_MODULATE
.

Le fragment de code suivant donne un exemple complet d'application de texture à un triangle avec gestion de la lumière:
// ==[ VARIABLES ]========================================= // l0_pos contient la position de la source 0. // l0_amb contient la couleur ambiente de la source 0. // l0_dif contient la couleur diffuse de la source 0. // l0_spe contient la couleur speculaire de la source 0. // l0_shi contient l'exposant de phong de la source 0. float l0_pos[4]; float l0_amb[4]; float l0_dif[4]; float l0_spe[4]; float l0_shi; // abc_amb contient la composante ambiente du material du triangle ABC // abc_dif contient la composante diffuse du material du triangle ABC // abc_spe contient la composante spéculaire du material du triangle ABC float abc_amb[4]; float abc_dif[4]; float abc_spe[4]; // pA, pB, et pC sont 3 tableaux de 2 valeurs contenant // les positions des sommets A, B, et C. float pA[2], pB[2], pC[2]; // tA, tB, et tC sont 3 tableaux de 2 valeurs contenant // les coordonnées des points de l'espace de texture // correspondant au sommets A, B, et C. float tA[2], tB[2], tC[2]; // N est un tableau de 3 valeurs représentant les // 3 compostantes de la normale à ABC. float N[3]; // texID est l'identifiant de la texture appliquée à ABC. unsigned int texID; // ==[ CODE ]============================================== glLightfv(GL_LIGHT0, GL_POSITION, l0_pos); glLightfv(GL_LIGHT0, GL_AMBIENT, l0_amb); glLightfv(GL_LIGHT0, GL_DIFFUSE, l0_dif); glLightfv(GL_LIGHT0, GL_SPECULAR, l0_spe); glLightf(GL_LIGHT0, GL_SHININESS, l0_spe); glEnable(GL_LIGHT0); glEnable(GL_LIGHTING); glEnable(GL_TEXTURE2D); glBindTexture(GL_TEXTURE_2D, texID); glBegin(GL_TRIANGLES); glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, abc_amb); glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, abc_dif); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, abc_spe); glNormal3f(N[0], N[1], N[2]); glTexCoord2f(tA[0], tA[1]); glVertex2f(pA[0], pA[1]); glTexCoord2f(tB[0], tB[1]); glVertex2f(pB[0], pB[1]); glTexCoord2f(tC[0], tC[1]); glVertex2f(pC[0], pC[1]); glEnd();
Conclusion
Après avoir lu ce document vous devriez être capable de faire le TP3 sans trop de difficultés. A vous de jouer.