Aller au menu - Aller au contenu

Icône Les textures

Par Kayl
Mise à jour : 27/06/2009
6 428 visites depuis 7 jours, dont 582 sur ce chapitre classé 35/786
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.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

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


Que vous le téléchargiez ou non je fournirai quand même les textures utilisées dans l'exemple en fin de chapitre. Vous pouvez donc vous en contenter.


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à :



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 :

Code : C++
1
2
#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 :
Code : C++
1
glEnable(GL_TEXTURE_2D);


Vous pouvez avoir envie dans vos scènes de faire cohabiter des objets texturés avec des objets non texturés (pourquoi pas après tout...). Pour ce faire il suffit de désactiver le texturing temporairement avec : Code : C++
1
glDisable(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 :

Code : C++
1
2
3
4
5
6
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 :
Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 :

Code : C++
1
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 :
Code : C++
1
2
glBegin(GL_QUADS);
    glTexCoord2d(0,1);  glVertex3d(1,1,1);


glTexCoord2d, comme glColor3ub, s'applique à tous les vertices définis par la suite. Il ne faut donc pas oublier d'y faire à nouveau appel avant chaque vertex.


En continuant avec les autres sommets de la face cela donne donc le code complet suivant :
Code : C++
1
2
3
4
5
6
7
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();


Il n'est pas possible de changer de texture en cours de définition d'une face. Pour appliquer une texture différente à la face suivante il faut donc terminer le bloc de vertices par un appel à glEnd(), changer la texture puis définir la prochaine face :
Code : C++
1
2
3
4
5
6
7
8
glBindTexture(GL_TEXTURE_2D, texture1);
    glBegin(GL_QUADS); //première face
    //définition des vertices
    glEnd();
    glBindTexture(GL_TEXTURE_2D, texture2); //changement de texture
    glBegin(GL_QUADS); //deuxième face
    //définition des vertices
    glEnd();

Ce n'est bien sûr pas nécessaire si vous souhaitez garder la même texture.


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.

Code : C++
1
2
3
4
5
6
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 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 :

Code : C++
1
2
3
4
5
6
7
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 utilisateur


Comme vous le voyez, la texture utilisée ici n'est pas un carré (384x128) et pourtant le coin en haut à droite a toujours les coordonnées (1,1). Les coordonnées d'un point sur la texture donnent donc une information relative à la taille totale de l'image. Ici on a voulu découper une partie de 128 pixels de large sur les 384 totaux soit un rapport de 1/3 (0.33).

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 :

Code : C++
1
2
3
4
5
6
7
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 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 :

Code : C++
1
2
3
4
5
6
7
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


Toutes les textures ne sont pas faites pour être répétées. Il faut en effet qu'elles soient conçues spécialement pour les bords correspondent quand plusieurs répétitions de l'image sont mises bout à bout. Dans le pack de textures, vous pouvez être quasiment sûrs que toutes les textures de sol, de mur, de plafond, d'herbe, de rocher sont susceptibles d'être répétées.

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 :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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 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 à :
Code : C++
1
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 :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//... 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 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.





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.
Chapitre précédent Sommaire Chapitre suivant

Partager

32 commentaires pour "Les textures"
Note moyenne : 3.45 / 4 (138 votes)
Pseudo Commentaire
Hors ligne Kévindu13 # Posté le 05/01/2012 à 01:28:42

La compilation refuse de marcher ([Linker error] undefined reference to `loadTexture(char const*, int)'), c'est décevant, je vais chercher un meilleur tuto
Hors ligne Ludavid21 # Posté le 26/02/2012 à 19:34:13
UNIX in C
Avatar

Avis : Bon

Le lien vers les textures est mort... Possible de le remettre svp?

90% of teens today would die if Facebook was completely destroyed. If you are one of the 10% that would be laughing, copy and paste this to your signature.
 
Hors ligne ab_souss # Posté le 04/03/2012 à 23:46:18
Avatar

Chers amis, pour faire marcher tout ça, n'oubliez pas d'inclure sdlglutils.cpp et sdlglutils.h dans votre projet, puis d'include windows.h et stio.h dans le fichier sdlglutils.cpp et enfin vous n'avez plus qu'à inclure sdlglutils.cpp dans le main.cpp de votre programme.

J'ai un peu galéré pour le faire marcher, mais en incluant tout ça, cela passe impec'. ;)
Hors ligne azerty1607 # Posté le 10/03/2012 à 18:49:00

Avis : Mitigé

bonjour
j'ai 15 ans
c'est la premiere fois que je viens ici
j'ai un probleme au niveau du chargement de la texture je reçois la ligne suivante :

undefined reference to `loadTexture(char const*, bool)'
je vous remercie
Hors ligne marshiell # Posté le 18/05/2012 à 18:52:12
return 0;
Avatar

Avis : Très bon

Le lien des textures est mort :(

Nouvel hébergeur ! http://linkocraft.com/
Mes jeux 3D C++ : Guerilla 2, Guerilla, Rollo et Rocketzor
 

Voir tous les commentaires
Ce tutoriel a été corrigé par les zCorrecteurs.