XNA Tutorial 8 : Les textures


< en construction / under construction>


Retourner au sommaire des cours  


Notre premier chapitre consacré aux textures : un vaste, un très vaste sujet et au combien important dans le développement 3D. Le texturing est sans aucun doute l’élément le plus important pour donner du réalisme et de l’interet à un projet 3D. Nul doute qu’après cet apprentissage, nos petites productions n’auront plus rien à voir avec ce que nous faisions jusque là !


Nous suivrons trois phases dans cet apprentissage :



  • Faire connaissance avec le texturing en apprennant les notions de base.

  • Faire connaissance avec le blending de filtrage, de mipmapping, de blending et de multitexturing.

  • Opérer sur les textures.

 


Texture ?

 

Aucun rapport avec la définition du dictionnaire. La texture est une image qui est plaquée sur un triangle. Il s’agit donc d’un objet à deux dimensions. Nous pourrions la comparer à une surface collée sur un objet 3D pour lui donner un habillage réaliste. XNA comme DirectX autorise toute sorte de taille de texture. En interne il les découpera en images carrés dont le nombre de pixels est une puissance de 2 (16,32,256, …). Dans tous les cas il faut toujours se rappeler que pour la majorité des matériels installés sur les machines, les textures les plus rapides à manipuler font une taille de 256 par 256 et plus la taille de la texture est importante, plus il faudra de temps GPU pour les manipuler.

 

Si les données affichées à l’écran sont nommées pixels, les données d’une texture sont nommée texels (pour “texture element”).

 

DirectX dans le chargement s’avère très polyvalent et peut lire un grand nombre de type de fichiers images, à savoir PNG, JPG, BMP et TGA.


Dans tous les cas le format d’image n’influence rien pour les performances de jeu. Chaque image étant traduite dans un format interne à Direct3D lors de son chargement. Notons enfin que les textures peuvent être générée à la volée à partir d’un accès mémoire.


Coordonnées de texture


Voilà une notion qui peut étonner au premier abord : Une coordonnée de texture permet de spécifier une position dans la texture. Dans la mesure où, dans une texture, l’espace est en 2D, seules deux valeurs sont nécessaire pour spécifier une position : U et V. U représente l’abscisse, à savoir le nombre d’unités à parcourir horizontalement et V qui représente le nombre d’unités verticales (ordonnées) à parcourir. Leurs valeurs oscillent entre 0 et 1 (des valeurs supérieure peuvent être données pour répéter une texture, nous verrons cela plus tard). Le point supérieur gauche de la texture est donc le (0,0) et le point inférieur droit (1,1) pour (U, V). Le schéma ci-dessous explicite tout cela avec la texture que nous allons employer dans notre premier exemple.



Les coordonnées de textures.


 


Placage de textures
 


Les coordonnées de textures sont donc une notion simple qui s’apparente aux bases mathématiques de la géométrie dans l’espace mathématique. Toutes les coordonnées (UV) pour le placage sur une forme 3D seront associées à chaque vertex qui la compose.


Pour plus de simplicité nous allons mettre de coté le cube qui servait à tous nos exemples depuis plusieurs cours pour revenir au carré utilisé dans le tutoriel 3. Dans ce programme un carré est formé de 4 vertices associés à une couleur et une position X, Y, Z comme ci-dessous.


Un programme simple déjà vu qui affiche un carré.
Un programme simple déjà vu qui affiche un carré.


Associons maintenant un troisième type de valeur : les coordonnées de texture nommée ici tu et tv. Nous allons afficher sur ce carré notre texture en entier. Le point haut gauche aura donc la coordonnée de texture (0,0), le point haut droit (1,0) le point bas gauche (0,1) et enfin le point bas droite (1,1). Simple donc. Nous aurons :


La texture entière occupe la surface du carré.
La texture entière occupe la surface du carré.


Essayons maintenant de n’afficher que la moitié haut droite de la texture, à savoir la partie :


 


Nous aurons donc pour caractéristiques des 4 vertices :



Essayons pour continuer de n’afficher que la moitié basse de la texture :



Les caractéristiques seront :



Autre exemple, affichons le centre du carré à savoir :



nous aurons :



Finissons sur une tâche plus compliquée (rassurez vous seulement d’un chouïa).  Nous n’allons pas prendre une portion de la texture de manière a avoir des coordonnées “ronde” (à savoir 0.5 ou 1) mais une portion complétement aléatoire. Il va donc nous falloir calculer la valeur exacte de la texture, prendre le pourcentage et reporter la valeur entre 0 et 1. Mettons au hasard le point haut gauche de la source au texel (12,16). Pour une texture de (64,64), converti en pourcentage nous avons : 18.75% en abscisse (12/64*100) et 25% en ordonnée soit donc en coordonnées de texture 0.1875 pour tu et 0.25 pour tv. Faisons de même pour les 3 autres coins. Au final nous avons :



Terminons enfin sur la répétition de textures. Jusqu’à présent les valeurs de tu et tv étaient comprises entre 0 et 1. Que ce passe t’il si ces valeurs dépassent 1 ? XNA, lors du mappage va répéter la texture suivant le pourcentage donné par les coordonnées ;  Si tu vaut 1.5 la texture sera copiée entièrement avec la moitié d’une texture sur l’abscisse. Si tv vaut 3, alors sur l’ordonnée nous aurons trois textures copiées. Le dernier schéma de ce point illustre cela très bien :



Notons aussi que les valeurs négatives données à tu et tv inverseront le mappage de la texture. XNA permet d’autres interprétations dans le cas ou tu et tv dépassent la valeur 1 (nous verrons cela plus loin dans ce chapitre).


Passons maintenant aux choses concrètes.


Textures et XNA


Préparer notre projet à l’utilisation de textures… 


Jusqu’à présent, la seule manière que nous avions pour ajouter des couleurs à un scène 3D étant de créer autant de vertices que de couleurs associées. Nous allons utiliser maintenant un moyen bien plus puissant et efficace en associant chaque vertices à une coordonnée de texture comme vu dans le point précédent. Nous reprendrons un projet relativement similaire à celui utilisé pour le chapitre 4 consacré aux matrices. Revenez quelques instants sur le code qu’il contient avant de continuer plus en avant. Notre but sur ce cours est d’afficher un carré à l’écran et, en utilisant un format de vertice adapté, y plaquer une texture.


La première étape consiste à définir 6 vertices pour les deux triangles formant le carré. Sachant que nous n’utilisons plus de couleur nous ne pouvons plus utiliser le type VertexPositionColor. Nous utiliserons à la place un nouveau type de vertex : VertexPositionTexture. *



Remarque : Nous avons déjà abordé les structure de format de vertices comme VertexPositionTexture ou comme VertexPositionColor. Ces formats permettent d’associer à un vertex différentes informations (comme la position, la couleur, les coordonnées de  texture …). Vous vous demandez peut être comment XNA peut il savoir quel type de données (couleur, texture, …) est associé aux vertices qu’on lui donne ? Il utilise tout simple le contenu de la propriété que chaque structure de format doit posséder. Pour plus d’information à ce sujet et pour créer vos propres formats rendez vous ici. 


Changez la déclaration du tableau de vertices ainsi :


VertexPositionTexture[] vertices;


Vient maintenant la création de chacun des vertices :

vertices = new VertexPositionTexture[6]; //triangle 1, face devantvertices[0].Position = new Vector3(-10f, 0f, -10f);vertices[0].TextureCoordinate = new Vector2(0f, 0f);vertices[1].Position = new Vector3(-10f, 0f, 10f);vertices[1].TextureCoordinate = new Vector2(0f, 1f);vertices[2].Position = new Vector3(10f, 0f, 10f);vertices[2].TextureCoordinate = new Vector2(1f, 1f);//triangle 2, face devantvertices[3].Position = new Vector3(-10f, 0f, -10f);vertices[3].TextureCoordinate = new Vector2(0f, 0f);vertices[4].Position = new Vector3(10f, 0f, 10f);vertices[4].TextureCoordinate = new Vector2(1f, 1f);vertices[5].Position = new Vector3(10f, 0f, -10f);vertices[5].TextureCoordinate = new Vector2(1f, 0f); 

On remarque ici une première différence avec nos programmes précédents : plus de couleur mais une coordonnée de texture associée à chaque vertex. Le vertex 0 se trouve en haut à gauche du carré. La coordonnée (U,V) associée est donc (0, 0). On ancrera donc la partie supérieure gauche de la texture sur ce vertex. Le second vertex se trouve en haut à droite sa coordonnée est donc  (0, 0). La partie supérieure droite va être ancrée sur lui. Ainsi de suite pour les 4 autres vertices. C’est exactement ce que nous avons vu au tout début de ce chapitre.


Nous devons aussi spécifier à notre effet que nous ne manipulons plus des vertices colorés, mais texturés. Pour cela modifiez la ligne :

this.effect.CurrentTechnique = effect.Techniques[“Colored”];

en


this.effect.CurrentTechnique = effect.Techniques[“Textured”];


Il a été vu précédemment que le device où est affiché notre jeu (la carte graphique principale donc) a besoin de connaître le type de vertices utilisé (c’est à dire leur format). Il est donc nécessaire dans la méthode de dessin de modifier la ligne :

this.graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(this.graphics.GraphicsDevice, VertexPositionColor.VertexElements);

en

this.graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(this.graphics.GraphicsDevice, VertexPositionTexture.VertexElements);

A ce stade, l’application, à l’exécution affiche un magnifique carré … noir ! Tout est normal : nous n’avons pas encore utilisé de texture et nous n’utilisons plus de couleur.


Ajout de textures au projet… 


Nous utiliserons la texture prise en exemple précédemment :


Notre texture


Celle-ci, nommée “texture.jpg” se trouve à la racine du répertoire HuitiemeChapitre. Ajoutez là en cliquant droit sur le nom du projet et en selectionnant “ajouter un élément existant” :


Cliquez droit sur le projet et selectionnez "Ajouter un élément existant" 


Dans la fenetre qui s’ouvre, choisissez “Content Pipeline” dans la combobox de choix de type de contenu et selectionnez le fichier texture.jpg.


 


Après validation 


 

Déclarez un object de type Texture2D dans la classe Game1

Texture2D texture;


 


C’est cet objet qui va nous permettre de manipuler les textures dans notre application. Rendez vous maintenant dans la méthode LoadGraphicsContent : Il nous faut y charger la texture mais aussi spécifier à notre effet que nous associons cette texture à l’affichage des vertices. Nous procédons comme ceci :


 

protected override void LoadGraphicsContent(bool loadAllContent){    if (loadAllContent)    {        // TODO: Load any ResourceManagementMode.Automatic content        texture = content.Load<Texture2D>(“texture”);        effect.Parameters[“xTexture”].SetValue(texture);    }     // TODO: Load any ResourceManagementMode.Manual content

}


 


 


L’objet content de type ContentManager s’occupe des ressources du jeu en évitant au développeur d’avoir à se soucier de leur gestion. La méthode Load, générique est son seul membre réellement interessant. Elle renvoie un objet du type spécifié en généricité à partir du path ressource donné. Ici :


 


texture = content.Load<Texture2D>(“texture”);


 


Nous indiquons la spécificité sur le type Texture2D et donnons en path la string “texture”. Nous ne donnons pas l’extension “.jpg” car la méthode Load prend le nom AssetName. Pour voir sa valeur, selectionnez la texture dans le projet et appuyez sur F4 (ou cliquez droit sur ce fichier et faites Propriété). Dans la fenêtre d’outil qui s’ouvre le champs AssetName contient la string “texture”. Vous pouvez toujorus changer cette valeur pour donner un nom explicite, mais faites attention à bien vous y retrouver lorsque vous avez plusierus centaines de fichiers ressources !


Si vous lancez l’application vous obtenez maintenant :


 



 


Voila nous avons terminé ! Comme exercice revenez sur le premier point de ce chapitre où l’ont voit différentes manière de texturer un carré. Appliquez ces différentes possibilités à votre programme pour les tester.


 


 


telecharger Vous pouvez télécharger le sample, les deux moteurs et les exercices ici.   


 


 


Ajout de textures et de couleurs au projet… 


 


Avant de passer aux choses sérieuses, nous allons tenter d’associer trois données à chaque vertex :




  • La position bien évidemment


  • La texture


  • Une couleur

Nous utiliserons pour cela la structure de format de vertex VertexPositionColorTexture en lieu et place de VertexPositionTexture.


La déclaration des vertices sera modifiée de manière a spécifier le champs color comme auparavant :


 


 

vertices = new VertexPositionColorTexture[6]; //triangle 1, face devantvertices[0].Position = new Vector3(-10f, 0f, -10f);vertices[0].TextureCoordinate = new Vector2(0f, 0f);vertices[0].Color = Color.Green;vertices[1].Position = new Vector3(-10f, 0f, 10f);vertices[1].TextureCoordinate = new Vector2(0f, 1f);vertices[1].Color = Color.Red;vertices[2].Position = new Vector3(10f, 0f, 10f);vertices[2].TextureCoordinate = new Vector2(1f, 1f);vertices[2].Color = Color.Yellow;//triangle 2, face devantvertices[3].Position = new Vector3(-10f, 0f, -10f);vertices[3].TextureCoordinate = new Vector2(0f, 0f);vertices[3].Color = Color.Green;vertices[4].Position = new Vector3(10f, 0f, 10f);vertices[4].TextureCoordinate = new Vector2(1f, 1f);vertices[4].Color = Color.Red;vertices[5].Position = new Vector3(10f, 0f, -10f);vertices[5].TextureCoordinate = new Vector2(1f, 0f);

vertices[5].Color = Color.Yellow;


 


Faites bien attention à remplacer partout VertexPositionTexture.


Si vous lancez cette nouvelle version du programme, vous obtenez la même sortie qu’auparavant : et pour cause, notre fichier effet ne permet pas de gérer les vertices qui possèdent à la fois une coordonnée de texture et une couleur. Deux possibilités face à ce problème : chercher un fichier effet qui permet cela, ou utiliser celui de base inclu dans le framework. C’est cette seconde option que nous utiliserons pour sa simplicité.


La classe BasicEffect est une classe qui encapsule en interne un fichier effet (.fx) pratiquement identique à celui que nous utilisions jusque là. Elle offre une interface objet simple et intuitive pour paramétrer la façon dont les vertices sont affichés à l’écran.


 



Remarque : Pourquoi ne pas avoir directement utilisé cette classe au tout début de notre apprentissage ? Pour des raisons de compréhension : lorsqu’on travaille à trop haut niveau les API nous cachent se qui se passe réellement dans les couches basses des fonctionnalités utilisées. On se trouve souvent ainsi confrontés à des bugs incompréhensibles faute de connaissances techniques… Ce qui a été appris jusqu’ici sera très utile lorsque nous aborderons le chapitre sur les effets.


 


Supprimez toutes les références vers la classe Effect et son instance effect du programme. Ajoutez la déclaration suivante dans la classe Game1 :


 


 

BasicEffect effect;

 


 


Dans la méthode Initialize ajoutez le code suivant :


 

effect = new BasicEffect(this.graphics.GraphicsDevice, null);
 effect.VertexColorEnabled = true;
effect.TextureEnabled = true;
effect.View = viewMatrix;
effect.Projection = projectionMatrix;
effect.World = Matrix.Identity;


 


Nous pouvons en convenir : le code est réellement plus simple ; Ecrire :


 

effect.View = viewMatrix;
effect.Projection = projectionMatrix;
effect.World = Matrix.Identity;


 


est plus lisible que  


 

effect.Parameters[“xView”].SetValue(viewMatrix);
effect.Parameters[“xProjection”].SetValue(projectionMatrix);
effect.Parameters[“xWorld”].SetValue(Matrix.Identity);


 


Les deux premières instructions indiquent que les vertices affichés à l’écran gèrent à la fois la couleur et les textures :


 

effect.VertexColorEnabled = true;
effect.TextureEnabled = true;


 


Reste a affecter la dite texture dans la méthode LoadGraphicsContent de cette manière :


 

effect.Texture = texture; 

 


Si vous lancez maintenant l’application le résultat est meilleur :


 


 



 


La couleur de chaque texel de la texture a été “aditionnée” à la couleur du pixel du carré issue de la couleur du vertex sous jacent.


 


Pour la suite de nos applications nous utiliserons la classe BasicEffect.


 


 


telecharger Vous pouvez télécharger le sample, les deux moteurs et les exercices ici.   


 


 


Passons aux choses sérieuses…


Nous allons maintenant modifier la classe Cube du chapitre précédent afin de lui faire afficher une texture. Il lui sera aussi ajouté un objet de type BasicEffect afin de mettre la gestion de l’affichage des vertices au niveau du cube et non au niveau de tous les cubes. De même, pour changer un peu, une nouvelle texture (qui devrait rappeller de bon souvenirs) sera utilisée:



La classe Cube va contenir deux nouveaux champs :

private Texture2D _texture;

private BasicEffect _effect;


et deux nouvelles propriétés :

/// <summary>/// <para>Gets the cube’s effect.</para>/// </summary>public BasicEffect Effect{    get    {        return this._effect;    }} /// <summary>/// <para>Gets or sets the cube’s texture.</para>/// </summary>public Texture2D Texture{    get    {        return this._texture;    }    set    {        this._texture = value;        this._effect.Texture = value;    }

}


Une méthode InitializeEffect est ajoutée pour initialiser notre effet :

private void InitializeEffect()
{
    this._effect = new BasicEffect(this._device, null);
    this._effect.VertexColorEnabled = true;
    this._effect.TextureEnabled = true;

}


La méthode Render est modifiée en conséquence :

public void Render(){    this._effect.Begin();     foreach (EffectPass pass in this._effect.CurrentTechnique.Passes)    {        pass.Begin();        this._device.Vertices[0].SetSource(this._vertexBuffer, 0, VertexPositionColorTexture.SizeInBytes);        this._device.Indices = this._indexBuffer;        this._device.VertexDeclaration = new VertexDeclaration(this._device, VertexPositionColorTexture.VertexElements);        this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 24, 0, 12);        pass.End();    }     this._effect.End();

}


Le code de la classe est maintenant largement simplifié. Il ne nous reste plus qu’à modifier la déclarations des 8 vertices du cube pour leur donner à chacun, une coordonnée de texture. En fait il y’a ici un problème. Nous avons 8 vertices pour 6 faces et donc 12 triangles. Donc chaque vertex du cube est utilisé pour plusieurs faces. L’image suivant illustre le problème :


Le vertex rouge est utilisé par plusieurs face
Le vertex rouge est utilisé par plusieurs face


Si on prend le vertex surmonté d’un point rouge, on remarque que celui-ci appartient à trois faces :



  • La rouge pâle

  • La jaune pâle

  • La bleue pâle

Comment donner à ce vertex une coordonnée de texture dans ces conditions ? Pour la face rouge, ce vertex se trouve en bas à droite il aurait donc la coordonnée (1,1), or pour la face jaune il est en haut à droite et possède donc l’emplacement (1,0) sans parler de la face bleue dans laquelle il a la coordonnée (0,0) soit haut gauche. En fait chaque point du cube (au total 8 points) appartiennent à 3 faces. Il est donc nécessaire de créer non pas 8 vertices pour un cube mais 24 : chaque face possèdera ses propres vertices non partagés avec les autres faces, nous avons 6 faces de 4 vertices donc 24 vertices. Il ne s’agit pas ici d’une regression ; nous ne revenons pas au cube du tutoriel 5 qui possédait 36 vertices. La méthode InitializeVertices se présente désormais ainsi :

private void InitializeVertices(){    VertexPositionColorTexture[] vertices = new VertexPositionColorTexture[24];     //face devant    vertices[0].Position = new Vector3(0f, 0f, 0f);    vertices[0].Color = this.Color;    vertices[0].TextureCoordinate = new Vector2(0, 1);    vertices[1].Position = new Vector3(0f, 0f, 1f);    vertices[1].Color = this.Color;    vertices[1].TextureCoordinate = new Vector2(0, 0);    vertices[2].Position = new Vector3(1f, 0f, 1f);    vertices[2].Color = this.Color;    vertices[2].TextureCoordinate = new Vector2(1, 0);    vertices[3].Position = new Vector3(1f, 0f, 0f);    vertices[3].Color = this.Color;    vertices[3].TextureCoordinate = new Vector2(1, 1);     //face derrière    vertices[4].Position = new Vector3(1f, 1f, 1f);    vertices[4].Color = this.Color;    vertices[4].TextureCoordinate = new Vector2(0, 0);    vertices[5].Position = new Vector3(1f, 1f, 0f);    vertices[5].Color = this.Color;    vertices[5].TextureCoordinate = new Vector2(0, 1);    vertices[6].Position = new Vector3(0f, 1f, 0f);//    vertices[6].Color = this.Color;    vertices[6].TextureCoordinate = new Vector2(1, 1);    vertices[7].Position = new Vector3(0f, 1f, 1f);    vertices[7].Color = this.Color;    vertices[7].TextureCoordinate = new Vector2(1, 0);     //face gauche    vertices[8].Position = new Vector3(0f, 1f, 0f);//    vertices[8].Color = this.Color;    vertices[8].TextureCoordinate = new Vector2(0, 1);    vertices[9].Position = new Vector3(0f, 1f, 1f);    vertices[9].Color = this.Color;    vertices[9].TextureCoordinate = new Vector2(0, 0);    vertices[10].Position = new Vector3(0f, 0f, 1f);    vertices[10].Color = this.Color;    vertices[10].TextureCoordinate = new Vector2(1, 0);    vertices[11].Position = new Vector3(0f, 0f, 0f);    vertices[11].Color = this.Color;    vertices[11].TextureCoordinate = new Vector2(1, 1);     //face droite    vertices[12].Position = new Vector3(1f, 0f, 0f);    vertices[12].Color = this.Color;    vertices[12].TextureCoordinate = new Vector2(0, 1);    vertices[13].Position = new Vector3(1f, 0f, 1f);    vertices[13].Color = this.Color;    vertices[13].TextureCoordinate = new Vector2(0, 0);    vertices[14].Position = new Vector3(1f, 1f, 1f);//    vertices[14].Color = this.Color;    vertices[14].TextureCoordinate = new Vector2(1, 0);    vertices[15].Position = new Vector3(1f, 1f, 0f);    vertices[15].Color = this.Color;    vertices[15].TextureCoordinate = new Vector2(1, 1);     //face haut    vertices[16].Position = new Vector3(0f, 0f, 1f);//    vertices[16].Color = this.Color;    vertices[16].TextureCoordinate = new Vector2(0, 1);    vertices[17].Position = new Vector3(0f, 1f, 1f);    vertices[17].Color = this.Color;    vertices[17].TextureCoordinate = new Vector2(0, 0);    vertices[18].Position = new Vector3(1f, 1f, 1f);    vertices[18].Color = this.Color;    vertices[18].TextureCoordinate = new Vector2(1, 0);    vertices[19].Position = new Vector3(1f, 0f, 1f);    vertices[19].Color = this.Color;    vertices[19].TextureCoordinate = new Vector2(1, 1);     //face bas    vertices[20].Position = new Vector3(1f, 0f, 0f);    vertices[20].Color = this.Color;    vertices[20].TextureCoordinate = new Vector2(0, 1);    vertices[21].Position = new Vector3(1f, 1f, 0f);    vertices[21].Color = this.Color;    vertices[21].TextureCoordinate = new Vector2(0, 0);    vertices[22].Position = new Vector3(0f, 1f, 0f);//    vertices[22].Color = this.Color;    vertices[22].TextureCoordinate = new Vector2(1, 0);    vertices[23].Position = new Vector3(0f, 0f, 0f);    vertices[23].Color = this.Color;    vertices[23].TextureCoordinate = new Vector2(1, 1);     if (this.IsObjectOriginInCubeCenter)        for (int i = 0; i < 24; i++)            vertices[i].Position += new Vector3(-0.5f, -0.5f, -0.5f);     this._vertexBuffer = new VertexBuffer(    this._device,    typeof(VertexPositionColorTexture),    24,    ResourceUsage.WriteOnly,    ResourceManagementMode.Automatic);     this._vertexBuffer.SetData(vertices);} 

A noter la présence du bloc :

    if (this.IsObjectOriginInCubeCenter)
        for(int i = 0 ; i < 24 ; i++)
            vertices[i].Position += new Vector3(-0.5f, -0.5f, -0.5f);

J’ai rajouté sur demande de certains d’entre vous une propriétée nommée qui permet de préciser si le centre du cube doit coincider avec l’origine du repère de l’objet (pour plus d’information allez voir ici). L’origine se trouve par défaut sur le vertice 0, pour mettre l’origine au centre du cube il me suffit de déplacer tous les vertices de 0.5f unités car le cube a une taille de 1. Modifiez la méthode d’affichage pour indiquer que vous gérez non plus 8 vertices mais 24. Au lancement vous obtenez


On aurait presque envie de donner un coup de tête à la mario :)


(je reviendrais dans une annexe sur la manière de faire des mouvements amples et fluides pour ses objets dans une annexe prochainement).


 


 


telecharger Vous pouvez télécharger le sample, les deux moteurs et les exercices ici.   


 


 


A ce stade de nos connaissances nous pouvons …


L’information qui suit est à prendre avec des pincette mais elle donne toutefois une idée assez précise du niveau de nos connaissances à ce stade de notre apprentissage. Une partie de ceux qui lisent ces lignes ont sans doute déjà joué à l’un des épisode de la série à succès Tomb Raider (l’auteur de cours XNA a pour sa part joué aux version 1, 2, 3, 4). Nous sommes maintenant capable de réaliser des mondes tous à fait similaires à celui présenté par la capture d’écran ci-dessous :



Regardez attentivement cette image. Vous verrez qu’en fait, ce monde n’est constitué que de formes cubiques comme nous savons très bien les faire maintenant : des cubes, des cubes coupés sur leur diagonale, des cubes coupés en leur milieu etc. C’est là toute l’ingéniosité et toute la puissance du moteur de Core/Eidos : reproduire n’importe quelle architecture/géographique à l’aide de ces formes. Les mouvements de Lara sont d’ailleur soumi à une seule unité de mesure : la taille d’un cube. Ce principe assez proche des jeux de plateforme 2D permet de gérer les collisions et les déplacements très facilement et de créer des formes à l’écran simplement. Core/Eidos peut donc créer des mondes et développer un moteur beaucoup plus rapidement que des moteurs complexes comme celui de World Of Warcraft (que nous reproduirons à la fin de cet apprentissage).


Si le temps nous le permet nous essayerons de reproduire un monde à base de cubes.


 


Les filtres


Introduction 


Tous ceux qui ont travaillé avec des programmes d’édition d’images comme paint shop pro ou photoshop sont familiers des effets de pixellisation. On obtient cet effet désagréable lorsqu’on réduit la taille d’une image selon un rapport impair. Résultat : de nombreux détails sont perdus et la qualité de l’image s’en ressent. Pour éviter cela, les bons programmes d’édition d’images proposent différentes techniques de réduction ou d’augmentation de taille d’image aussi nommées filtrage pour réduire au minimum les pertes dues au changement de taille. XNA aussi.


Pour mieux comprendre, voici un exemple concrêt : Quand XNA affiche une primitive, il plaque un modèle 3-D sur un écran 2-D. Si la primitive possède une texture, XNA doit utiliser cette dernière afin de créer une couleur pour chaque Pixel de ce modèle en utiliser une valeur de couleur de la texture. C’est ce processus qu’on appelle filtrage de texture.


Quand une opération de filtre de texture est effectuée, la texture utilisée est “magnifiée” (agrandie) ou “minifiée” (réduite). En d’autres termes, elle est plaquée sur une primitive qui est plus grande ou plus petite que sa propre taille. La magnification d’une texture peut avoir comme conséquence l’utilisation d’un seul texel (point de couleur dans une texture) pour colorer un grand nombre de Pixel à l’écran. L’image résultante peut être volumineuse et pixelisée. La Minification d’une texture au contraire signifie souvent qu’un Pixel simple se substitue à plusieurs texels. L’image résultante peut être trouble . Pour résoudre ces problèmes, se mélanger des couleurs de texel doit être effectué pour arriver à une couleur pour le Pixel.


XNA tout comme DirectX fournit plusieurs types de filtrage de texture (lineaire, anisotropic, gaussien …) et un système de mipmapping (notion sur laquelle nous reviendrons plus loin). Si vous ne choisissez aucun filtre, XNA emploie une technique appelée nearestpoint sampling (en gros : choix du texel le plus proche).


Chaque type de filtrage de texture a des avantages et des inconvénients. Par exemple, le filtrage linéaire de texture peut produire des bords déchiquetés ou un aspect volumineux dans l’image finale. Cependant, c’est un filtre très simple et rapide à mettre en place à faible coùt de mémoire. Le filtrage anisotrope demande plus de temps processeur et beaoucp plus de mémoire.


L’exemple suivant dans le jeu Might and Magic le montre bien :


— La maison vue de loin est belle …
La maison vu de loin est belle …


— … mais quand on s’approche … tout est pixellisé et … hideux 
… mais quand on s’approche … tout est pixellisé et … hideux le jeu à l’époque, manquait d’un bon filtre de texture…


Might and Magic dans sa version 6 (NDA : la meilleure version) n’était pas très avancé graphiquement parlant. Le jeu manquait en autres choses d’un bon filtrage de texture. La première image montre un batiment dans la ville de Sorpigal. Vu de loin, la texture qui est plaquée sur ce batiment semble normale, mais comme le montre la seconde image, si l’on s’approche, un filtre nearest point sampling est effectué pour créer une magnification : on agrandit énormément la texture. En ressort alors un très désagréable effet de pixelisation. Dans la première image, la surface visible du batiment est d’environ 300*200. On s’approche donc de la taille de cette texture : l’affichage est donc optimal. Dans la seconde image la surface d’affichage pour la texture passe a 450*350 et ce pour n’afficher que 8% de la texture ! L’utilisation d’un bon filtrage permet d’éviter cet effet pixélisé horrible.


Et Xna dans tout ça ?


XNA offre au développeurs 5 filtrages (tous énuméré dans le type TextureFilter) :

 























Membre


Description


GaussianQuad


Un filtre 4-sample Gaussien pour la magnification ou la minification.


PyramidalQuad


Un filtre 4-sample pour la magnification ou la minification.


Anisotropic


Un filtre de texture anisotrope utilisé pour la magnification ou la minification. Les filtres anisotrope compensent la distorsion représentent par  l’angle  du polygone à mapper et la position de la caméra.


Linear


Un filtre de texture anisotrope utilisé pour la magnification ou la minification. Une moyenne pondérée de 2×2 texels autour du texel concerné est utilisé.


Point


Un filtre de texture point utilisé pour la magnification ou la minification. Le texel avec les coordonnées les plus proches du pixel voulu est utilisé.


None


Aucun filtre.


 


C’est la propriété SamplerStates de l’objet GraphicsDevice qui va nous permettre de spécifier le filtrage à appliquer en minification et/ou en magnification.
Si nous voulons appliquer un filtre anisotrope en mignification et lineaire en magnignification nous ferons :

this.graphics.GraphicsDevice.SamplerStates[0].MinFilter = TextureFilter.Anisotropic;
this.graphics.GraphicsDevice.SamplerStates[0].MagFilter = TextureFilter.Linear;


…pas très compliqué. Nous utilisons ici la propriété SamplerStates comme un tableau tout simplement parcequ’il est possible de plaquer plusieurs textures sur un même polygone (jusqu’à 8 textures). Nous appliquons ici nos filtres MinFilter et MagFilter sur la première texture (0). La classe SamplerState possède de nombreux membres pour la gestion de l’affichage des textures à l’écran. Nous les verrons tous dans ce chapitre.


Un exemple concrêt


Rien de mieux pour bien comprendre l’effet d’un filtre à l’écran que de développer un exemple. Nous reprendrons le dernier projet pour y effectuer quelques menues modifications. Voici ce que nous allons faire : un cube va être affiché en gros plan. Il tournera sur lui-même a vitesse très réduite de sorte de bien mettre en évidence les différents filtres que nous appliquerons. Les filtres seront selectionnés au clavier : la touche ‘A’ pour anisotope, ‘L’ pour linéaire, ‘P’ pour point, ‘G’ pour Gaussian, ‘Q’ pour Pyramidal, ‘N’ pour aucun. Si la touche Shift est appuyée en même temps, le filtre s’applique en magnification, sinon, en minification.


En fait le sample sera rapide à écrire, nous n’allons ajouter qu’une vingtaine d’instructions. Première étape, la classe Cube. Ajout de deux membres et deux propriétés associées pour sauvegarder le filtre en magnification et minification :

        private TextureFilter _minfilter;
        private TextureFilter _magfilter;

        public TextureFilter MagTextureFilter
        {
            get
            {
                return this._magfilter;
            }
            set
            {
                this._magfilter = value;
            }
        }
 
        public TextureFilter MinTextureFilter
        {
            get
            {
                return this._minfilter;
            }
            set
            {
                this._minfilter = value;
            }

        }


Seconde étape : Application des filtres dans la méthode Render :

/// <summary>/// <para>Render the cube on the device.</para>/// </summary>public void Render(){    this._effect.Begin();     foreach (EffectPass pass in this._effect.CurrentTechnique.Passes)    {        pass.Begin();         this._device.SamplerStates[0].MinFilter = _minfilter;        this._device.SamplerStates[0].MagFilter = _magfilter;         this._device.Textures[0] = this.Texture;        this._device.Vertices[0].SetSource(this._vertexBuffer, 0, VertexPositionColorTexture.SizeInBytes);        this._device.Indices = this._indexBuffer;        this._device.VertexDeclaration = new VertexDeclaration(this._device, VertexPositionColorTexture.VertexElements);        this._device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 24, 0, 12);        pass.End();    }     this._effect.End();

}


C’est terminé ! La classe Game1 a bien entendu été modifiée pour pouvoir affecter au cube le filtre voulu suivant la touche clavier appuyée. A l’exécution, l‘application se présente ainsi :



En jouant avec les touches a, A, l, L, p, P, g, G, q, Q, n ou N on peut facilement voir les différences de qualité entre tous les filtres suivant les angles de vue et la distance du cube. Les deux images suivants explicite cela. En mode point, le cube en courte distance donne l’affichage pixélisé suivant :


Mode point


Le zoom sur la face du cube en mode point montre bien l’effet de pixalisation. Maintenant la même face en anisotropie :


La même face en anisotropie


donne un bien meilleur résultat visual. 


Cet exemple n’est qu’une première approche des filtres. Il met en évidence l’importance qu’ils revettent pour le design et l’aspect de nos productions. C’est aussi un élément important à prendre en compte si l’on veut rendre son jeu accessible à tous, un filtre gourmant comme l’anisotrope doit être un choix et non une obligation.


Pour cela vous devez toujours vérifier avant toute chose que la machine sur laquelle tourne votre application supporte la technologie ou la fonctionnalité que vous voulez utiliser. D’une manière générale faites donc toujours des tests par rapport à la propriété GraphicsDeviceCapabilities de l’objet GraphicsDevice. Cette propriété de type GraphicsDeviceCapabilities contient un ensemble de booléean et d’énumération indiquant toutes les fonctionnalités et technologies supportés par le matériel en rapport avec le device courant. Pour savoir si l’anisotrop est supporté, faites un test sur :

this.graphics.GraphicsDevice.GraphicsDeviceCapabilities .TextureFilterCapabilities.SupportsMagnifyAnisotropic

this.graphics.GraphicsDevice.GraphicsDeviceCapabilities.TextureFilterCapabilities.


pour la magnification et sur :

this.graphics.GraphicsDevice.GraphicsDeviceCapabilities .TextureFilterCapabilities.SupportsMinifyAnisotropic

pour la minification.



Remarque : une annexe sera écrite prochainement et consacrée uniquement à l’importance des tests sur les capacités du matériel avant l’exécution.


De même (mais là nous voyons vraiment loin) il est préférable de proproser les fonctionnalités poussées comme ce filtre dans un menu d’option.


 


Le mip mapping



 


Mip vient du latin Multum in parvo, qui signifie « beaucoup de choses dans un petit endroit ».


 


 Principe


 Le mipmapping est une technique visant à améliorer l’affichage des textures sur les polygones en prennant en compte la distance comprise entre le dit polygone et la position de la caméra. Le niveau de détail des textures est adapté à la distance de l’objet. Ainsi, un objet proche de la caméra affichera des textures en haute résolution tandis qu’un objet situé au loin sera plaqué avec une texture de petite taille.


Chaque texture utilisée dans un processus de mip mapping est appellé “niveau de MIP map” ou LOD (pour Level Of Detail). Elles sont choisies suivant la distance à la caméra. La technique du MIP mapping consiste à envoyer au GPU des textures de résolutions décroissantes qui seront utilisées à la place de la texture originale. Le choix de la texture ayant la taille la plus adaptée s’effectue selon la distance du point de vue de l’objet texturé et le niveau de détails nécessaire. Le GPU n’a alors plus qu’à appliquer les bonnes textures sur les bons objets suivant leur éloignement, réadaptant la texture chaque fois que l’objet se rapproche. La texture utilisée lors du rendu sera alors celle dont la résolution est la plus proche de celle de l’objet sur l’image projetée. Chaque niveau doit avoir pour hauteur et largeur la moitié de la hauteur et largeur du niveau précédent. Le dernier niveau a bien évidemment au moins un coté égal à 1.


Par exemple, d’une image d’une taille de 256×256 pixels seront produits les mêmes images aux résolutions de 128×128 pixels, 64×64, 32×32, 16×16, 8×8, 4×4, 2×2 et 1×1. Si la taille de l’objet sur l’image projetée à l’écran est de 30×30 pixels, la texture utilisée sera alors celle de résolution 32×32 pixels.


Le filtrage joue ici un rôle primodial en évitant de voir les “sauts” lors du passage d’une texture à l’autre, grâce à une transition progressive.


 Quel est l’interet de du mip mapping ? Si cette technique est gourmande en mémoire vidéo (elle consomme un tiers de mémoire en plus), elle est plus rapide : La diminution du nombre de texels à traiter et des opérations de filtrages en temps réel de la texture permettent un rendu plus rapide de l’image. De plus le choix des différents niveaux de mipmap peut être laissé à l’apprecciation du développeur : Souvent laisser votre machine opérer pour voir les filtres peut donner un résultat qui s’avère desastreux et ce, même avec les meilleures techniques de filtrage. Prennez l’exemple de l’image suivante qui pourrait se trouver dans un de vos jeu :



Le message qu’elle véhicule est clair : tirer sur la cible fait perdre le joueur. Si nous laissons la machine gérer le mip mapping et la réduction de la texture nous pouvons obtenir le résultat suivant



C’est cette texture qui pourrait etre plaqué sur une forme 3D se trouvant loin du joueur. Celui-ci ne voyant pas le message tirera à coup sur. En fait l’algorythme de réduction d’image ne se préoccupe pas de l’information que l’image porte dans le mesure où il ne peut la quantifier. Il ne prend donc pas en compte l’importance de laisser le texte visible après une réduction n’est donc pas pris en compte dans le calcul du filtre. Si ce n’est pas la machine qui effectue cette tâche mais une personne, celle-ci peut réduire l’image de manière intelligente en gardant l’information visible au maximum :



Paramétrage du Mipmapping


 Deux paramètres peuvent intervenir dans l’affichage d’une texture mipmappée : Le MaxAnisotropy et le MipMapLevelOfDetailBias.


MaxAnisotropy


L’anysotropie est un filtre intelligent et assez gourmant en ressource (bien qu’il soit souvent intégré au hardware des cartes graphiques). Il se base sur l’aspect graphique de l’objet sur lequel on plaque la texture. Les texels ne sont pas tirés d’une forme symétrique, mais plutôt en utilisant un motif irrégulier ajusté à la perspective (on parle de figure anisotropique). Le nombre de texels pris en compte pour ce filtrage détermine la qualité de celui-ci. Avec le filtrage anisotropique 1x, on prend 8 texels. Le niveau maximum est 16x, les plus courants sont 2x (16 texels), 4x (32 texels), 8x (64 texels) et 16x (128 texels). Plus la valeur de MaxAnisotropy est importante, et plus le niveau de filtrage est élevé, et forcement, plus le temps de calcul augmente (et ceci de manière exponentielle…).


MipMapLevelOfDetailBias


Il arrive que pour la beauté du jeu il faille garder un LOD plus longtemps que prévu en retardant le passage à un LOD inférieur lorsque la caméra s’éloigne par exemple. C’est le LOD bias qui permet de réaliser cette opération. Si une surface s’éloigne de l’utilisateur, augmenter le LOD bias revient à retarder l’utilisation du LOD inférieur. Au contraire, en diminuant sa valeur, on passe plus rapidement aux LOD inférieurs.


 


 Après la théorie, la pratique !


 Il est temps de passer aux choses sérieuses. Le sample que nous allons réaliser ici aura pour but de mettre en évidence le mipmaping et les impacts des différents filtres. Nous allons réaliser pout cela un damier de 64 cases sur 64 cases. Chaque case sera associée à une texture “mipmappée”. La camera pourra se déplacer sur ce damier et nous pourrons ainsi voir, en se déplaçant l’évolution des textures sur les différentes cases. Nous aurons trois types de textures : des textures de couleur qui permettrons d’apprecier facilement le passage entre deux LOD, des textures représentant un sol pavé pour voir en réel ce que nos choix produisent, une texture damier pour apprécier la qualité des filtres.


La première étape consiste donc à créer ce qui va être notre premier terrain de jeu. A ce stade de nos connaissances il y a ici aucune difficulté : ayant déjà développé des applications affichants un carré, nous sommes largement en mesure de créer un monde constitué de 64 carrés sur 64 carrés. Ouvrez le projet nommé “TroisiemeProjetMipmap”. Une nouvelle classe Region fait son apparition. Elle sera chargé de la création et de l’affichage d’un terrain. Elle possède un seul vertexbuffer et un seul indexbuffer. Le vertexbuffer contiendra les 64*64 cases du terrain, sachant que chaque case possède 4 vertices. La méthode InitializeVertices se présente comme suit :

private void InitializeVertices(){    float caseSize = 16;    VertexPositionColorTexture[] vertices = new VertexPositionColorTexture[_numberOfVertices];     int index = 0;    for (int j = 0; j < this._depth; j++)    {        for (int i = 0; i < this._width; i++)        {            vertices[index].Position = new Vector3(i * caseSize, j * caseSize, 0f);            vertices[index].Color = this.Color;            vertices[index].TextureCoordinate = new Vector2(0, 0);             vertices[index +1 ].Position = new Vector3(i * caseSize + caseSize, j * caseSize, 0f);            vertices[index +1 ].Color = this.Color;            vertices[index +1 ].TextureCoordinate = new Vector2(1, 0);             vertices[index + 2].Position = new Vector3(i * caseSize + caseSize, j * caseSize – caseSize, 0f);            vertices[index + 2].Color = this.Color;            vertices[index + 2].TextureCoordinate = new Vector2(1, 1);             vertices[index + 3].Position = new Vector3(i * caseSize, j * caseSize – caseSize, 0f);            vertices[index + 3].Color = this.Color;            vertices[index + 3].TextureCoordinate = new Vector2(0, 1);             index+=4;        }    }     this._vertexBuffer = new VertexBuffer(    this._device,    typeof(VertexPositionColorTexture),    _numberOfVertices,    ResourceUsage.WriteOnly,    ResourceManagementMode.Automatic);     this._vertexBuffer.SetData(vertices);

}


Les membres depth et width représentent la profondeur et la largeur du monde (ils possèdent donc pour valeur 64 chacun donc). La double boucle for parcours le monde de jeu sur chaque ligne et chaque colonne en créant un carré à chaque fois. La valeur caseSize représente la taille d’un carré. Rien de compliqué ici. La méthode InitializeIndices est la suivante :

private void InitializeIndices(){    short[] indices = new short[_numberOfIndices];     short index = 0;    short indexCase = 0;    for (int j = 0; j < this._depth; j++)    {        for (int i = 0; i < this._width; i++)        {            indices[index] = indexCase;            indices[index + 1] = (short)(indexCase + 1);            indices[index + 2] = (short)(indexCase + 3);            indices[index + 3] = (short)(indexCase + 1);            indices[index + 4] = (short)(indexCase + 2);            indices[index + 5] = (short)(indexCase + 3);            index += 6;            indexCase += 4;        }    }     this._indexBuffer = new IndexBuffer(        this._device,        typeof(short),        _numberOfIndices,        ResourceUsage.WriteOnly,        ResourceManagementMode.Automatic);     this._indexBuffer.SetData(indices);

}


Nous spécifions 6 indices pour chaque carrés (une case équivaut à deux triangles de 3 points). Rien de vraiment compliqué là encore. Terminons sur cette classe en ajoutant qu’une propriété TextureMipMapFilter a été ajoutée afin de pouvoir spécifier un filtre mipmap depuis la classe Game1.



Chaque texture utilisée sera un mipmap composé de 6 LODs (256*256, 128, 60, 32, 16 et enfin 8). De la même façons que le premier projet de ce chapitre nous allons incorporer les LODs de chaque texture dans le projet. La texture colorée possédera les LOD suivants :



chaque couleur permettra de bien discerner les niveaux de mip map utilisé. La texture de sol utilisera les LOD suivants:


 


Chaque LOD a été placé dans un répertoire du projet nommé MipMapTextures de la même façon que pour le premier projet de ce chapitre. C’est la classe Game1 qui va former les trois textures composée de 6 LODs. Dans celle-ci se trouvent trois méthodes similaires nommées GenerateMipmap, GenerateMipmapTexture et GenerateMipmapCase respectivement pour la texture colorée, de sol et de damier. A noter qu’on aurait pu ici factoriser le code en une seule méthode, mais pour des raisons de compréhension, cela n’a pas été fait. Ces trois méthodes étant similaires, nous n’en verrons qu’une :

private void GenerateMipmap(){    Texture2D mainTexture = new Texture2D(this.graphics.GraphicsDevice,        256, 256,        6,        ResourceUsage.Dynamic,        SurfaceFormat.Color, //std 32 bits mode        ResourceManagementMode.Manual);      Texture2D texture256 = content.Load<Texture2D>(“MipmapTextures\\256″);    Texture2D texture128 = content.Load<Texture2D>(“MipmapTextures\\128″);    Texture2D texture064 = content.Load<Texture2D>(“MipmapTextures\\64″);    Texture2D texture032 = content.Load<Texture2D>(“MipmapTextures\\32″);    Texture2D texture016 = content.Load<Texture2D>(“MipmapTextures\\16″);    Texture2D texture008 = content.Load<Texture2D>(“MipmapTextures\\8″);     uint[] array = new uint[256 * 256];    texture256.GetData<uint>(array);    mainTexture.SetData<uint>(0, null, array, 0, array.Length, SetDataOptions.None);     array = new uint[128 * 128];    texture128.GetData<uint>(array);    mainTexture.SetData<uint>(1, null, array, 0, array.Length, SetDataOptions.None);     array = new uint[64 * 64];    texture064.GetData<uint>(array);    mainTexture.SetData<uint>(2, null, array, 0, array.Length, SetDataOptions.None);     array = new uint[32 * 32];    texture032.GetData<uint>(array);    mainTexture.SetData<uint>(3, null, array, 0, array.Length, SetDataOptions.None);     array = new uint[16 * 16];    texture016.GetData<uint>(array);    mainTexture.SetData<uint>(4, null, array, 0, array.Length, SetDataOptions.None);     array = new uint[8 * 8];    texture008.GetData<uint>(array);    mainTexture.SetData<uint>(5, null, array, 0, array.Length, SetDataOptions.None);     _sampleMipmapColor = mainTexture;} 


Cette méthode charge dans une variable locale nommée 6 LOD. Si quelque chose vous semble obscur de les explications suivent, reportez vous à la présentation du premier projet au tout début de ce chapitre. La première instruction

    Texture2D mainTexture = new Texture2D(this.graphics.GraphicsDevice,
        256, 256,
        6,
        ResourceUsage.Dynamic,
        SurfaceFormat.Color, //std 32 bits mode
        ResourceManagementMode.Manual);

créé la texture qui va contenir les LOD. Nous n’utilisons pas la classe Content pour la créer et nous aurons donc à la gérer nous même (nous reviendrons sur ce point plus loin). Une référence vers le device est bien évidemment donnée ainsi qu’une taille de 256*256. C’est à dire la taille du premier Level Of Detail. La valeur 6 représente le nombre de LOD que nous utilisonsResourceUsage.Dynamic indique que nous laissons le driver décider ou placer le buffer contenant les données de la texture. En général si il y’a la place, celui-ci est placé dans la mémoire AGP, sinon dans la mémoire vidéo, sinon en dernier cas dans la RAM. Un bon jeu sais se référer à la taille mémoire pour placer les textures les plus utilisées en AGP et les autres moins importantes dans la RAM afin d’optimiser les affichages (nous vérrons celà dans la quatrième partie de cet apprentissage). Le paramètre SurfaceFormat.Color indique simplement que la texture aura un format de type 32 bits standard (ARGB : 8bits pour l’alpha, 8 pour la composante Rouge, 8 pour le vert et 8 pour le bleu, soit 256 variations par composantes). Enfin ResourceManagementMode.Manual précise que nous gérons nous même la vie de la texture (a nous de la recréer lorsqu’elle est “perdue”. Vient ensuite le chargement des 6 LODs : 

    Texture2D texture256 = content.Load<Texture2D>(“MipmapTextures\\256″);
    Texture2D texture128 = content.Load<Texture2D>(“MipmapTextures\\128″);
    Texture2D texture064 = content.Load<Texture2D>(“MipmapTextures\\64″);
    Texture2D texture032 = content.Load<Texture2D>(“MipmapTextures\\32″);
    Texture2D texture016 = content.Load<Texture2D>(“MipmapTextures\\16″);
    Texture2D texture008 = content.Load<Texture2D>(“MipmapTextures\\8″);

Rien de compliqué ici. La valeur “256” est le nom Asset de la texture et “MipmapTextures” le path vers celle-ci (voir premier projet au début de ce chapitre). Les instructions qui suivent créént le Mipmap. Mais qu’est ce qu’un Mipmap sur le plan technique ? Il s’agit (pour parler simplement) d’une sorte de liste chainée. Chaque texture à l’intérieur possède pour taille la moitiée de son prédécesseur et est deux fois plus grande que son successeur directe. La première texture de cette liste à la taille la plus importante, la dernière la plus petite. Cette liste va être “ratachée” à une texture qui va faire office de “pointeur”. Suivant le degré de détail voulue, la texture va pointer sur telle ou telle élément de la liste. Créer cette structure en Xna/C# est relativement aisé. Il nous suffit d’associer une texture (notre pointeur) à un ensemble de buffers correspondant aux différents LODs. C’est les méthodes GetData et SetData qui vont nous permettre cela. Prenons exemple du chargement du buffer correspondant au troisième LOD :

    array = new uint[64 * 64];
    texture064.GetData<uint>(array);
    mainTexture.SetData<uint>(2, null, array, 0, array.Length, SetDataOptions.None);

 Première instruction, nous créeons un array de uint (entier non signés) qui va faire office de buffer. La seconde instruction charge ce buffer à partir des données de la texture ayant pour taille 64 par 64. Enfin, la troisième instruction associe ce buffer au troisième LOD (index 2) de la texture “pointeur” (mainTexture). Le premier paramètre de  correspond au niveau de LOD voulu, le second correspond à un objet de type Rectangle pour indiquer la zone du buffer que nous remplissons (ici null donc nous chargeons tout le buffer). Nous passons ensuite le buffer lui-même, indiquons à partir de quel index nous le lisons et sa taille. Le dernier paramètre de type SetDataOptions spécifie la manière dont on écrase le buffer déjà associé à ce LOD (si il y’en a un) : supprimé, comblé ou surchargé (soit respectivement SetDataOptions.Discard, SetDataOptions.NoOverwrite, SetDataOptions.None).


La partie LOD de notre programme est terminée ! Ne reste plus qu’à gérer le déplacement sur le terrain de jeu et la modification des différents filtres à l’aide des touches clavier. Pour cela, une simple analyse de la méthode Update vous suffira à ce stade de cos connaissances. Avant de lancer le programme, jetez juste un oeil à la méthode LoadGraphicsContent :

/// <summary>/// Load your graphics content.  If loadAllContent is true, you should/// load content from both ResourceManagementMode pools.  Otherwise, just/// load ResourceManagementMode.Manual content./// </summary>/// <param name=”loadAllContent”>Which type of content to load.</param>protected override void LoadGraphicsContent(bool loadAllContent){    if (loadAllContent)    {    }     // TODO: Load any ResourceManagementMode.Manual content    this.GenerateMipmapTexture();    this.GenerateMipmap();    this.GenerateMipmapCase();    this._terrain.Texture = _sampleMipmapColor;

}


Nous y plaçons le chargement de nos textures. Dans la mesure ou les 3 mipmaps sont chargés à l’aide de l’option ResourceManagementMode.Manual ce chargement se fait à l’extérieur du bloc conditionnelif (loadAllContent)“.


Il y’a deux types d’appel à cette méthode dans la vie d’une application Xna : le premier appel lors du lancement de l’application passe à cette méthode la valeur trueDe ce fait toutes les ressources sont chargés à la fois dans le bloc if et à l’extérieur. Le second appel intervient lorsqu’il y’a perte de ressources (après un redimentionnement ou une  réduction de la fenêtre … tout ce qui peut obliger le device graphique à s’occuper d’une autre application au détriment de la notre), à ce moment là, Xna passe à la méthode falseSeules les ressources en dehors du bloc conditionnel sont donc chargés : à ce stade les ressources ResourceManagementMode.Automatic ont déjà été rechargée automatiquement.Dans la même sens nous devons gérer nous même la libération de ces ressources dans la méthode UnloadGraphicsContent :

/// <summary>/// Unload your graphics content.  If unloadAllContent is true, you should/// unload content from both ResourceManagementMode pools.  Otherwise, just/// unload ResourceManagementMode.Manual content.  Manual content will get/// Disposed by the GraphicsDevice during a Reset./// </summary>/// <param name=”unloadAllContent”>Which type of content to unload.</param>protected override void UnloadGraphicsContent(bool unloadAllContent){    if (unloadAllContent == true)    {        content.Unload();    }    this._sampleMipmapAuto.Dispose();    this._sampleMipmapColor.Dispose();    this._sampleMipmapTexture.Dispose();

}



là encore à l’extérieur du bloc conditionnel.


Pour preuve déplacez les quatres instructions de la méthode de chargement dans le bloc if. Lancez le programme et agrandissez la fenêtre : vous obtiendez une belle erreur spécifiant que le programme essaye d’accéder à une texture supprimée. Et pour cause au redimentionnement de la fenêtre les textures sont perdues et sont donc libérée de la mémoire. Lorsque le programme reprend la main il essaye d’utiliser ces textures pour l’affichage et plante forcement.


 


L’application


Les touches de l’application sont les suivantes :  la touche ‘A’ pour anisotope, ‘L’ pour linéaire, ‘P’ pour point, ‘G’ pour Gaussian, ‘Q’ pour Pyramidal, ‘N’ pour aucun. Si la touche Shift est appuyée en même temps, le filtre s’applique en magnification, sinon, en minification. Si la touche Alt est appuyée, le filtre s’applique au Mipmap. Les touches fléchées permettent de se déplacer dans le monde. Les touches Page Haut et Page Bas permettent de prendre de l’altitude et de redescendre. Enfin les touche + et – augmentent le paramètre MaxAnisotropy (voir plus haut). Les mêmes touches avec Shift appuyé modifient le LodBias (voir plus haut). La touche espace ajoute une grille qui permet de bien discerner les cases du terrain de jeu (en fait le terrain est affiché une deuxième fois en mode WireFrame). Enfin les touches F1, F2, F3 affichent respectivement les texture de couleur, sol, damier.


Au lancement de l’application, vous obtenez l’affichage suivant : 



Magnifiquement … rouge, et pour cause : le mipmap n’est pas activé (None). Dans ce cas, le programme n’utilise que le LOD 0 qui correspond à la texture 256*256 qui est rouge. Appuyez sut les touches Alt et A pou lancer le mipmapping avec le filtre anisotropique. L’écran se change en :



Les différentes strates que vous voyez là correspondent aux 6 textures de LOD que nous avons chargée. On remarque parfaitement que suivant la distance à la caméra, les différentes cases du terrain de jeu affichent une texture adaptée à leur taille à l’écran :


 


Maintenant en maintenant la touche Shift appuyée appuyez sur la touche + (Add). Ceci a pour conséquence d’augmenter le Bias et donc le passage au LOD suivant :



Shift avec la touche – (Substract) réduit au contraire le Bias et prolonge la vie du LOD 0 :



Le filtre mipmap que nous avons utilisé ici (Anisotropique) est sans doute le meilleur. Le passage d’un Lod à un autre se fait de manière progressive et douce. Prennez un chouïa d’altitude à l’aide de la touche Page Haut. Si vous affichez la grille à l’aide de la touche Espace vous rendrez compte que cette transition ne dépend absolument pas de la case mais bien de la distance du “point” dans l’espace qui recoit la texture à la caméra :



Pour comprendre la puissance du filtre anisotropique, changez le filtre en filtre Point en appuyant sur les touches Alt + P. L’affichage qui apparait alors est rédibitoire … :



Passons à un autre type de texture, la texture sol. Appuyez sur la touche F2 et annulez tout mipmaping à l’aide de la combinaisons Alt +  N. Vous obtenez l’affichage suivant :



Aucun mipmapping et aucun filtre en mignification donne un résultat plus que décevant. Améliorons tout cela : Ajoutez un mipmapping avec filtre point (combinaison Alt + P) :



L’affichage est meilleur mais comme le montrent les deux lignes rouges nous voyons parfaitement les lignes de transition entre un Lod et un autre (avec la texture de couleur c’est encore plus flagrant). Et pour cause : le filtre point pour le mipmapping est des plus simples : lorsqu’il y’a besoin de faire une transition vers un autre LOD il utilise directement les texels de la texture voulue sans essayer de rendre la transition progressive. Si vous vous déplacez à l’aide des touches fléchées vous remarquez encore mieux cette transition. Nous pouvons toujours appliquer un filtre lineaire en mignification (touche L) :


 


mieux, mais la transition est toujours visible… Si on applique maintenant un filtre linaire pour le mipmapping (Alt + L), le rendu est quasi parfait :



On parle dans le cas du mipmapping avec filtre linaire de “filtrage trilineaire”. Ce filtrage effectue une moyenne progressive entre les texels des textures concernées par la transitions d’un LOD à un autre. Même si vous vous déplacez, vous ne verrez pas la transition d’un LOD à un autre. A noter que le filtre trilinaire ou le filtre anisotropique donnent pratiquement le même résultat dans la mesure où nous affichons des formes relativement “droites”.


Revenez à un affichage sans filtre (touche N et shift N) et sans mipmapping (touche Alt + N). Affichez la texture Damier avec F3 :



On voit que l’affichage n’arrive absolument pas a gérer les lignes horizontales et verticales de la texture. Combien même vous affichez un filtre Linaire en mignification et magnification on garde un affichage desastreux.


 


mais si vous appliquez un filtre mipmapping en anisotropie l’affichage est parfait :


 


Nous en avons terminé avec le mipmapping. Un point très important pour la fluidité et la beauté de nos applications 3D. Continuez à vous familiariser avec cette application qui met parfaitement en évidence les filtres de mignification et magnification et les filtres de mipmapping.


Conclusion


 


 


 


 


 


telecharger Vous pouvez télécharger le sample, les deux moteurs et les exercices ici.   


 


 


Retourner au sommaire des cours 


 


</ en construction / under construction>


 

43 thoughts on “XNA Tutorial 8 : Les textures”

  1. Bonsoir,

    Je trouves vos tutoriaux exellent, vivement la suite !

    Un petit tutorial sur les shaders et les fameux fichiers effets serait le bienvenu car je m’y perd ;)

  2. salut,

    juste une petite erreur dans la ligne:

    Déclarez un projet de type Texture2D dans la classe Game1.
    au lieu de
    Déclarer un objet

    vivement la suite

  3. Tout cela est fort intéressant. Merci d’écrire ces tuturiaux. Ils sont forts utilisés pour les débutants en 3D comme moi.

  4. dabord merci pour tes efforts pour les newbees comme moi.
    j’aurais plusieurs questions: comment se positionne directx9, 10 et xna pour toi? quels sont les avantages de chacun. pourquoi XNA est sorti alors microsoft doit chercher a promovoir direct x10.
    est ce que c’est totalement different?
    estil possible de creer un exe de XNA sans pour autant que le client doit avoir xna installé sur sa machine?

    merci encore…

  5. Pour Masu:

    XNA et DirectX 10 n’ont pas grand chose à voir l’un avec l’autre, et concerne des marchés différents. Microsoft communique beaucoup sur le théme “XBOX 360, la seule console Next Generation programmable par des amateurs, grâce au XNA..”. A mon sens, le XNA a été conçu comme un outil stratégique pour promouvoir les ventes de la XBOX 360. Sans jeu une console n’est rien.. Quelques centaines de jeux amateurs seraient un énorme avantage pour la XBOX 360 dans la guerre des consoles, qui ne fait que commencer.

    Microsoft mise beaucoup sur le XBOX LIVE, où les gens peuvent acheter des jeux XBOX 360 directement en ligne. Quand le XNA PRO sera disponible d’ici quelques mois, un éditeur pourra créer un jeu XBOX 360 et le vendre directement en ligne sur XBOX LIVE, en versant une commission a Microsoft. A terme cela peut représenter un énorme marché.

    Et ne parlons pas du baladeur mp3 Zune qui sera aussi programmable avec le XNA d’ici peu.. Ainsi que du projet de console portable Microsoft, qui doit sortir d’ici un ou deux ans.

  6. Pour Masu (suite)

    Il n’est pas possible de créer un exe XNA directement utilisable sur un pc. Cependant Microsoft fournit un petit programme d’installation de quelques Mo permettant d’installer le Framework d’exécution XNA sur n’importe quel pc. Tu peut le distribuer librement avec tes logiciels.

    Le XNA est entièrement libre et gratuit sur PC, ce qui est loin d’être le cas sur la XBOX 360, où seul les membres du Club des créateurs XNA (99 euros par an) peuvent exécuter des jeux XNA. Oué c’est n’importe quoi..

    Il faudra attendre la sortie proche de XNA PRO (arg.. plus de 1.000 euros!) pour pouvoir créer des jeux exécutables sur une XBOX 360 de base.

  7. Oui, les fonctions du XNA appellent en interne les fonctions DirectX, du moins sur PC. C’est parce que XNA est une “surcouche virtuelle” utilisant les fonctions graphiques présentes de base sur les machines.

    S’il y avais un XNA pour Apple (peu probable) les fonctions utiliseraient en interne les fonctions graphiques Mac.

    Interrogé sur la portabilité de XNA, un responsable de Microsoft a déclaré, sur un ton ironique, que le XNA pourrait être adapté sur Wii et PS3 si Nintendo et Sony en faisaient la demande.

  8. salut Valentin,

    tout d’abord felicitation pour tes tutoriaux les meilleurs en francais dois je avouer :)

    J’aurais une bête petite question, qu’elle serait la meilleure manière de procéder (niveau performances) si je désire par exemple dessiner 3 cubes à l’écran :

    - Définir un tableau de vertices et d’indices pour chacuns des cubes et faire 3 appel de la fonction DrawIndexedPrimitives() ?

    - Definir un tableau de vertices contenant l’ensemble  des vertices des 3 cubes, de même pour les indices. Et finalement faire un seul appel de DrawIndexedPrimitives() ?

    Merci de bien répondre :)

    Bonne journée

  9. ( c’est encore moi juste pour dire que j’ai laissé un petit copié/collé de code qui n’a pas grand chose à faire là. Mes excuses ^^ )

  10. pas de soucis, j’ai enlevé le copié collé,

    Pour ta question je n’aurais qu’une seule réponse :
    tout dépend de la manière dont tu percois tes trois cubes : Si c’est comme un tout (ils forment à eux trois un seul et même modele 3D) alors oui un seul appel est préférable parceque plus rapide (mais franchement c’est imperceptible si tu ne répètes pas cette opération plusieurs centaines de fois).

    Si par contre il s’agit de trois objets 3D différents et qu’il est possible dans le “scénario” de ton application que d’autres cubes arrivent, ne t’embete pas avec une centralisation des appels à DrawIndexedPrimitives et “modularise” ton code.

    N’hesites pas à me donner plus de détails.

    merci d’avoir avoué ton commentaire :)

  11. Merci de répondre aussi rapidement :)

    Donc voilà en gros mon problème :

    J’ai écris un loader de map pour charger mes maps dans un format bien spécifique que j’ai creer. En gros le fichier map se compose :

    – d’un tableau de “Polygone”, contenant l’ensemble des polygones de la map.
    – chaque classe “Polygone” encapsulant à leur tour un tableau “Vertex”, contenant evidemment les coordonnées des points composant le polygone ^^)

    J’ai implementer une methode “Render()” dans ma classe “Polygone” qui s’occupe de dessiner tout les tout les points du polygone ainsi que les indices.

    Dans la methode Draw() du programme principal j’appel la fonction Render() pour tout les polygones de ma map.
    Résultat : pour une map de 4433 polygones et 8696 indices je suis à 2 fps voir 3 max :(
    Je sais que mon xboite peut faire beaucoup mieu et que je crois en elle ^^

    J’ai du me planter qqpart comme tout débutant mais ou je cherche encore :(

    Malheureusement il y a un gros manque d’informations sur le net concernant la gestion de plus grand espaces et au niveau optimisation, je vois que tu parlais vaguement de frustum culling, occlusion culling … j’ai pensé à l’impémenter mais je me suis dit qu’à ce stade ça n’en veut pas la peine surtout pour une petite map de 4433 polygones qui ne devrait à la base ne poser aucun problème vu les capacitées de la bête.

    Merci

  12. Oui les 4 cullings (distance, frustum, occlusion et backface) seront abordés dans 4 tutoriaux à peu pres.

    4433 polygones ce n’est pas énorme… obtenir 3 fps pour ca c’est très etonnant. Regarde cette image :
    http://msmvps.com/photos/valentin/images/477487/original.aspx
    j’ai bien plus que 4000 polygones ici (tu peux multiplier allegrement par 10) et j’ai 200 fps.

    Une phrase me titille :

    “j’appel la fonction Render() pour tout les polygones de ma map.” tu appelle donc 4433 fois la méthode render ?

  13. oui c’est exact j’appelle bien autant de fois :-x
    Mais bon je ne voyais pas trop comment procéder sachant que je n’utilise aucunes de ces techniques de culling.

    Peut-etre aurait t’il été mieu que je redesinne seulement à chaque mouvement de caméra ?

    Merci

  14. ah oui … quand meme … :)
    “Il y’a des méthodes plus simples … mais elles sont meilleures”
    C’est donc là que vient ton problème. Pourquoi ne fais tu pas un seul vertex buffer et index buffer pour tous les polygones et donc un seul Draw ?

  15. je pensais essayer ça, mais je viens de penser si je fais un buffer de vertices contigus, il n’y aurait pas un probleme au niveau des indices vu qu’ils sont relatifs à chaque polygone?

  16. c’est là toute l’astuce. Lorsque je veux créer un terrain vite fait pour faire mes tests je procède comme ceci :

    Je pars du principe que mon terrain peut être découpé comme un damier, c’est à dire de grosses cases. Chaque case étant elle même composée de carrés, chaque carré étant composé de deux polygones / triangles.

    Une case pour moi est composée de 16 * 16  carrés. Donc 512 triangles. Je me créé donc une classe Region (qui correspond à une case) qui contient un vertex et un index buffer. Et le tour est joué. Les indices de l’indexbuffer seront toujours les mêmes puisque si l’altitude z des vertices changent, la position en x et y ne varie pas. Les carrés se trouvent toujours au même emplacement.

    Tu vois à peu pres le principe ? Dans le moteur que j’ai mis en téléchargement c’est ce principe qui est appliqué. Pour se faire des tests ou des moteurs non commerciaux c’est très très rapide.

    Si tu patiente un peu c’est le tutorial 9…

  17. merci beaucoup pour ton aide j’ai réussis à regler mon prob en utilisant en effet un seul meme buffer comme tu disais, et j’ai trouvé la ptite astuce pour les indices (la valeur de l’indice n + le nombre de vertex du polygone précédent pour avoir l’indice n du polygon courant ^^)

    Encore merci et j’attend impatiemment tes nouveaux tuto :)

    Bonne soirée!

  18. Trés intéressante cette explication sur les filtres. je comprend maintenant certaines choses qui me laissait perplexe. Cependant, je me demande quelle est la charge de calculs necessaire a chaque filtre. Pourrait-tu ajouter a ton logiciel de démonstration un compteur de performances ? Histoire de comparer le ratio efficacité/coût des techniques de filtrages sur plusieurs configurations, ainsi que sur la XBOX 360..

  19. Ce tutorial parle des textures bitmaps. J’ai lu quelque part que les concepteurs de la XBOX 360 ont privilégiés les textures procédurales, pour minimiser l’occupation mémoire. Si j’ai bien compris ce sont des “textures virtuelles” définies par des paramétres mathématiques, permettant de reproduire l’aspect de certains matériaux. Cela existe en XNA ?

  20. Oui les textures procédurales permettent une amélioration des performante et un cout en mémoire réduit. On ne manipule pas des pixels par milliers mais juste le résultat d’une équition simple, on ne sauvegarde pas une texture entière, mais juste une formule. Très utile pour les supports de destinations “clos” comme les console qui sont toutes baties sur le même modèle et donc pas conséquent non extensibles comme un PC. En ce moment, lorsque tu lis ce message, tous les widgets que tu vois à l’écran (comme le bouton d’ajout de commentaire, la scroll bar à gauche, les menus du browser en haut) sont réalisés avec cette technique. Pour l’OS c’est bien puls performant que de manipuler une texture de bouton.

  21. Donc pas besoin de mipmap ni de filtrage pour ces textures procédurales ? Aurait-tu un exemple d’utilisation de ces textures mathématiques ?

  22. Pour ma part j’aurais plutôt pensé que les éléments d’interfaces dont tu parle, comme le bouton d’ajout de commentaires, la scroll bar et le menu déroulant sont dessinés en vectoriel. Ce sont des lignes droites, des rectangles, des triangles, quelques arrondis pour les coins, des zones de couleurs et du texte. Bref, des formes faciles a dessiner avec des fonctions graphiques vectoriels.

  23. –>”Donc pas besoin de mipmap ni de filtrage pour ces textures procédurales ? Aurait-tu un exemple d’utilisation de ces textures mathématiques ?”

    Si il y’a besoin on a toujours besoin d’avoir un ensemble de texture adaptée à la taille d’un objet. Le fait est ici car les mipmaps n’ont à priori pas à être générés par l’utilisation d’un filtre mais par l’utilisation de la formule ou des composants vectoriels adaptés à la taille du support.

    –>”Pour ma part j’aurais plutôt pensé que les éléments d’interfaces dont tu parle, comme le bouton d’ajout de commentaires, la scroll bar et le menu déroulant sont dessinés en vectoriel. Ce sont des lignes droites, des rectangles, des triangles, quelques arrondis pour les coins, des zones de couleurs et du texte. Bref, des formes faciles a dessiner avec des fonctions graphiques vectoriels. ”
    Ca reste pour moi du procedural, on ne dispose pas de texture en tant que tel avec un aggrégat de pixels mais plutot un plan schématique pour la construire. Ce plan indique comment juxtaposer tes éléments vectoriels comme tu le dis parfaitement ou comment appliquer une formule.

    -> jago138 ok :)
    vais étudier la chose parce que pour l’instant les perfs je les mesure avec perfmon et c pas ce qu’il y’a de plus friendly à utiliser :)

  24. Un grand merci pour toutes ces information …

    j’ai deux question :

    – ou trouves-tu toutes ces informtions pour apprendre a utilisr XNA ?

    – a quand le prochaine tuto ? :)
    merci, tes tuto sont des vrai petites merseile pour les débutants !

  25. Merci pour tes remarques :)
    Alors question 1 :

    J’utilise trois moyens pour me former à une nouvelle techno :

    Je regarde les sample fournis par Ms
    Je regarde l’aide de Ms qui offre des tas de petits articles pour débuter
    J’utilise reflector pour entrer dans le code du framework Xna et analyse le fonctionnement. Pour des articles comme le content pipeline très utile.

    Après unc ertain temps je vole de mes propres ailes et je teste, je tatonne, le tout avec reflector à portée de main.

    Question 2 :

    Courant avril : deux tutos : Lumières et affichage de modèles.

  26. Merci beaucoup pour les tutoriels que tu as rédigé,ils sont vachement interessants.Est-ce que vous pouvez m’aider à afficher 6 images differentes sur les six faces de mon cube 3D??? je n’arrive pas à charger les 6,le code ne prend en compte que la dernière et du coup il m’affiche la meme image sur les 6 faces, alors que ce n’est pas ce qui m’est demandé.SVP si vous avez une solution,nhésitez pas à m’informer ,j’ai une présentation demain (je suis en stage de fin d’études) Merci d’avance
    Bonne journèe à tous

  27. please help !
    je n’arrive pas à afficher 6 images jpeg ou bmp peu importe sur les 6 faces du cube 3D,le programme prend en compte que la dernière??

  28. ok je vais t’aider.
    tu as deux solutions possibles

    Solution 1 :
    Soit une seule texture qui contient les six faces de ton cube.
    Tu dois alors donner les bonnes coordonnées de textures pour chaque vertex du cube.

    Solution 2 :
    La classe cube n’a plus un mais 6 objets Texture2D (un par face). Tu ne dois plus dessiner le cube en tant qu’un seul objet (et donc un seul vertexbuffer) mais avec 6 objets c’est à dire 6 faces et donc 6 vertexbuffer.

  29. Tout d’abord bravo pour la clarté de tes tutos.
    J’ai une question sur ce tuto 8 tout de même.

    Si j’ai bien compris la source, tu utilises autant de carrés texturés que spécifié par les paramètres depth et width. Pourquoi ne pas avoir placé seulement un très grand carré de 2 triangles dont on aurait modifié les coordonnées de textures pour se répéter (>1) ?

  30. alors la raison est très simple
    Tout d’abord sur le principe tu as raison. Mais ceci n’ira que pour des samples et non pour des productions un tant soit peu évoluée.

    Il faut que tu te repporte aux premiers sample sur le cube texturé de ce chapitre. Au départ j’utilise un vertex pour plusieurs faces (jusqu’à 3 faces utilisant le même vertex). Ce vertex pour la première face s’apparentera disons au coin au gauche, mais il sera le coin bas droite de la seconde face et le coin haut gauche de la troisième. Ainsi il est impossible à ce stade de nos connaissances de pouvoir spécifier une coordonnée de texture suivant la face à laquelle appartient le vertex. La solution simple consiste alors à créer 4 vertices par carrés.

    Cette solution est evidemment plus lourde et plus consommatrice de cpu mais elle est néanmoins la plus pusisante pour créer des terrains réalistes. Couplée avec du culling on récupère grandement le temps CPU et GPU perdu.

  31. Salut,

    j’en suis à la partie sur les effets, et le code sur les textures me laisse pantois : comment fait-on pour appliquer différentes textures, par exemple une texture différente par face du cube ? Nulle part il n’est précisé qu’on utilise la texture “texture” et pas une “texture2″ qu’on aurait déclarée plus tôt.

  32. Effectivement, ma question n’est pas claire ; reformulons : en lisant sur cette page la section consacrée à l’introduction aux textures, il m’est apparu que nulle part on ne fait appel à la texture “texture”, ce qui implique que tous les carrés dessinés à l’écran portent le même dessin. Comment faire pour afficher plusieurs textures, une différente par carré ? (en gros, je n’ai pas compris la solution 2 dans ta réponse à Niss sur cette page).

    La texture par défaut étant la dernière chargée, est-il possible d’éviter de passer par les SpriteBatches ?

  33. Bonjour

    Juste une petite remarque concernant les images, je sais que ce post date de 2006 mais il est vraiment dommage d’avoir une erreur 404 quand on veut voir les images.

  34. Salut,
    Tes tutos m’ont vraiment beaucoup aidé à apprendre la 3D mais je reste assez perplexe sur le fichier effet. Enfaite j’ai la dernière version de XNA et les paramètres comme effet.Techniques[“Textured”] ou effet.Parameters[“xTexture”] n’y sont pas dans cette nouvelle version et j’aimerais savoir par quoi doit-on les remplacer :S

    Merci d’avance ^^

    PS: Ce serait sympas un tuto pour les débutants sur les effets de particules en 2D avec la création de list de Vector etc… ^^

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>