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:

  1. le chargement des textures,
  2. 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 fonction glGenTextures. 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 type unsigned 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.