Free online content available in this course.

Got it!

Last updated on 12/5/13

Textures

Log in or subscribe for free to enjoy all this course has to offer!

Dans ce chapitre, nous allons faire un petit tour de tout ce qui touche aux textures et à leurs applications. Nous allons notamment passer en revue les différentes manières de texturer un modèle sous Irrlicht.

Le texture mapping en une leçon

Reprenons depuis le début en commençant par expliquer en quoi consiste le texture mapping (aussi appelé texturing). Le but de la manoeuvre est d'appliquer une texture (pour simplifier disons une image pour le moment) sur une surface (pas forcément plane), dans le but d'améliorer le réalisme d'une scène virtuelle. Par exemple, à gauche une scène sans textures, et à droite, la même avec textures :

Image utilisateurImage utilisateur

On peut voir que le mur derrière a vraiment l'apparence d'être en pierre, et si on y fait bien attention, on remarque que les mêmes pierres se répètent sans arrêt. C'est une des idées directrices du texture mapping, il vaut mieux répéter plusieurs fois une petite texture que d'appliquer une seule fois une grande. Tout simplement car une petite texture prendra moins de place en mémoire, ce qui permet un chargement plus rapide de la scène. Ou dans le cas d'un jeu en réseau par exemple, un téléchargement plus rapide du fichier.

Cela pose néanmoins une condition : il faut que la texture soit répétitive, c'est-à-dire que les raccords entre les répétitions ne doivent pas êtres visibles. Par exemple, à gauche une texture non répétitive, à droite une texture répétitive :

Image utilisateurImage utilisateur

Il est évident que si la texture n'est pas répétitive, on perd en quelque sorte tout le réalisme qu'on avait gagné en l'appliquant...

Une texture est composée de texels (mot qui vient de la compression de texture et element). Vous allez sans doute me demander pourquoi elles ne sont pas composées de pixels alors qu'il s'agit à la base d'images matricielles comme les autres...

Pourquoi elles ne sont pas composées de pixels alors qu'il s'agit à la base d'images matricielles comme les autres ?

Car il y a une différence très importante entre les pixels et les texels.Image utilisateur

Pour comprendre il faut se représenter son écran comme une énorme grille de pixels (ce qu'il est en fin de compte, du point de vue du rendu).

Partons du principe que la grille à droite représente l'écran.

Image utilisateurNous avons dit qu'une texture n'est rien d'autre qu'une image matricielle, donc elle aussi une grille, mais de texels (image à gauche).

Le problème va être que la grille de texels de la texture doit d'une manière ou d'une autre pouvoir être affichée dans la grille de pixels de l'écran (c'est le rendu).

Image utilisateurDans le cas merveilleux où chaque texel correspond à un pixel (image à droite), pas de problème, tout concorde.
Seulement ce cas très particulier n'arrive quasiment jamais.

Image utilisateurImaginons par exemple que vous décidiez de vous éloigner, les texels seront alors plus petits que les pixels, et un pixel contiendra plusieurs texels (image à gauche).

Image utilisateurAlors que si vous vous rapprochez, les texels grossiront jusqu'à devenir plus gros que les pixels, et il faudra alors plusieurs pixels pour contenir un seul texel (image à droite).

Se pose alors le problème suivant : comment déterminer la couleur finale du pixel s'il ne coïncide pas exactement avec un texel ? La solution consiste à appliquer un filtre, portant d'ailleurs souvent un nom barbare (filtre anisotrope, filtre bilinéaire, etc...).

Le but de ce chapitre n'étant pas tant d'expliquer les différentes méthodes de filtrage que le texturing, nous allons les laisser de côté pour le moment. Vous pouvez néanmoins tester les différences de rendu entre les différentes techniques en temps réel via cette petite application créée de toute pièce par votre serviteur : télécharger le pack.

Et si vous voulez en savoir plus, vous pouvez toujours vous mettre cet article sous la dent : Comprendre la 3D : le filtrage des textures.

Les textures, parlons en !

C'aurait pu être le nom d'une nouvelle campagne de communication gouvernementale, mais il ne s'agit que du titre de cette sous-partie qui a pour but d'expliquer de quoi est faite la classe ITexture, qui sert à contenir les textures sous Irrlicht. Pour commencer, il faut savoir que plusieurs choses caractérisent une texture sous Irrlicht :

La classe ITexture étant abstraite, ce n'est pas elle que nous allons instancier lors de la création d'une texture, mais une classe dérivée de ITexture. Les différences entre les différentes classes enfant de ITexture tiennent au fait qu'Irrlicht peut utiliser plusieurs API. La manipulation d'une texture sous OpenGL étant différente de la manipulation sous direct3D par exemple, les méthodes de ITexture sont redéfinies en fonction de l'API utilisée.

Le driver est exactement soumis aux mêmes contraintes. Etant donné que c'est par lui qu'on charge les textures, celles-ci gardent en mémoire le type du driver qui les a chargées. Enfin, en pratique il est peu utile de savoir tout ça, car il n'y a qu'un seul driver par application, qu'il ne change pas de type en cours d'exécution, et que l'interface ITexture nous permet de manipuler indifféremment des textures chargées à partir de drivers de types différents. :)

Il existe plusieurs formats d'encodage des couleurs possibles pour une texture. Le fait qu'une texture soit encodée dans un format plutôt qu'un autre dépend essentiellement de l'encodage de l'image à l'intérieur du fichier à partir duquel la texture est chargée, mais aussi du texture creation flag du driver (on en a parlé dans le 5ème chapitre de ce tutoriel ;) ).

Si vous n'y connaissez rien en profondeur de codage et que vous ne voyez pas la différence entre celles-ci, jetez donc un oeil à ce tuto : Les pixels avec SDL. La méthode permettant de savoir en quel format est encodée la texture est getColorFormat().

Chaque texture se voit attribuer un nom lors de sa création, qui correspond au nom de fichier à partir duquel elle est chargée. On peut récupérer ce nom à l'aide de la méthode getName(), mais on ne peut pas le modifier.

Lorsqu'une texture est chargée, il est possible qu'elle soit redimensionnée. Dans le cas d'une texture dont les dimensions ne sont pas des multiples de 2 par exemple. Dans ce cas, il peut être utile de connaître les dimensions originales de la texture. La méthode getOriginalSize() sert à ça. Sinon pour connaître la taille actuelle de la texture, il faut se servir de getSize().

Les niveaux de mip map d'une texture permettent d'améliorer l'affichage de celle-ci en réduisant son niveau de détail en fonction de sa distance. L'aspect technique de cette méthode est un peu trop complexe pour être développé ici, nous y reviendrons peut être dans un prochain chapitre. En attendant, vous pouvez toujours constater visuellement la différence grâce à cet article : Le mipmapping.

En règle générale, Irrlicht génère automatiquement des niveaux de mip map pour toutes les textures chargées. Mais il est possible que ces niveaux n'existent pas ou ne soient plus valables après une modification de la texture par exemple. Pour savoir si une texture possède des niveaux de mip map, il faut utiliser la méthode hasMipMaps(), qui renvoie true si la texture en a. Dans le cas contraire, on peut utiliser la méthode regenerateMipMapLevels() pour en générer de nouveaux.

De la bonne manière de texturer un modèle

Maintenant que nous avons vu ce que sont et à quoi servent les textures, reste à voir comment les appliquer. Mais commençons d'abord par mettre quelques petites choses au point.

Si vous avez déjà fait des applications 3D utilisant OpenGL, vous êtes sûrement passés (à un moment ou un autre) par le mode de déclaration directe des sommets. Celui-ci permet de déclarer plusieurs sommets d'affilée qui définissent au final des primitives de base (triangles, carrés, etc...). Par exemple :

glBegin(GL_QUADS);
    glVertex2d(-0.75,-0.75);
    glVertex2d(-0.75,0.75);
    glVertex2d(0.75,0.75);
    glVertex2d(0.75,-0.75);
glEnd();

L'un des avantages de ce mode est qu'il permet de déclarer les coordonnées de texture en même temps. On associe une coordonnée de texture à chaque sommet déclaré, et au final tout se retrouve là où il faut lors du rendu. Et bien grande nouvelle : vous pouvez oublier tout ça ! En effet vous ne trouverez pas ce mode de déclaration des sommets sous Irrlicht.

Mais pourquoi donc ? Encore un complot pour rendre l'informatique inaccessible et mystérieuse ?

Que nenni. Il y a en fait deux bonnes raisons à ce qu'Irrlicht n'intègre pas cette fonctionnalité :

  • Elle n'est proposée que par OpenGL, et pas par Direct3D. Etant donné qu'Irrlicht interface indifféremment les deux API, ç'aurait été un véritable casse-tête à implémenter.

  • Ce mode est un des (voir le) plus lents qui puissent exister en termes de performances.

Nous (re)verrons dans un prochain chapitre en quoi consistent les autres modes de déclaration des sommets (et comment ils sont implémentés sous Irrlicht), mais si vous êtes vraiment pressés, vous pouvez toujours vous faire les dents sur ces deux tutoriels :

Reste une question en suspens :

Comment fait-on alors pour déclarer les sommets et les coordonnées de texture qui vont avec ?

Et bien malheureusement (ou pas) nous n'allons pas voir comment le faire manuellement pour le moment. Parce que ça implique de connaître beaucoup de choses (qui peuvent se résumer à "savoir comment créer un scene node personnalisé"). Nous y reviendrons lors d'un prochain chapitre, mais en attendant... il va falloir vous en passer. Il existe néanmoins d'autres moyens d'appliquer des textures, le seul "souci" est qu'il faut passer par un modèle 3D.

Coordonnées de texture

La plupart (une grande majorité à vrai dire) des formats de fichiers 3D fonctionne sur le principe des coordonnées de texture. L'idée est la suivante : le modèle 3D en lui même est contenu dans un fichier (MD2, OBJ, etc...), et la texture qui le recouvre dans un autre (BMP, PNG, etc...). A chaque sommet du modèle sont associées 2 valeurs représentant des coordonnées en 2 dimensions. Ces coordonnées désignent un point sur la surface de la texture. L'aire délimitée sur la surface de la texture par tous les sommets d'une face du modèle représente exactement le morceau de la texture qui sera appliqué sur cette face.

Si vous jetez un oeil au modèle Sydney qu'on utilise depuis le début de ce tutoriel, vous vous apercevrez que c'est ce système qui est utilisé. Le modèle 3D en lui même est contenu dans un fichier MD2, et la texture qui le recouvre dans un banal fichier BMP. Basiquement, le code permettant d'assigner une texture à un mesh est le suivant :

sceneNode->setMaterialTexture( 0, driver->getTexture("le_nom_de_la_texture") );

Le 0 du premier paramètre indique le niveau de cette texture.
En effet, il est possible d'appliquer plusieurs niveaux de textures sur un mesh.

Tout en un

Il existe des formats de fichiers 3D qui contiennent le modèle et les textures. Là c'est que du bonheur, vous n'avez plus qu'à vous mettre les doigts de pieds en éventail et laisser Irrlicht faire le boulot. ^^ Lorsque vous chargez le fichier dans votre scène, le modèle et les textures sont chargées en même temps.

Le format .bsp utilisé pour les maps de Quake 3 est de ceux-là. Et d'ailleurs si vous avez fait attention, vous aurez remarqué que c'est également le format de la map qu'on a chargée lors du chapitre sur les événements. On ne le voit pas au premier coup d'oeil puisque le fichier est à la base au format .pk3 qui n'est rien de plus qu'un fichier .bsp compressé.

Plaquage répétitif

La dernière solution, qui fait un peu "roue de secours", est de plaquer répétitivement une texture sur le modèle. Pour ce faire il faut passer par le manipulateur de mesh. Il s'agit d'une classe qui, comme son nom l'indique, va nous permettre de... manipuler des meshs. La méthode à appeler est celle-ci : makePlanarTextureMapping (IMesh *mesh, f32 resolution)

Le premier paramètre est un pointeur vers le mesh à texturer, et le deuxième permet de définir le nombre de répétitions de la texture. Si vous avez bonne mémoire, vous devez vous souvenir qu'on a déjà rencontré cette fonction. Dans le chapitre sur l'éclairage, au moment de texturer la pièce. Je vous remets le code intéressant :

// La salle
scene::IAnimatedMesh *room = scenegraph->getMesh("room.3ds");
scene::IMeshSceneNode *Nroom = scenegraph->addMeshSceneNode(room->getMesh(0));
//On desactive la sensibilite a la lumiere
Nroom->setMaterialFlag(video::EMF_LIGHTING, true);
//On definit le nombre de repetition de la texture
scenegraph->getMeshManipulator()->makePlanarTextureMapping(room->getMesh(0), 0.04f);
//On choisit la texture
Nroom->setMaterialTexture( 0, driver->getTexture("rockwall.bmp") );

A dire vrai, la seule difficulté consiste à savoir quelle valeur passer au deuxième paramètre. Vous pouvez toujours tâtonner pour trouver la valeur qui vous plaît le plus. Plus ce chiffre est élevé, et plus la texture sera répétée. Par exemple, sur les images ci-dessous, les valeurs sont (de gauche à droite) 0.0004, 0.004, 0.04 :

Image utilisateurImage utilisateurImage utilisateur

La "formule" permettant de calculer le nombre de répétitions est la suivante :

longueur de la plus grande face de la bounding box du mesh * le nombre passé au deuxième paramètre = nombre de répétitions de la texture

Si vous ne savez pas ce qu'est une bounding box, pas de panique. On le verra dans un prochain chapitre. En attendant, imaginez-vous que c'est un grand rectangle qui englobe votre mesh. Donc, prenez en gros la longueur de votre mesh pour faire le calcul. ;)

Example of certificate of achievement
Example of certificate of achievement