• Facile

Ce cours est visible gratuitement en ligne.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 08/01/2013

Les textures

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Maintenant que nous savons faire des objets en 3D, en couleur certes mais un peu moches il faut l'avouer :lol: , il est temps de les habiller grâce aux textures. Nous verrons donc dans ce chapitre les rudiments du texturing, comment charger une texture et l'appliquer sur un objet. De quoi nous ouvrir la voie vers des scènes 3D de plus en plus réalistes.

Charger une texture

Grâce à notre choix d'utiliser OpenGL avec la SDL, vous allez voir que la phase de chargement des textures nous sera hautement simplifiée.

Mais avant même de pouvoir charger une quelconque texture, encore faut-il en avoir... Pour cela je vous propose de travailler avec ce pack de textures hautes résolutions. Il est assez volumineux (124 Mo) mais assez complet et contient des textures photoréalistes classées dans diverses catégories : sols, caisses, végétaux, métaux, pierres, etc.

Image utilisateur

Exemples de textures incluses dans le pack

Format et taille

Nous utiliserons principalement deux formats pour les textures : .jpg et .png.
Le format .jpg est parfait pour les textures, car il donne les plus petites tailles de fichier sur des images complexes (comme les textures photoréalistes).
Le format .png (24 bits) quant à lui est utilisé, car il gère très bien la transparence.

La largeur et la hauteur des textures doivent impérativement être des puissances de 2 (64,128,512,1024). En effet si elles ne le sont pas, les textures seront de toute façon redimensionnées en interne pour respecter cette contrainte et vous risquez donc de perdre inutilement en qualité.

En terme de qualité visuelle, plus la résolution de la texture est grande, meilleur est le résultat. Le pack que je vous fournis contient principalement des textures haute-résolution (512x512) avec lesquelles vous n'aurez pas de mal à avoir des rendus de meilleure qualité que Half-Life premier du nom :soleil: (effets de lumière mis à part pour l'instant).

Utiliser SDL_Image pour charger une texture

Le code nécessaire pour créer une texture OpenGL à partir d'un tableau de pixels n'est pas extrêmement compliqué. Cependant la phase la plus pénible est le chargement d'un fichier image (.jpg, .bmp, .tga ou autre).
Heureusement pour nous SDL_Image est là ; nous aurons simplement à l'utiliser pour qu'elle nous retourne une SDL_Surface à partir d'un nom de fichier.

Une fois cette surface créée, elle doit être retournée verticalement car SDL et OpenGL n'ont pas les mêmes conventions. Vous avez dû le remarquer lors du chapitre sur les transformations car le (0,0) en OpenGL était en bas à gauche alors que celui en SDL (comme indiqué dans le cours de M@teo) est en haut à gauche.

La dernière chose à faire est de convertir le tableau de pixels contenus dans la surface en texture OpenGL par des appels OpenGL appropriés.

Oh là là ! Ça a l'air compliqué tout ça... Image utilisateur :( Je vais réussir à faire ça moi ?

Savoir coder vous-mêmes cette phase n'est pas nécessaire pour la compréhension de la suite du chapitre. Le plus important est l'utilisation des textures créées. Nous reviendrons plus en détail sur les appels en question lorsque nous verrons les textures procédurales mais pour l'instant je vous donne tout ça sur un plateau d'argent, voilà :

Téléchargez le code pour charger une texture (3 Ko)

Dans l'archive, je vous fournis deux fichiers : sdlglutils.h et sdlglutils.cpp. J'ai choisi ce nom car j'y rajouterai petit à petit des fonctions utiles pour ce tuto qui ne sont là que pour nous simplifier la vie mais que vous auriez pu faire vous-mêmes avec un peu plus de connaissances.

La fonction qui nous intéresse pour l'instant est loadTexture qui s'utilise très simplement :

#include "sdlglutils.h"
GLuint identifiant_texture = loadTexture("ma_texture.jpg");

Si vous êtes curieux vous pouvez regarder le code derrière mais il n'a rien de compliqué, juste ce que je vous ai expliqué plus haut (chargement de l'image, retournement, et création texture OpenGL).

Le type renvoyé est GLuint soit l'équivalent d'un unsigned int. Cependant je n'utiliserai jamais le type unsigned int pour les textures pour ne pas être tenté dans mon code de faire des calculs dessus, en effet ce nombre retourné a une signification bien précise : c'est l'identifiant de la texture OpenGL créée. À chaque fois que nous voudrons utiliser cette texture, il suffira d'utiliser cet identifiant.

Activer le texturing

Comme nous avons déjà créé des scènes en 3D sans texture nous savons que le texturing n'est pas activé par défaut. Pour l'activer il suffit donc d'appeler :

glEnable(GL_TEXTURE_2D);

Voilà nous sommes prêts, nous savons charger une texture, nous savons activer le texturing. Maintenant voyons comment, en reprenant notre cube 3D du chapitre précédent, appliquer une texture sur un objet.

Plaquage de texture

Partons du code que nous avions pour définir la première face de notre cube :

glBegin(GL_QUADS);
glVertex3d(1,1,1);
glVertex3d(1,1,-1);
glVertex3d(-1,1,-1);
glVertex3d(-1,1,1);
glEnd();

Nous allons utiliser la texture « stainedglass05.jpg » que vous trouverez dans le pack dans la catégorie window (ou dans l'archive en fin de chapitre).

Commençons donc par la charger au lancement de notre application comme expliqué précédemment :

GLuint texture1; //en variable globale

int main(int argc, char *argv[])
{
    // ... lancement de l'application
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);
    texture1 = loadTexture("stainedglass05.jpg"); // pendant l'initialisation d'OpenGL, avant la boucle d'affichage
    //... boucle d'affichage et de gestion des événements
}

Dans le code du dessin, avant de définir les vertices de notre première face texturée, il nous faut dire à OpenGL que l'on veut utiliser cette texture en appelant glBindTexture avec l'identifiant de notre texture :

glBindTexture(GL_TEXTURE_2D, texture1);

Et maintenant nous allons réaliser le plaquage proprement dit de la texture sur la face en faisant correspondre à chaque vertex composant notre face une coordonnée sur la texture comme représenté sur le schéma ci-dessous :

Image utilisateur

Correspondance coordonnées texture / coordonnées réelles

Pour définir les coordonnées du vertex nous savons utiliser glVertex, eh bien pour définir les coordonnées de texture nous utiliserons :

glTexCoord2d (double x_texture, double y_texture);

L'espace de coordonnées sur la texture est en 2D, le coin en bas à gauche a les coordonnées (0,0) et le coin en haut à droite est en (1,1), quelque soit la taille en pixels de l'image.

Ici je vais commencer par définir le vertex (1,1,1) auquel je veux plaquer le coin haut gauche de la texture soit (0,1).
Je fais donc :

glBegin(GL_QUADS);
    glTexCoord2d(0,1);  glVertex3d(1,1,1);

En continuant avec les autres sommets de la face cela donne donc le code complet suivant :

glBindTexture(GL_TEXTURE_2D, texture1);
    glBegin(GL_QUADS);
    glTexCoord2d(0,1);  glVertex3d(1,1,1);
    glTexCoord2d(0,0);  glVertex3d(1,1,-1);
    glTexCoord2d(1,0);  glVertex3d(-1,1,-1);
    glTexCoord2d(1,1);  glVertex3d(-1,1,1);
    glEnd();

Erreurs fréquentes

Une mauvaise utilisation de glTexCoord2d (mauvaises coordonnées, oublié de redéfinir glTexCoord2d pour le vertex suivant) donne lieu à de drôles de résultats :

Image utilisateur

Erreur en oubliant de redéfinir glTexCoord2d pour le dernier vertex

:waw: :p Ne rigolez pas ces erreurs sont fréquentes au début et je suis prêt à parier que ça vous arrivera au moins une fois :lol: . Vous saurez au moins d'où vient le problème...

Cas d'un triangle

Dans les jeux vidéos, la primitive de base la plus utilisée est le triangle (nous verrons pourquoi quand nous importerons des models 3D). Le plaquage de texture fonctionne de la même manière en ne choisissant que 3 points sur la texture, points qui ne sont donc pas forcément des coins de l'image.

glBindTexture(GL_TEXTURE_2D, texture2);
    glBegin(GL_TRIANGLES);
    glTexCoord2d(0,0);      glVertex3d(1,1,-1);
    glTexCoord2d(1,0);      glVertex3d(-1,1,-1);
    glTexCoord2d(0.5,1);    glVertex3d(0,0,1);
    glEnd();
Image utilisateurImage utilisateur

Utilisation d'une partie de la texture

Nous venons de le voir dans le cas du triangle, il n'est pas obligatoire d'utiliser toute la texture. C'est une pratique souvent utilisée pour regrouper toutes les textures pour un seul objet dans un seul fichier, comme par exemple ici la skin de Tommy Vercetti dans GTA:Vice City :

Image utilisateur

Texture du héros (une des skins possibles) de GTA:Vice City

Il suffit alors, à coup de glTexCoord2d bien pensés, de délimiter la zone de la texture que l'on souhaite utiliser pour la face courante :

glBindTexture(GL_TEXTURE_2D, texture3);
    glBegin(GL_QUADS);
    glTexCoord2d(0,1);  glVertex3d(1,1,1);
    glTexCoord2d(0,0);  glVertex3d(1,1,-1);
    glTexCoord2d(0.33,0);  glVertex3d(-1,1,-1);
    glTexCoord2d(0.33,1);  glVertex3d(-1,1,1);
    glEnd();
Image utilisateurImage utilisateur

Texture répétitive

Jusqu'à présent nous appliquions tout ou une partie de la texture sur nos objets. Mais si on essaye de créer un sol (un simple carré de 20x20) dans la scène avec la même technique on obtient le résultat suivant :

glBindTexture(GL_TEXTURE_2D, texture4);
    glBegin(GL_QUADS);
    glTexCoord2i(0,0);      glVertex3i(-10,-10,-1);
    glTexCoord2i(1,0);     glVertex3i(10,-10,-1);
    glTexCoord2i(1,1);    glVertex3i(10,10,-1);
    glTexCoord2i(0,1);     glVertex3i(-10,10,-1);
    glEnd();
Image utilisateurImage utilisateur

Comme vous le voyez la texture n'est pas faite pour être étalée sur une si grande surface. Nous devons donc faire en sorte qu'elle se répète !
Dans les exemples que nous avons vus nous n'utilisions que des coordonnées entre 0 et 1. Mais en réalité l'espace de coordonnées de la texture n'a pas de limite :

Image utilisateur

Espace des coordonnées de texture

Quand nous considérions l'image d'origine, nous nous restreignions à une partie de cet espace. Mais ici nous voulons faire répéter la texture 10 fois par exemple donc nous allons prendre des coordonnées entre 0 et 10 tout simplement !

Et en effet en modifiant le code en conséquence :

glBindTexture(GL_TEXTURE_2D, texture4);
    glBegin(GL_QUADS);
    glTexCoord2i(0,0);      glVertex3i(-10,-10,-1);
    glTexCoord2i(10,0);     glVertex3i(10,-10,-1);
    glTexCoord2i(10,10);    glVertex3i(10,10,-1);
    glTexCoord2i(0,10);     glVertex3i(-10,10,-1);
    glEnd();

Nous obtenons un bien meilleur résultat visuel :

Image utilisateur

Texture du sol répétée 10 fois

Les couleurs

Je n'ai volontairement pas mentionné les couleurs depuis le début de ce chapitre pour rester concentré sur la nouveauté du moment : les textures. Mais nos bonnes vielles couleurs ne sont pas mortes pour autant.

Combiner texture et couleur

Nous savons depuis le début de ce tuto définir la couleur des vertices à venir avec glColor3ub. Rien ne nous interdit de continuer à l'utiliser en plus de ce que nous venons d'apprendre sur les textures. La définition complète d'un vertex peut donc maintenant contenir jusqu'à 3 lignes :

  • définition de la couleur avec glColor3ub(facultatif) ;

  • définition des coordonnées sur la texture avec glTexCoord2d(obligatoire sur utilisation d'une texture) ;

  • définition des coordonnées spatiales du vertex avec glVertex3d.

Il n'est bien sûr pas obligatoire de redéfinir glColor3ub à chaque fois si l'on ne souhaite pas changer de couleur.
Appliqué au sol vu précédemment, en affectant des couleurs à chaque sommet on obtient donc :

glBindTexture(GL_TEXTURE_2D, texture4);
    glBegin(GL_QUADS);
    glColor3ub(255,0,0); //Nouveau
    glTexCoord2i(0,0);
    glVertex3i(-10,-10,-1);
    glColor3ub(0,255,0); //Nouveau
    glTexCoord2i(10,0);
    glVertex3i(10,-10,-1);
    glColor3ub(255,255,0); //Nouveau
    glTexCoord2i(10,10);
    glVertex3i(10,10,-1);
    glColor3ub(255,0,255); //Nouveau
    glTexCoord2i(0,10);
    glVertex3i(-10,10,-1);
    glEnd();
Image utilisateurImage utilisateur

Comme vous le voyez, la couleur agit comme un filtre et vient donner une teinte locale à la texture.
Tout semble aller très bien jusqu'à ce que l'on décide d'afficher autre chose juste après avoir défini notre sol, un cube texturé par exemple :

Image utilisateur

Comme vous le voyez le cube est lui aussi affecté par la dernière couleur utilisée. C'est tout à fait logique car glColor3ub par définition est utilisé pour tous les vertex définis à la suite (jusqu'au prochain appel à glColor3ub).

Il faut donc utiliser une couleur neutre qui, appliquée comme filtre, ne viendra pas modifier la couleur de la texture. Et cette couleur n'est autre que... le blanc !

Ainsi, pour en quelque sorte « annuler l'effet des couleurs », il suffit d'utiliser le blanc comme prochaine couleur avec un appel à :

glColor3ub(255,255,255);

Et en effet en interposant un appel à glColor3ub(255,255,255); entre la définition du sol et celle du cube, on obtient bien un cube vierge de tout effet de couleur :

//... début du sol
    glColor3ub(255,0,255);
    glTexCoord2i(0,10);
    glVertex3i(-10,10,-1);
    glEnd(); //fin du sol

    glColor3ub(255,255,255); //on enlève la couleur

    glBegin(GL_QUADS); //début du cube
    glTexCoord2d(0,1);
    glVertex3d(1,1,1);
    //... fin du cube
Image utilisateurImage utilisateur

Voilà vous savez maintenant les précautions qu'il faut prendre lorsque l'on souhaite combiner dans un même code les couleurs et les textures. Rassurez-vous, il ne vous sera pas rare d'oublier de repasser en blanc de temps en temps et vous créerez souvent de jolis effets de couleur involontairement. ;)

Qui aurait cru que nous passerions aussi rapidement d'objets moches mais joliment colorés à de somptueux objets texturés ?!
Comme vous avez pu le voir il n'y a vraiment rien de sorcier dans l'application des textures, il suffit de bien savoir affecter à chaque vertex les bonnes coordonnées sur la texture et le tour est joué.
Le meilleur moyen de vous assurer que vous avez bien compris est de vous entraîner à réaliser une petite scène en 3D avec des textures, notamment une caisse de 4x2x2 avec la texture de caisse (voir caisse.jpg dans zip plus bas) utilisée dans ce chapitre.

Je vous en ai fait une rapidement dont vous pouvez télécharger le code plus bas :

Image utilisateur

Laissez libre cours à votre imagination et n'hésitez pas à poster vos créations dans les commentaires du chapitre ou sur le forum.

Téléchargez les textures utilisées et un exemple de scène texturée (482 Ko)

Téléchargez la vidéo au format avi/Xvid (1.10 Mo)

Dans le prochain chapitre nous verrons comment créer des formes un peu plus complexes qu'un simple cube ou une pyramide, toujours dans le but d'enrichir le contenu de nos scènes.

Exemple de certificat de réussite
Exemple de certificat de réussite