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);
vertices[ 6 ].Position = new Vector3(10f, -10f, 10f);
vertices[7].Position = new Vector3(10f, 10f, 10f);
vertices[ 8 ].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);

Nous avons mis en évidence les vertices qui possède une position identique en les regroupant par couleur. On remarque que seule 8 couleurs sont utilisées et donc que seuls 8 positions sont nécessaire pour créer un cube. Mais la contrainte du TriangleList nous oblige a utiliser un vertex pour chaque triangle qui l'utilise. L'image suivante se passe de commentaires :

Le vertex en surbrillance rouge, qui correspond au vertices 8, 10, 13 et 35 appartient à 3 faces dans ce cube. Il est ainsi relié à un triangle de la face supérieure (rose), à deux triangles de la face frontale (jaune) et un triangle de la face droite (bleue). Il sera donc à ce stade de nos connaissances répliqué 4 fois ! C'est autant de place de perdue dans la mémoire de la carte graphique lorsque nous appellons la méthode DrawUserPrimitive en lui passant un tableau aussi inutilement gros.

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);
vertices[ 6 ].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. Avant de passer à la pratique faisons un peu de theorie.

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) ! La mémoire de la carte graphique vous remerciera par de meilleures performances.

Maintenant que la théorie est aquise, passons à la pratique.

Notre code

Reprennez le code du projet précédent et commencez par définir deux nouvelles variables :

VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;

Ces deux objets représentent des buffers. Ils sont destinés à receuillir respectivement des vertices et des indices. 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 place leur contenu dans sa propre mémoire afin d'y accéder rapidement, 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 ces données. Nous devrons donc remplir vertexBuffer avec les vertices de notre cube et remplir indexBuffer avec les indices vers les vertices dans un ordre approprié permettant de lister les primitives de ce cube. Nous allons créer deux méthodes la première pour initialiser et charger le contenu du buffer de vertices et le second pour intiialiser et charger le buffer d'indices. La première méthode sera nommée InitializeVertices, elle se présente ainsi :

private void InitializeVertices()
{
    vertices = new VertexPositionColor[ 8 ];
    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;
    vertices[ 6 ].Position = new Vector3(-10f, -10f, -10f);
    vertices[ 6 ].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, BufferUsage.WriteOnly);
    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 de VertexBuffer prend en paramètre, le device (lien vers la carte graphique), le type des vertices, leur nombre enfin, en dernier paramètre nous donnons une valeur de type BufferUsage qui présice de quelle manière notre application va manipuler le buffer. BufferUsage prend trois valeurs :

  • None : indique que nous ne précisons pas de quelle manière le buffer doit être manipulé par notre code
  • Points : précise que les éléments présents dans le vertex buffer seront utilisés pour afficher des points et qu'il doit se préparer à des accès fréquent pour des mises à jours de position.
  • WriteOnly : précise que notre application n'accèdera jamais au buffer hormi lors de la phase de création (seule la carte graphique et le fichier effet y accéderont). Dans ce cas, la carte graphique va chercher le meilleur emplacement possible en terme de performance pour stocker le contenu du buffer. A noter que tout accès en lecture depuis notre application échouera.

Passons à la méthode qui sera nommée InitializeIndices :

private void InitializeIndices()
{
    short[] indices = new short[36]{ 
    0,1,2, //face devant
    0,2,3,
    3,2,4, //face droite 
    3,4,5,
    5,4,7, //face arrire 
    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, BufferUsage.WriteOnly);
    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 (triplets explicités en début de tutorial). Vient ensuite la création de l'objet IndexBuffer. Le constructeur est similaire au constructeur de VertexBuffer nous ne reviendrons donc pas dessus. La dernière instruction rempli le buffer à l'aide des indices préalablement créés. Ces deux méthodes étant créées il nous reste à nettoyer la méthode Initialize de notre application pour supprimer toutes les instructions de remplissage de vertices issues du tutorial précédent. Nous remplacerons ces instruction par deux appels vers les méthodes que nous venons d'écrire :

protected override void Initialize()
{
    // TODO: Add your initialization logic here
    this.graphics.IsFullScreen = false;
    this.graphics.PreferredBackBufferWidth = 800;
    this.graphics.PreferredBackBufferHeight = 600;
    this.graphics.ApplyChanges();
    this.Window.Title = "Sixime Tutorial, CinquimeProjet : Les indices !";
    this.Window.AllowUserResizing = true;

    this.InitializeVertices();
    this.InitializeIndices();

    base.Initialize();
}

Ne reste donc plus qu'à modifier l'affichage du cube. Nous devons spécifier au device le vertexbuffer et l'index buffer que nous utilisons et utiliser une autre méthode de rendu (autre que DrawuserPrimitive) conçue pour l'affichage de formes 3D à l'aide d'indices et utilisant des buffers. La méthode Draw se présente maintenant ains i :

protected override void Draw(GameTime gameTime)
{
    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);
    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.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);
        pass.End();
    }

    effect.End();

    base.Draw(gameTime);
}

Voyons maintenant les instructions qui nous sont inconnues.

L'objet device possède un membre Vertices qui permet de spécifier des VertexBuffer utilisés pour l'affichage. C'est une technique très utile pour passer plusieurs vertexbuffer en parallèle au device (le passage de plusieurs vertex buffer permet de simplifer des techniques d'affichage 3D comme le Morphing en soumettant au fichier effet plusieurs sources de données). Pour l'heure nous ne donnons qu'un seul vertexbuffer (en position 0 donc) à l'aide de la méthode SetSource. Celle-ci reçoit en paramètres 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 :

this.graphics.GraphicsDevice.Indices = this.indexBuffer;

 Vient enfin l'instruction d'affichage modifiée :

this.graphics.GraphicsDevice.DrawIndexedPrimitives(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 ainsi 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.

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

A bientôt sur ce Blog !

Valentin

Retourner au sommaire des cours 

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

# re: XNA Tutorial 6 : Les indices

Saturday, December 13, 2008 10:26 AM by ahmed

tu es fort, j'ai bien aime tes tutorials, ne t'arrête pas la s'il vous plait,continue.

et surtout !!! sur le HLSL, je suis nul

# Tutoriaux Xna Game Studio : sommaire général

Friday, June 19, 2009 9:36 AM by Graphic Stream

(Tutoriaux adaptés au Xna Game Studio 3.1) Première partie : Apprentissage XNA Tutorial

# re: XNA Tutorial 6 : Les indices

Sunday, June 21, 2009 11:55 AM by Amine

Merci pour ces merveilleux tutoriaux.

Q: est ce qu'on peut importer des modélisation créer par blender.

On attend avec impatience la suite. Encore mille merci.

# re: XNA Tutorial 6 : Les indices

Sunday, June 21, 2009 12:04 PM by valentin

Oui on peut, Xna supporte tout un tas de formats de base et notamment pour les modèles 3D : X et Fbx.

Ensuite tu peux trouver sur le net des importers déjà créés ou bien réaliser tes propres importers.

(pour ce dernier point regarder le livre blanc que je consacre au content pipeline :) ).

# re: XNA Tutorial 6 : Les indices

Sunday, June 28, 2009 11:40 AM by roberta

comment faire pour afficher 2 cubes avec 2 vertexBuffer différents? merci

# re: XNA Tutorial 6 : Les indices

Sunday, June 28, 2009 12:08 PM by valentin

Créé un second vertex buffer et initialize le de la même manière.

Créé une méthode DrawVertexBuffer qui prend en parametre le vertexbuffer à afficher, un truc du genre :

protected override void DrawVertexBuffer(VertexBuffer buffer)

{

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

   this.graphics.GraphicsDevice.Indices = this.indexBuffer;

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

   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.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);

       pass.End();

   }

   effect.End();

   base.Draw(gameTime);

}

Ensuite dans la méthode Draw tu appelle deux fois cette méthode avec tes deux vertexbuffers :

protected override void Draw(GameTime gameTime)

{

  this.DrawVertexBuffer(this.VertexBuffer);

  this.DrawVertexBuffer(this.VertexBuffer2);

}

# Annexe : Intégration de Xna dans WPF

Monday, July 20, 2009 7:52 AM by Graphic Stream

Retourner au sommaire des cours Il existe de nombreuses méthodes pour afficher des scènes

# re: XNA Tutorial 6 : Les indices

Wednesday, December 16, 2009 6:57 AM by XP

10/10 for this magnificent and extraordinary tutorial that helped us to get a good A++;

# re: XNA Tutorial 6 : Les indices

Sunday, January 23, 2011 1:12 PM by Wei

Voici comment j'ai procédé en 4.0:

http://snipt.org/wlgM

Leave a Comment

(required) 
(required) 
(optional)
(required) 
If you can't read this number refresh your screen
Enter the numbers above:  
Powered by Community Server (Commercial Edition), by Telligent Systems