XNA Tutorial 6 : Les indices

Retourner au sommaire des cours  

Nous avions vu dans le troisième tutorial (ici) qu'il y avait plusieurs type de reliasons. Toutes ont des avantages et des défauts. Si la reliaison TriangleList permet de créer n'importe quelle forme, elle demande un grand nombre de vertices. TriangleFan ne permet que de faire des formes coniques, mais avec peu de vertices. Enfin TriangleStrip est à préférer dans l'assemblage de triangles côtes à côtes. Certaines formes trop complexes nous obligent à utiliser TriangleList au prix d'un grand nombre de vertices. Le précédent tutorial illustre parfaitement ce type de problème : Pour un simple cube, nous avons du créer 36 vertices ! Les indices sont une solution très efficaces à ce problème.

Les indices

36 vertices pour faire un cube ... c'est énorme. Encore plus si on regarde le tableau de vertices sous cet angle :

vertices[0].Position = new Vector3(-10f, -10f, -10f);vertices[1].Position = new Vector3(-10f, -10f, 10f);vertices[2].Position = new Vector3(10f, -10f, 10f);vertices[3].Position = new Vector3(-10f, -10f, -10f);vertices[4].Position = new Vector3(10f, -10f, 10f);vertices[5].Position = new Vector3(10f, -10f, -10f);verticesDevil.Position = new Vector3(10f, -10f, -10f);vertices[7].Position = new Vector3(10f, -10f, 10f);verticesMusic.Position = new Vector3(10f, 10f, 10f);vertices[9].Position = new Vector3(10f, -10f, -10f);vertices[10].Position = new Vector3(10f, 10f, 10f);vertices[11].Position = new Vector3(10f, 10f, -10f);vertices[12].Position = new Vector3(10f, 10f, -10f);vertices[13].Position = new Vector3(10f, 10f, 10f);vertices[14].Position = new Vector3(-10f, 10f, 10f);vertices[15].Position = new Vector3(10f, 10f, -10f); vertices[16].Position = new Vector3(-10f, 10f, 10f);vertices[17].Position = new Vector3(-10f, 10f, -10f);vertices[18].Position = new Vector3(-10f, 10f, -10f); vertices[19].Position = new Vector3(-10f, 10f, 10f);vertices[20].Position = new Vector3(-10f, -10f, 10f);vertices[21].Position = new Vector3(-10f, 10f, -10f); vertices[22].Position = new Vector3(-10f, -10f, 10f);vertices[23].Position = new Vector3(-10f, -10f, -10f);vertices[24].Position = new Vector3(-10f, 10f, -10f); vertices[25].Position = new Vector3(-10f, -10f, -10f);vertices[26].Position = new Vector3(10f, -10f, -10f);vertices[27].Position = new Vector3(-10f, 10f, -10f); vertices[28].Position = new Vector3(10f, -10f, -10f);vertices[29].Position = new Vector3(10f, 10f, -10f);vertices[30].Position = new Vector3(-10f, 10f, 10f); vertices[31].Position = new Vector3(-10f, -10f, 10f);vertices[32].Position = new Vector3(10f, -10f, 10f);vertices[33].Position = new Vector3(-10f, 10f, 10f);vertices[34].Position = new Vector3(10f, -10f, 10f);

vertices[35].Position = new Vector3(10f, 10f, 10f);

J'ai mit en évidence les points qui sont utilisés plusieurs fois à l'aide de couleur. On remarque que seule 8 couleurs sont utilisées et donc que seuls 8 points sont nécessaire pour créer un cube. Mais la contrainte du TriangleList nous oblige a utiliser un même point pour chaque triangle qui l'utilise. Les indices permettent de spécifier la reutilisabilité d'un point. Le système est assez comparable au jeu "reliez les points" :


Figure (a)

Appliquons ce système au cube. Le tableau de vertices n'aura maintenant que 8 cases correspondant au 8 vertices uniques que nous avons déterminé plus haut :

vertices[0].Position = new Vector3(-10f, -10f, -10f);vertices[1].Position = new Vector3(-10f, -10f, 10f);vertices[2].Position = new Vector3(10f, -10f, 10f);vertices[3].Position = new Vector3(10f, -10f, -10f);vertices[4].Position = new Vector3(10f, 10f, 10f);vertices[5].Position = new Vector3(10f, 10f, -10f);verticesDevil.Position = new Vector3(-10f, 10f, -10f);vertices[7].Position = new Vector3(-10f, 10f, 10f);

En image cela nous donne :


Figure (b)

Maintenant nous devons relier ces points. Non pas comme le père noel de la figure (a), en reliant tous les points par ordre croissant, mais en définissant des triplets correspondant à des triangles. Le premier triplet sera (0,1,2), c'est à dire le premier vertice, le second, et le troisième (nous les spécifions dans l'ordre des aiguilles d'une montre pour le culling). Le premier triangle est relié !

Passons au second triangle le triplet sera : (0,2,3). Nous donnons ici deux indices (0 et 2) qui ont déjà été utilisé avant. Un bel exemple du gain en terme de nombre de vertices.

et ainsi de suite. Au final l'ensemble de triplet sera :

(0,1,2)//face devant
(0,2,3)
(3,2,4)//face droite
(3,4,5)
(5,4,7)//face arrière
(5,7,6)
(6,7,1)//face gauche
(6,1,0)
(6,0,3)//face bas
(6,3,5)
(1,7,4)//face haut
(1,4,2)

Amusez vous à relier les triangles à l'aide du schéma de la figure (b) et cet ensemble de triplet. Vous devirez avoir au final :


Figure (c)

Avec seulement 8 vertices nous créons une forme a 12 primitives (12 triangles) !. Maintenant que la théorie est aquise, passons à la pratique.

Notre code

Un nettoyage est nécessaire pour passer du projet "QuatriemeProjet" à "CinquiemeProjet". Le code de la méthode Initialize doit être modularisé pour plus de compréhension et une meilleur maintenance. Celle-ci se présentera désormais ainsi :

protected override void Initialize(){    // TODO: Add your initialization logic here    this.InitializeRenderTarget();    this.InitializeVertices();    this.InitializeIndices();    this.InitializeEffect();    this.SetupDevice();     base.Initialize();

}

La méthode InitializeRenderTarget s'occupera du paramétrage de la surface de rendu. InitializeVertices s'occupera de gérer les vertices et InitializeIndices les indices. InitializeEffect créera et paramètrera les effets. Enfin, SetupDevice contiendra le code pour définir les propriétés d'affichage du device. Pour l'heure seules les méthodes liées aux vertices et aux indices nous interessent.

Commencez par définir deux nouvelles variables :

private VertexBuffer vertexBuffer;

private IndexBuffer indexBuffer;

Il s'agit de deux buffer liés pour le premier aux vertices et aux indices pour le second. Ces buffers sont plus efficaces qu'un simple tableau pour gérer des ensembles de données. La carte vidéo sait très bien les appréhender, et le développeur y trouve son compte par l'intermédiaire de méthodes clés en main qui lui permettent un contrôle sécurisé sur les données. La méthode InitializeVertices  contiendra le code suivant :

private void InitializeVertices(){    vertices = new VertexPositionColorMusic;     vertices[0].Position = new Vector3(-10f, -10f, -10f);    vertices[0].Color = Color.Yellow;    vertices[1].Position = new Vector3(-10f, -10f, 10f);    vertices[1].Color = Color.Green;    vertices[2].Position = new Vector3(10f, -10f, 10f);    vertices[2].Color = Color.Blue;    vertices[3].Position = new Vector3(10f, -10f, -10f);    vertices[3].Color = Color.Black;    vertices[4].Position = new Vector3(10f, 10f, 10f);    vertices[4].Color = Color.Red;    vertices[5].Position = new Vector3(10f, 10f, -10f);    vertices[5].Color = Color.Violet;    verticesDevil.Position = new Vector3(-10f, 10f, -10f);    verticesDevil.Color = Color.Orange;    vertices[7].Position = new Vector3(-10f, 10f, 10f);    vertices[7].Color = Color.Gray;     this.vertexBuffer = new VertexBuffer(    this.graphics.GraphicsDevice,     typeof(VertexPositionColor),    8,    ResourceUsage.WriteOnly,     ResourceManagementMode.Automatic);     this.vertexBuffer.SetData(vertices);

}

Nous créons ici un simple tableau à 8 cases (pour nos 8 vertices). Les deux dernières instructions créé et "remplissent" le vertexBuffer. Le constructeur du vertexbuffer possède 4 surchages. Nous utilisons ici sans doute la plus simple. Elle prend en paramètre, le device (lien vers la carte graphique), le type des vertices, leur nombre, le comportement que l'on a sur la gestion de ces données et enfin par qui ces données sont gérées (ici nous laissons la gestion au framework). L'instruction suivant rempli le buffer avec les vertices créées précédemment. On notera au passage que les couleurs pour chaque vertices correspondent aux couleurs de la figure (c). Passons à la méthode InitializeIndices.

private void InitializeIndices(){    short[] indices = new short[36]{    0,1,2,    0,2,3,    3,2,4,//face droite    3,4,5,    5,4,7,//face arrière    5,7,6,    6,7,1,//face gauche    6,1,0,    6,0,3,//face bas    6,3,5,    1,7,4,//face haut    1,4,2};     this.indexBuffer = new IndexBuffer(this.graphics.GraphicsDevice, typeof(short), 36, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);    this.indexBuffer.SetData(indices);

}

La première instruction créé et initialize un tableau de short. On y trouve 12 fois 3 entiers qui correspondent aux triplets formant des triangles. Vient ensuite la création de l'objet IndexBuffer. Le constructeur possède 4 surcharges là encore assez similaire. Les paramètres sont sensiblement les mêmes que pour le VertexBuffer nous ne reviendrons donc pas dessus. La dernière instruction rempli le buffer à l'aide des indices préalablement créés. Ne reste donc plus qu'à modifier l'affichage du cube. Nous devons spécifier au device le vertexbuffer que nous utilisons, les indices et enfin utiliser une autre méthode de rendu conçue pour l'affichage de formes 3D à l'aide d'indices. La méthode Draw se présente maintenant ainsi :

protected override void Draw(GameTime gameTime){    graphics.GraphicsDevice.Clear(Color.CornflowerBlue);     // TODO: Add your drawing code here    this.effect.Begin();     foreach (EffectPass pass in effect.CurrentTechnique.Passes)    {        pass.Begin();         this.graphics.GraphicsDevice.Vertices[0].SetSource(this.vertexBuffer, 0, VertexPositionColor.SizeInBytes);        this.graphics.GraphicsDevice.Indices = this.indexBuffer;        this.graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(this.graphics.GraphicsDevice, VertexPositionColor.VertexElements);         this.graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);         pass.End();    }     effect.End();     base.Draw(gameTime);

}

Les vertices ne sont plus spécifié dans la méthode Draw elle-même. L'objet device possède un objet de type VertexStreamCollection qui permet de spécifier plusieurs VertexBuffer. C'est une technique très utile pour passer plusieurs vertexbuffer en parallèle au device ayant chacun une ou plusieurs proprietés liées aux vertices à afficher (le premier vertexbuffer pourrait porter sur la couleur, le second sur la position, le troisième sur la position de la texture ...). Cette technique permet de facilier modifier en masse les propriétés des vertices sur une seule composante (couleur, position, texture) sans affecter l'ensemble du modèle. Elle est particulièrement appreciée dans le morphing. Pour l'heure nous sommes encore loin de ce genre de problématiques et ne donnons qu'un seul vertexbuffer (en position 0 donc) à l'aide de la méthode SetSource de l'objet VertexStreamCollection. Celle-ci reçoit le buffer, l'index à partir duquel on parcours le buffer et enfin la taille en octet du type VertexPositionColor :

this.graphics.GraphicsDevice.Vertices[0].SetSource(this.vertexBuffer, 0, VertexPositionColor.SizeInBytes);

La seconde instruction spécifie les indices à utiliser sous la forme d'un IndexBuffer. L'instruction suivante est connue. On "explique" au device la structure du type des vertices utilisés, par l'intermédiaire de la propriété VertexElements. Cette propriété indique que VertexPositionColor contient une information de position et de couleur. Vient l'affichage

this.graphics.GraphicsDevice.DrawIndexedPrimitive(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);

Nous sommes toujours en type de reliaison TriangleList mais en mode indexé (avec indices). Le second paramètre spécifie l'index à ajouter à chaque indices pour atteindre le bon vertex. Le troisième paramètre indique à partir de quel vertice on commence à parcourir le buffer de vertices. Le quatrième paramètre correspond au nombre de vertices à afficher. L'avant dernier indique à partir de quel indice on commence à parcourir le buffer d'indices. Enfin le dernier paramètre donne le nombre de primitives (triangles) à afficher.

A l'exécution on obtient le même cube, mais avec 28 vertices en moins à afficher et une carte vidéo heureuse :

Conclusion

Cette fois-ci notre maitrise des vertices est parfaite. Nous sommes en mesure d'afficher des formes avec le minimum de vertices possible. Il convient de rester conscient toutefois qu'il est impossible, même avec les indices de créer des formes complexes comme un visage humain, une voiture de course ou tout autre forme complexe. Nous devons passer par des outils de modélisations comme 3DSMax ou Maya qui sont spécifiquement conçu dans cette optique. Nous verrons d'ailleur dans un prochain tutorial comment charger de tel modèles générés par ces outils (sous la forme de fichiers) et les afficher.

Pour l'heure le prochain tutorial fera une synthèse de tout ce que nous avons vu depuis le début en nous faisans réaliser notre premier monde de jeu : une ville avec des grattes-ciel.

telecharger Vous pouvez télécharger le sample ici.   

A bientôt sur ce Blog !

Valentin

Retourner au sommaire des cours 

Published Thursday, January 18, 2007 7:20 AM by valentin

Comments

# re: XNA Tutorial 6 : Les indices

Thursday, February 01, 2007 3:47 AM by jago138

Bonjour,

je vous félicite pour ces tutoriels qui mon beaucoup aider dans mon apprentissage...

un grand merci a vous !

en plus ils sont tres bien expliquer. chapeau !

j'attends avec grande impatience la suite :)

++

# re: XNA Tutorial 6 : Les indices

Thursday, February 01, 2007 3:55 PM by william montaz

Tout d'abord. Merci mille fois. Tes tutos sont excellents.

Etant moi meme étudiant en informatique ca me passione bien.

Deux questions : en utilisant les indices, on peut remarquer que les couleurs ne sont pas traitées de la meme facon ke dans le tuto 5. logique vu ke l'on a plus ke 8 vertices et une couleur pour chaque. Existe-t-il des classes pouvant associer plusieurs couleurs a un vertex ? ( c un peu bizarre comme question mais ki sait ?! )

vu que pour des figures complexes on utilise d'autres logiciels ( 3dsmax... ), a quoi peut servir la programmation "manuelle" des vertices ( hormis son caractere instructif ) ? crée des effets de particules ? projections ???

j'attend impatiemment les prochains tutos.

ps: sur le net, je me lache pour l'orthographe donc dsl aux puristes horrifiés!

# re: XNA Tutorial 6 : Les indices

Friday, February 02, 2007 5:47 PM by valentin

Oui cela est possible, on touche à ce moment là aux techniques de blending (mélange). On verra justement cela plus tard. On peut aussi s'amuser avec la méthode SetSource vue plus haut dans cet article.

La programmation manuelle des vertices ici sert principalement à bien comprendre comment s'orienter et se positionner dans l'espace car c'est le lecteur qui créé ses formes. La seconde raison c'est qu'on est souvent confronté au besoin de lire des formes 3D dans un format non maitrisé par DiretX. Il convient donc d'en extraite toutes les données 3D comme les vertices, les indices ... pour reformer les buffers et appeller soit meme les méthodes de dessin. Enfin effectivement on peut être amené à créer ses formes sois-même. C'est d'abord un exercice amusant et c'est ensuite bien utile dans de nombreux cas. Les effets de particules comme vous le dites, mais aussi l'affichage de grilles de graduation dans les éditeurs de monde, il y'a des tas de cas où cela peut être utile.

Merci pour tes remarques :)

# re: XNA Tutorial 6 : Les indices

Saturday, March 17, 2007 6:51 PM by dEM

Bonjour,

tout d'abord un grand merci pour ce tutoriel, bien fait et facile à suivre, même pour les novices !

Une petite question sur Maya et 3DS... Les prix vus sur internet avoisinent les 6000€ pour les dernieres versions. Mais est-il possible de se procurer des versions moins lourdes et beaucoup plus cheap ???

Merci par avance, et bonne continuation.

# re: XNA Tutorial 6 : Les indices

Monday, March 19, 2007 3:22 AM by valentin

Oui pour Maya tu as la Maya PLE (personal learning edition) :

http://www.autodesk.fr/adsk/servlet/index?siteID=458335&id=8803009

gratuite dans un but non commercial. C'est moins cher que les 6000 € :)

Pour 3Ds Max, je me souviens juste d'une trial de 30 jours...

http://usa.autodesk.com/adsk/servlet/index?siteID=123112&id=5972446

Leave a Comment

(required) 
(required) 
(optional)
(required) 
Powered by Community Server (Commercial Edition), by Telligent Systems