Jeux Libres
       
           

» Les Tutoriels » Création d'un jeu vidéo » Gestion des textures

Gestion des textures


Dans le chapitre précédent, nous avons rencontré des difficultés pour la gestion des textures. On aimerait que les textures ne puissent être chargées qu'une seule fois et qu'elles puissent être partagées entre plusieurs objets. De plus les dimensions des textures doivent être mémorisées.

Pour cela, nous allons créer une classe qui se chargera de gérer les textures.





Chapitre précédent     Sommaire     Chapitre suivant


Un peu de théorie


Ce que nous voulons



Nous voulons créer une classe qui contiendra toutes les textures de votre jeu vidéo. Les textures seront partagées entre tout les objets. Les textures ne seront chargées qu'une seule fois.

Comment nous allons nous y prendre



Partage des textures



Au sein de notre classe, nous définirons ce qu'est une texture (largeur, hauteur, identifiant). Puis nous créerons un tableau de textures à l'aide de la classe std::map. Ce tableau sera statique de telle sorte qu'il soit le même pour tout les objets qui utiliseront les textures. Ainsi, un objet pourra utiliser la texture d'un autre objet.

Éviter les textures en double



Pour ne pas recharger une texture déjà chargée, on accompagnera chacune des textures d'un compteur. Si le compteur est à 0, on crée effectivement la texture. Si un autre objet demande à recréer la même texture, on incrémentera simplement le compteur de la texture. Lorsqu'une texture n'est plus utilisée par un objet, on décrémente la valeur du compteur associé à la texture, et lorsque ce compteur arrive à zéro, on détruit effectivement la texture.


Avant : Les textures sont chargées autant de fois qu'elles sont utilisées


Après : Les textures ne sont chargées qu'un fois et utilisées plusieurs fois


Définition du conteneur de textures


Nous allons créer une classe que nous appèlerons ConteneurTextures dans un fichier conteneurTextures.h et .cpp.

Au sein de notre classe, nous définirons une structure de type Texture contenant les différentes caractéristiques d'une texture.
1
2
3
4
5
6
7
8
9
10
class ConteneurTextures
{
   struct Texture
   {
       GLuint texture;
       uint32 compteur;
       sint32 largeur;
       sint32 hauteur;
   };
};

Le compteur est là pour compter le nombre de fois que doit exister la texture.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ConteneurTextures
{
   private:
       struct Texture
       {
           GLuint texture;
           uint32 compteur;
           sint32 largeur;
           sint32 hauteur;
       };
 
       typedef std::map<std::string, Texture> Textures;
       Textures textures;
 
   public:
       Texture ajouter(std::string nomFichier);
       void supprimer(std::string nomFichier);
       Texture texture(std::string nomFichier);
};

Nous retrouvons notre std::map de textures ainsi que 3 méthodes publiques permettant de gérer ce std::map : ajouter(), supprimer() et une méthode pour récupérer une texture à partir de son nom.

Fonctionnement de notre conteneur



Au début, le std::map de texture est vide. Lorsqu'on fait appel à la méthode ajouter(), on ne créera la texture que si la texture n'est pas déjà chargée. Si elle est déjà chargé, on incrémente son compteur pour indiquer qu'elle est créé pour être utilisée 2 fois.

La méthode supprimer(), produit l'effet inverse. Elle décrémente le compteur de la texture, et si le compteur passe à 0, on libère la texture de la carte graphique.

Quant à la méthode texture(), elle retourne la structure correspondante à la texture.


Définition des méthodes : ajouter, supprimer...


La méthode ajouter()



Avant d'ajouter la texture, on essai de la récupérer, voir si elle n'est pas déjà créé. Si elle n'est pas déjà créé, lors de la sollicitation de la "case", une texture est créé avec des champs initialisé à 0. Si la texture n'existe pas, la champs compteur de la structure récupérée (et créée) vaudra 0.

Si la texture n'est pas déjà créé, on cré la texture en inscrivant ses caractéristiques dans les différents champs de la structure.

Ensuite, on incrémente le compteur de cette texture, que celle-ci vienne d'être créé ou non.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ConteneurTextures::Texture ConteneurTextures::ajouter(std::string nomFichier)
{
   Texture& texture = this->textures[nomFichier];
 
   // Si la texture n'existe pas
   if (0 == texture.compteur)
   {
       // On cr reellement la texture
       this->chargerTexture(nomFichier.c_str(), &texture.texture, &texture.largeur, &texture.hauteur);
   }
 
   // On compte une texture supplementaire
   texture.compteur++;
 
   return texture;
}

Lors de la récupération des informations de la texture, j'utilise une référence de façon à pouvoir travailler directement dessus.


Pour simplifier la méthode ajouter(), j'ai créé une méthode chargerTexture() qui aura pour rôle de charger la texture dans la carte graphique.

Chargement d'une texture



Cette méthode chargerTexture() n'a pas besoin d'être rendu publique. En paramètre, nous aurons besoin de connaitre le nom du fichier à charger, puis les adresses dans lesquelles devront être inscrit l'identifiant de la texture, sa largeur et sa hauteur.
1
2
3
4
void ConteneurTextures::chargerTexture(const char* nomFichier, GLuint* texture, sint32* largeur, sint32* hauteur)
{
 
}

N'étant pas à l'aise face à ce genre de méthode, je me suis aidé de cette page web pour la décrire.

Remarque : On peut voir que la fonction vérifie que les dimensions soient de "puissance de deux". J'admire beaucoup la technique utilisée. Sachez que pour de vieilles configurations, il est parfois necessaire que les textures soit de "puissance de deux". Je vous invite à lire lire ceci à ce sujet.


Après quelques révisions de ma part, voici le contenu de ma méthode de chargement de texture :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
void ConteneurTextures::chargerTexture(const char* nomFichier, GLuint* texture, sint32* largeur, sint32* hauteur)
{
   SDL_Surface *surface;
   GLenum formatTexture;
   GLint  octetsParPixel;
 
   // Creationde la surface SDL a partir du fichier de la texture
   surface = SDL_LoadBMP(nomFichier);
 
   if (NULL != surface)
   {
 
       // Verification de la largeur
       if ( 0 != (surface->w & (surface->w - 1)) )
       {
           printf("Attention : la largeur de %s n'est pas une puissance de 2\n ", nomFichier);
       }
 
       // Verification de la hauteur
       if ( 0 != (surface->h & (surface->h - 1)) )
       {
           printf("Attention : la hauteur de %s n'est pas une puissance de 2\n ", nomFichier);
       }
 
       // Recuperation du nombre d'octets par pixel
       octetsParPixel = surface->format->BytesPerPixel;
 
       if (octetsParPixel == 4) // Contient une composante alpha
       {
           if (surface->format->Rmask == 0x000000ff)
           {
               formatTexture = GL_RGBA;
           }
           else
           {
               #ifndef GL_BGRA
                   #define GL_BGRA 0x80E1
               #endif
               formatTexture = GL_BGRA;
           }
       }
       else if (octetsParPixel == 3) // Pas de composante alpha
       {
           if (surface->format->Rmask == 0x000000ff)
           {
               formatTexture = GL_RGB;
           }
           else
           {
               #ifndef GL_BGR
                   #define GL_BGR 0x80E0
               #endif
               formatTexture = GL_BGR;
           }
       }
       else
       {
           printf("Attention : l'image n'est pas en couleur vraie\n");
           SDL_FreeSurface(surface);
           return;
       }
 
       // Activation des textures
       glEnable(GL_TEXTURE_2D);
 
       // Generation de la texture
       glGenTextures(1, texture);
 
       // Selection de la texture
       glBindTexture(GL_TEXTURE_2D, *texture);
 
       // Initialisation des proprits de la texture
       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
       // Rend les arrtes invisibles
       #ifndef GL_CLAMP_TO_EDGE
           #define GL_CLAMP_TO_EDGE (0x812F)
       #endif
       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
       // Chargement de la texture
       glTexImage2D( GL_TEXTURE_2D, 0, octetsParPixel, surface->w, surface->h, 0,
                         formatTexture, GL_UNSIGNED_BYTE, surface->pixels );
 
       // Recuperation de la taille de la texture
       *largeur = surface->w;
       *hauteur = surface->h;
 
       // Liberation de la surface SDL de la texture
       SDL_FreeSurface(surface);
   }
   else
   {
       printf("SDL ne peut pas charger l'image %s : %s\n", nomFichier, SDL_GetError());
       *texture = 0;
       *largeur = 1;
       *hauteur = 1;
   }
}

Note : J'ai été obligé de définir certaines constantes que je n'avais pas chez moi : GL_BGRA et GL_BGR.


Supprimer une texture



Pour la suppression, je vous laisse comprendre par vous même ce code très simple :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void ConteneurTextures::supprimer(std::string nomFichier)
{
   // Recuperation de la texture
   Texture texture = this->textures[nomFichier];
 
   // Si la texture existe
   if (0 != texture.compteur)
   {
       texture.compteur--;
   }
 
   // Si la texture n'est plus utilise
   if (0 == texture.compteur)
   {
       // Liberation de la texture
       glDeleteTextures(1, &texture.texture);
 
       // Suppression de la case de la texture
       this->textures.erase(nomFichier);
   }
}

On fait attention à bien supprimer la case, elle n'a plus de raison d'exister.


L'accesseur


1
2
3
4
ConteneurTextures::Texture ConteneurTextures::texture(std::string nomFichier)
{
   return this->textures[nomFichier];
}

Petite mise au point



Notre conteneur peut-être instancié plusieurs fois. Les textures sont "dans" le conteneur. Pour éviter que les textures soit propres au conteneur, on doit rendre l'attribut textures statique.
1
static Textures textures;

On doit alors le créer dans le cpp :
1
ConteneurTextures::Textures ConteneurTextures::textures;


Intégration du conteneur de texture


Nous avons "finit" de créer notre conteneur. Il n'est pas testé et nous ne l'utilisons encore pas. Commencez par vous assurer qu'il compile. Vous pourrez commencer l'intégration après.

Ajout du conteneur à l'objet 3D



Les textures sont maintenant gérées par le conteneur. On ajoute un attribut conteneurTextures à notre classe Objet3DStatique. L'attribut texture ne nous servira plus à faire le lien entre le nom et la texture ; en revanche, nous allons le garder pour connaitre la liste des textures utilisés par l'objet. Un std::set suffira.
1
2
3
4
5
6
7
8
9
10
class Objet3DStatique
{
   private:
       typedef std::set<std::string> Textures;
 
       Textures textures; // Les textures de l'objet
       ConteneurTextures conteneurTextures;
 
       // . . .
};

Les méthodes de l'objet 3D



Ajout d'une texture



Avant nous faisions comme ça pour ajouter une texture :
1
2
3
4
5
6
                // Si la texture n'est pas deja creee
               if(this->textures.find(nomFichierTexture) == this->textures.end())
               {
                   // On cr la texture
                   this->textures[nomFichierTexture] = loadTexture(nomFichierTexture.c_str());
               }

Maintenant, l'ajout se fait à l'aide de la méthode ajouter(). On n'oublie pas d'ajouter la nouvelle texture à la liste des textures utilisé par l'objet.
1
2
3
4
5
                if(this->textures.find(nomFichierTexture) == this->textures.end())
               {
                   this->conteneurTextures.ajouter(nomFichierTexture);
                   this->textures.insert(nomFichierTexture);
               }

Pour identifier la texture courante, on utilisera maintenant le nom de la texture. textureCourante est maintenant un std::string.
1
2
                // Cette texture devient la texture courante
               textureCourante = nomFichierTexture;

Sélection de la texture



Le code de sélection de la texture à afficher est maintenant différent.
A partir de son nom, on peut accéder à la structure de la texture que l'on cherche :
1
this->conteneurTextures.texture(textureCourante)

Et ainsi récupérer l'identifiant OpenGL de la texture pour la sélectionner:
1
2
                // Selection de la dernire texture rencontre
               glBindTexture(GL_TEXTURE_2D, this->conteneurTextures.texture(textureCourante).texture);

Définition des sommets de la texture



Maintenant que l'on connait la taille de la texture, nous pouvons l'utilise afin que notre classe s'adapte automatiquement à la texture. Comme ceci :
1
2
3
4
5
glTexCoord2f(
(float16)x2D / this->conteneurTextures.texture(textureCourante).largeur,
(float16)y2D / this->conteneurTextures.texture(textureCourante).hauteur);
 
glVertex3d(x3D, y3D, z3D);

Libération des textures



Le code du destructeur ressemble maintenant à ceci :
1
2
3
4
5
6
7
8
9
10
11
Objet3DStatique::~Objet3DStatique()
{
   // Libration des textures
   for (Textures::iterator element = this->textures.begin(); element != textures.end(); element++)
   {
       this->conteneurTextures.supprimer(*element);
   }
 
   glDeleteLists(this->listeAffichage, 1);
   glDeleteLists(this->listeRST, 1);
}

On se sert de l'attribut textures pour supprimer les textures que l'on connait. En les supprimant du conteneur, celui-ci assure la suppression ou la décrémentation du compteur d'utilisation de la texture.




Le chapitre est terminé. Il vous a sans doute paru peu intéressant : rien de visuel n'a été apporté à votre jeu. Mais la gestion des textures est maintenant beaucoup mieux faite et optimisée.

Lien : Le projet actuel



Chapitre précédent     Sommaire     Chapitre suivant



Rédigé par David
Consulté 15424 fois



Hébergeur du site : David
Version PHP : 5.4.45-0+deb7u2
Uptime : 31 jours 5 heures 4 minutes
Espace libre : 2115 Mo
Dernière sauvegarde : inconnue
Taille de la sauvegarde : 1100 Mo


4007133 pages ont été consultées sur le site !
Dont 1686 pages pendant les 24 dernières heures.

Page générée en 0.595 secondes


Nos sites préférés
- Création d'un jeu de plateforme de A à Z avec SDL
- Zelda ROTH : Jeux amateurs sur le thème de Zelda
- Zeste de Savoir : la connaissance pour tous et sans pépins
- YunoHost : s'héberger soi-même en toute simplicité
- Site de Fvirtman : recueil de projets et de codes en C et C++


  © 2005-2017 linor.fr - Toute reproduction totale ou partielle du contenu de ce site est strictement interdite.