XNA Tutorial 5 : Les matrices et les Transformations

Retourner au sommaire des cours  

Ce tutorial est une suite du tutorial précédent. Nous allons continuer à utiliser les matrices pour mieux les comprendre au travers des transformations. Nous introduirons aussi le tutorial suivant qui abordera les indices. La notions de vertices (et leur utilisation) sera elle aussi abordée puisque nous allons passer de l'affichage d'un simple triangle à un joli cube coloré.

Notre programme

La première difficulté à appréhender nous vient de l'affichage du cube. Pour le débutant, l'affichage d'un triangle était déjà un exercice relativement compliqué. Avec l'affichage d'un cube l'exercice se corse puisqu'il faut pas moins de 12 triangles pour former un cube ! (un cube = 6 faces, une face = 2 triangles). Nous commencerons donc à réaliser l'affichage d'une face, puis de deux, et enfin le cube en entier. Nous appliquerons une transformation propre au  cube pour terminer.

Regardez l'image ci-dessous, elle explicite les douze triangles qui forment le cube. 

Un Cube en 3D est l'assemblage de 12 triangles

 Les traits noirs représentent les faces, les traits gris les triangles. C'est ce résultat que nous devons reproduire (en plus coloré). Reprennons le code du précédent tutorial. La première étape consiste à modifier notre triangle afin de le rendre rectangle (triangle à angle droit). Les trois vertices définis ainsi :

vertices = new VertexPositionColor[3];
vertices[0].Position = new Vector3(0.5f, -0.5f, 0f);
vertices[0].Color = Color.Yellow;
vertices[1].Position = new Vector3(0, 0.5f, 0f);
vertices[1].Color = Color.Green;
vertices[2].Position = new Vector3(-0.5f, -0.5f, 0f);
vertices[2].Color = Color.Red;

deviennent :

vertices = new VertexPositionColor[3];
vertices[0].Position = new Vector3(-10f, -10f, 10f);
vertices[0].Color = Color.Green;
vertices[1].Position = new Vector3(-10f, 10f, 10f);
vertices[1].Color = Color.Red;
vertices[2].Position = new Vector3(10f, 10f, 10f);
vertices[2].Color = Color.Yellow;

On remarque que les trois points du triangle on la même profondeur (10f) ceci, afin de rendre la définition des position des vertices plus simple à imager. L'image ci-dessous résume le travail que nous venons de faire (les numéros en rouge correspondant à l'index des vertices dans le tableau vertices) :

On notera que nous avons défini les vertices dans l'ordre des aiguilles d'une montre (voir cours précédent sur le backface culling)... En outre nous sommes en train de construire un cube. Il n'est donc plus nécessaire d'annuler le backface culling les faces des triangles non visibles étant situées à l'intérieur du cube. Supprimer pour cela l'instruction :

this.graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;

 De la méthode répondant à l'événement de reinisitalisation du device graphique (graphics_DeviceReset).

 Annulez les transformations que nous appliquions au triangle afin de le faire tourner en remplaçant dans la méthode Update l'instruction : 

this.effect.Parameters["World"].SetValue(Matrix.CreateRotationY((float)gameTime.TotalGameTime.TotalSeconds));

par :

this.effect.Parameters["World"].SetValue(Matrix.Identity); 

En outre, les dimensions de notre triangle sont bien plus importantes que dans le tutorial précédent. Il faut donc replacer la caméra en conséquence. Modifiez les instructions d'affectation aux matrices View et Projection de la méthode Load ainsi :

this.effect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(0, 10, -50), Vector3.Zero, Vector3.Up));
this.effect.Parameters["Projection"].SetValue(Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, this.GraphicsDevice.Viewport.AspectRatio, 0.1f, 100f));

Nous nous placons pile en face du triangle que nous venons de créer à 50 unité de distance et avec une légère vue de haut de 20 unités.Si vous lancez le programme à ce stade vous obtenez :

Le premier triangle du cube est affiché. En fait aucun mérite à avoir, cela fait déjà quatre tutoriaux que nous savons faire cela. Ajoutons une difficulté en créant un nouveau triangle collé au premier de manière a former une face carrée ! Modifiez la création du tableau de vertices dans Initialize de cette manière :

vertices = new VertexPositionColor[ 6 ];

//triangle 1, face devant
vertices[0].Position = new Vector3(-10f, -10f, 10f);
vertices[0].Color = Color.Green;
vertices[1].Position = new Vector3(-10f, 10f, 10f);
vertices[1].Color = Color.Red;
vertices[2].Position = new Vector3(10f, 10f, 10f);
vertices[2].Color = Color.Yellow;

//triangle 2, face devant
vertices[3].Position = new Vector3(-10f, -10f, 10f);
vertices[3].Color = Color.Green;
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.Yellow;

Nous avons maintenant 6 vertices. Deux triangles isocèles à angle droit et donc une face. Schematiquement nous avons réalisé ceci :

Nous n'affichons donc plus une seule primitive (triangle) mais deux. Il faut donc aussi modifier l'instruction de dessin dans la méthode Draw :

this.graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 2);

Le "2" à la fin de l'appel insique que nous affichons maintenant deux primitives (deux triangles). Etant donné que nous sommes en TriangleList il faut donc 6 vertices, ce que nous donnons à l'aide du tableau vertices.  Pas de surprise, à l'execution nous obtenons :

 

Accelerons le rythme, la prochaine étape sera plus importante : une nouvelle face va être ajoutée, soit deux triangles en plus et donc 12 vertices à définir. Modifiez de nouveau la création du tableau de vertices pour passer à 12 vertices :

vertices = new VertexPositionColor[12];

//triangle 1, face devant
vertices[0].Position = new Vector3(-10f, -10f, 10f);
vertices[0].Color = Color.Green;
vertices[1].Position = new Vector3(-10f, 10f, 10f);
vertices[1].Color = Color.Red;
vertices[2].Position = new Vector3(10f, 10f, 10f);
vertices[2].Color = Color.Yellow;

//triangle 2, face devant
vertices[3].Position = new Vector3(-10f, -10f, 10f);
vertices[3].Color = Color.Green;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.Yellow;
 
//triangle 3, face droite
vertices[ 6 ].Position = new Vector3(10f, -10f, 10f);
vertices[ 6 ].Color = Color.Green;
vertices[7].Position = new Vector3(10f, 10f, 10f);
vertices[7].Color = Color.Red;
vertices[ 8 ].Position = new Vector3(10f, 10f, -10f);
vertices[ 8 ].Color = Color.Yellow;

//triangle 4, face droite
vertices[9].Position = new Vector3(10f, -10f, 10f);
vertices[9].Color = Color.Green;
vertices[10].Position = new Vector3(10f, 10f, -10f);
vertices[10].Color = Color.Red;
vertices[11].Position = new Vector3(10f, -10f, -10f);
vertices[11].Color = Color.Yellow;

Les deux triangles ajoutés correspondent dans l'image à :

Modifiez l'instruction de dessin ainsi :

this.graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 4);

Nous affichons maintenant 4 triangles. Pourtant à l'affichage nous obtenons le même résultat que précédemment, que s'est il passé ? Ce problème et lié à la position de la caméra. Nous sommes pile en face des deux triangles que nous avons créé au début. Les deux nouveaux triangles ne sont donc pas visibles. Modifiez la position de la caméra (dans la méthode LoadContent) de manière a la placer légèrement plus haut et à droite :

this.effect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(20, 30, 50), Vector3.Zero, Vector3.Up));

Cette fois ci à l'exécution nous obtenons :

On distingue bien les deux faces. A ce stade l'ajout de triangles pour créer un cube doit être assimilé, nous allons directement ajouter les 4 autres faces restantes (soit 8 triangles). Le tableau de vertices est désormais créé de cette manière :

//triangle 1, face devant
vertices[0].Position = new Vector3(-10f, -10f, 10f);
vertices[0].Color = Color.Green;
vertices[1].Position = new Vector3(-10f, 10f, 10f);
vertices[1].Color = Color.Red;
vertices[2].Position = new Vector3(10f, 10f, 10f);
vertices[2].Color = Color.Yellow;
//triangle 2, face devant
vertices[3].Position = new Vector3(-10f, -10f, 10f);
vertices[3].Color = Color.Green;
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.Yellow;
 
//triangle 3, face droite
vertices[ 6 ].Position = new Vector3(10f, -10f, 10f);
vertices[ 6 ].Color = Color.Green;
vertices[7].Position = new Vector3(10f, 10f, 10f);
vertices[7].Color = Color.Red;
vertices[ 8 ].Position = new Vector3(10f, 10f, -10f);
vertices[ 8 ].Color = Color.Yellow;
//triangle 4, face droite
vertices[9].Position = new Vector3(10f, -10f, 10f);
vertices[9].Color = Color.Green;
vertices[10].Position = new Vector3(10f, 10f, -10f);
vertices[10].Color = Color.Red;
vertices[11].Position = new Vector3(10f, -10f, -10f);
vertices[11].Color = Color.Yellow;
 
//triangle 5, face arrriere
vertices[12].Position = new Vector3(10f, -10f, -10f);
vertices[12].Color = Color.Green;
vertices[13].Position = new Vector3(10f, 10f, -10f);
vertices[13].Color = Color.Red;
vertices[14].Position = new Vector3(-10f, 10f, -10f);
vertices[14].Color = Color.Yellow;
//triangle 6, face arrirere
vertices[15].Position = new Vector3(10f, -10f, -10f);
vertices[15].Color = Color.Green;
vertices[16].Position = new Vector3(-10f, 10f, -10f);
vertices[16].Color = Color.Red;
vertices[17].Position = new Vector3(-10f, -10f, -10f);
vertices[17].Color = Color.Yellow;

//triangle 7, face gauche
vertices[18].Position = new Vector3(-10f, -10f, -10f);
vertices[18].Color = Color.Green;
vertices[19].Position = new Vector3(-10f, 10f, -10f);
vertices[19].Color = Color.Red;
vertices[20].Position = new Vector3(-10f, 10f, 10f);
vertices[20].Color = Color.Yellow;
//triangle 8, face gauche
vertices[21].Position = new Vector3(-10f, -10f, -10f);
vertices[21].Color = Color.Green;
vertices[22].Position = new Vector3(-10f, 10f, 10f);
vertices[22].Color = Color.Red;
vertices[23].Position = new Vector3(-10f, -10f, 10f);
vertices[23].Color = Color.Yellow;

//triangle 9, face bas
vertices[24].Position = new Vector3(-10f, 10f, 10f);
vertices[24].Color = Color.Green;
vertices[25].Position = new Vector3(-10f, 10f, -10f);
vertices[25].Color = Color.Red;
vertices[26].Position = new Vector3(10f, 10f, -10f);
vertices[26].Color = Color.Yellow;
//triangle 10, face bas
vertices[27].Position = new Vector3(-10f, 10f, 10f);
vertices[27].Color = Color.Green;
vertices[28].Position = new Vector3(10f, 10f, -10f);
vertices[28].Color = Color.Red;
vertices[29].Position = new Vector3(10f, 10f, 10f);
vertices[29].Color = Color.Yellow;

//triangle 11, face haut
vertices[30].Position = new Vector3(-10f, -10f, -10f);
vertices[30].Color = Color.Green;
vertices[31].Position = new Vector3(-10f, -10f, 10f);
vertices[31].Color = Color.Red;
vertices[32].Position = new Vector3(10f, -10f, 10f);
vertices[32].Color = Color.Yellow;
//triangle 12, face haut
vertices[33].Position = new Vector3(-10f, -10f, -10f);
vertices[33].Color = Color.Green;
vertices[34].Position = new Vector3(10f, -10f, 10f);
vertices[34].Color = Color.Red;
vertices[35].Position = new Vector3(10f, -10f, -10f);
vertices[35].Color = Color.Yellow;

L'instruction DrawUserPrimitive se présente ainsi :

this.graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 12);

A l'exécution nous obtenons un splendide cube :

Pour profiter des formes ce cube nous allons le soumettre à toutes les transformations existantes. Il va être déplacé, tourné et redimentionné. Là où nous allons faire fort, c'est que nous le déplacerons et le redimentionnerons à l'aide du clavier ! 

Gestion des entrées clavier 

Nous ferons un cours complet sur la gestion des entrées clavier, gamepad ou autre plus tard. pour l'heure il s'agit juste de s'amuser un peu pour se récompenser de l'effort fourni jusqu'ici. Il nous tout d'abord faut créer deux variables de type Vector3 afin de sauvegarder la taille courante (changement de taille en X, Y ou Z) et la position (déplacement sur X, Y ou Z) du cube.

Vector3 position = Vector3.Zero;
Vector3 size = Vector3.One;

La méthode Update a été réécrite afin d'accueillir de nouvelles instructions.

if (Keyboard.GetState()[Keys.Up] == KeyState.Down)
      position += Vector3.Up;
if (Keyboard.GetState()[Keys.Down] == KeyState.Down)
      position += Vector3.Down;
if (Keyboard.GetState()[Keys.Left] == KeyState.Down)
      position += Vector3.Left;
if (Keyboard.GetState()[Keys.Right] == KeyState.Down)
      position += Vector3.Right;
if (Keyboard.GetState()[Keys.PageUp] == KeyState.Down)
      size += new Vector3(0.1f, 0.1f, 0.1f);
if (Keyboard.GetState()[Keys.PageDown] == KeyState.Down)
      size -= new Vector3(0.1f, 0.1f, 0.1f);

La propriété Keyboard.GetState() renvoie l'état du clavier courant. Il s'agit d'une collection de touches clavier définies dans le type Keys. Ici nous verifions l'état des touches Haut (Up), Bas (Down), Gauche, Droite, Page Haut, Page Bas. Deux états peuvent exiter, soit la touche est appuyée (KeyState.Down), soit elle est relachée (KeyState.Up). Ne reste plus en fonction de l'état de chacun de ces touches à mettre à jour nos deux variables. Si la touche est haut, nous faisons évoluer Y (la distance), si c'est gauche ou droite c'est X (deplacement horizontal). Nous n'avons pas défini de touches pour les déplacement verticaux (Z). Mais essayez comme exercice de créer ce déplacement à l'aide des touches + et - (Keys.Add et Keys.Subtract). Enfin pour page haut et page bas nous faisons évoluer la taille du cube de 0.1 unité. Essayez là encore comme exercice de ne faire évoluer qu'une seule des composantes X, Y ou Z afin d'étudier le résultat. Vector3.Up/.Down.Left/.Right correspondent à des contants qui nous évitent d'écrire des "valeurs" dans notre code. Vector3.Up correspond ainsi à la valeur "new Vector3(0, 1, 0)". Le code devient ainsi plus lisible. Les autres constantes suivent le même principe.

En ce qui concerne la rotation du code, la transformation est réalisée là aussi à l'intérieur de la méthode Update :

float fAngle = (float)gameTime.TotalGameTime.TotalSeconds;
//la transformation en elle mme
Matrix world = Matrix.CreateRotationY(fAngle) * Matrix.CreateRotationX(fAngle)
* Matrix.CreateScale(size)
* Matrix.CreateTranslation(position);

this.effect.Parameters["World"].SetValue(world);

Nous nous basons ici sur le temps écoulé depuis le lancement de l'application. La valeur est utilisé pour réaliser des rotation sur Y et X (les rotations étant cycliques prendre le temps écoulé est une façon de faire parfaite !). Celles-ci seront constantes quelque soit la vitesse de la machine qui fait fonctionner l'application. Les transformations (rotation Y, rotation X, Scale, Translation) sont multipliées ensemble pour obtenir une transformation globale qui les intègres toutes. Le resultat est donnée à la matrice World du fichier effet. Ce fichier effet étant utilisé pour afficher le cube, la transformation s'appliquera donc au cube.

Au final nous obtenons :

Vous pouvez de même tester la distance de vision de la caméra en eloignant le cube, au bout d'un moment il disparait dépassant la distance du far plane.

Conclusion

Nous avons vraiment progressé dans notre maitrise des Vertices et des transformations. Nous avons vu que pour faire un simple cube il faut pas moins de 36 vertices ! Cela peut faire peur si on imagine le nombre qu'il faut pour créer des modèles détaillés comme les arbres, le corps humain ou autres ... C'est l'objet de notre prochain tutorial : nous verrons une méthode pour réduire au maximum le nombre de vertices d'un modèle et optimiser leur utilisation.

Pour ce qui est des transformations, nous aurons encore l'occasion de les découvrir lorsque nous aboderons les lumières. Nous allons alors créer une reproduction de l'univers et des planètes du système solaire. Une bonne occasion et un excellent exercice pour éprouver ses connaissances en géométrie 3D et dans les calculs matriciels...

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

A bientôt sur ce Blog !

Valentin Billotte

Retourner au sommaire des cours 

Published Wed, Jan 17 2007 3:34 by valentin

Comments

# re: XNA Tutorial 5 : Les matrices et les Transformations

Wednesday, January 17, 2007 10:45 AM by QuakeMaker

Impressionnant la qualité des tutoriaux

vivement que Progworld revienne ca me manquait.

Sous DirectX c'était un repère main gauche non ?

# re: XNA Tutorial 5 : Les matrices et les Transformations

Thursday, January 18, 2007 2:19 AM by valentin

Merci,

oui c'était un repère main gauche. MS a préféré le repère main droite sous XNA parceque la main gauche provoquait beaucoup de confusion.

Va voir ici : http://msdn.microsoft.com/directx/xna/migration/

" The biggest breaking change has been the removal of left-handed-coordinate-system math functions in favor of unifying on a right-handed coordinate system. This simplifies the Math API and omits functionality that causes a large amount of confusion."

# re: XNA Tutorial 5 : Les matrices et les Transformations

Monday, March 05, 2007 2:37 PM by SangJun

Je ne sais pas si je suis le seul à avoir ce souci.

Mais à plusieurs reprises (pour ce tutoriel c'est ici : //triangle 3, face droite

verticesDevil.Position = new Vector3(10f, -10f, -10f);

verticesDevil.Color = Color.Green;

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

vertices[7].Color = Color.Red;

verticesMusic.Position = new Vector3(10f, 10f, 10f);

verticesMusic.Color = Color.Yellow;

mais ça apparaît à nouveau dans les tutos suivants)

les [chiffres] sont remplacés par des dessins

# re: XNA Tutorial 5 : Les matrices et les Transformations

Friday, March 09, 2007 2:55 AM by valentin

Oui c'est normal, c'est l'interpréteur du texte du blog qui remplace les Devil et Music par des icones :)

# re: XNA Tutorial 5 : Les matrices et les Transformations

Saturday, June 02, 2007 5:54 AM by Romain

Je tiens juste a dire qu'avec t'as mise en page on peut rester longtemp bloqué. Moi en tout cas je n'arrivais pas à me retrouver si tu veux je copie se que j'ai écris au propre et je le poste comme commentaire.

Si tu veux répond moi.

# re: XNA Tutorial 5 : Les matrices et les Transformations

Saturday, June 02, 2007 6:01 AM by valentin

rien compris :)

# re: XNA Tutorial 5 : Les matrices et les Transformations

Saturday, June 02, 2007 6:27 AM by Romain

Lol. Je voulais dire que quand tu as mis toute les vetices t'as mise en page n'étais pas très jolie on si perdait. Alors je t'es proposé que je copie se que j'ai écris avec un bonne mise en page et que je te le mette comme commentaire.

Voilas

PS: Desfois le texte ne s'écrit plus mais continue ensuite ex:

C'est écris ça:

Matrix.CreateRotationY(MathHelper.Pi/1000f*gameTime.ElapsedGameTime.Milliseconds);effect.Para

Mais c'est sensé être ça:

Matrix.CreateRotationY(MathHelper.Pi/1000f*gameTime.ElapsedGameTime.Milliseconds);effect.Parameters["xWorld"].SetValue(world);          

# re: XNA Tutorial 5 : Les matrices et les Transformations

Monday, June 04, 2007 2:48 AM by valentin

oui c'est depuis la refonte de ce serveur que ca *** un peu l'affichage

c'est vraiment ennuyant...

# re: XNA Tutorial 5 : Les matrices et les Transformations

Friday, July 27, 2007 3:11 AM by @NOTIZ@

Salut !

Je viens de faire ce tutorial (sauf la partie transformation, rotation, ...) que je vais commencé.

Et c'est super génial j'ai réussi à faire la base sans

copié le code et à faire le toit comme pour une maison :

img401.imageshack.us/my.php

je suis trop content de moi et je suis préssé de

continuer.

Tes tutoriaux sont super continue les !

# 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

Leave a Comment

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