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.

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 (projet "TroisièmeProjetAvecRotation"). La première étape consiste à modifier notre triangle afin de le rendre rectangle (triangle à angle droit). Les trois vertices définis ainsi :
vertices[0].Position = new Vector3(-10f, 5f, 0f);
vertices[0].Color = Color.Green;
vertices[1].Position = new Vector3(7, 8f, 10f);
vertices[1].Color = Color.Red;
vertices[2].Position = new Vector3(2f, -7f, 3f);
vertices[2].Color = Color.Yellow;
deviennent :
vertices[0].Position = new Vector3(-10f, -10f, -10f);vertices[0].Color = Color.Green;vertices[1].Position = new Vector3(-10, -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 :

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)... Annulez les transformations que nous appliquions au triangle afin de le faire tourner en supprimant les instructions :
Matrix world = effect.Parameters["xWorld"].GetValueMatrix() * Matrix.CreateRotationY(MathHelper.Pi/1000f*gameTime.ElapsedGameTime.Milliseconds);effect.Parameters["xWorld"].SetValue(world);
de la méthode Update(). De même repositionnez la camera ainsi :
Matrix
viewMatrix = Matrix.CreateLookAt(new Vector3(0f, -50f, 10f), new Vector3(0f, 0f, 0f), new Vector3(0, 1f, 0));
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 10 unités.Si vous lancez le programme à ce stade vous obtenez :

Le premier triangle du cube est affiché. En fait aucun mérite à avoir, ca 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 devantvertices[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.

Il faut modifier l'instruction qui dessine à l'écran les triangles dans la méthode Draw :
this.graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 1);
en
this.graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 2);
Le deux 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. A l'exécution le programme affiche :

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 3, face droitevertices[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 droitevertices[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 tout bête bien de la caméra. Nous sommes pile en face des deux triangles que nous avons créé au début. Les triangles 3 et 4 ne sont donc pas visibles. Modifiez la position de la caméra de manière a la place légèrement plus haut :
Matrix
viewMatrix = Matrix.CreateLookAt(new Vector3(0f, -50f, 20f), new Vector3(0f, 0f, 0f), new Vector3(0, 1f, 0));
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 :
vertices = new VertexPositionColor[36]; //triangle 1, face devantvertices[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 devantvertices[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 droitevertices
.Position = new Vector3(10f, -10f, -10f);vertices
.Color = Color.Green;vertices[7].Position = new Vector3(10f, -10f, 10f);vertices[7].Color = Color.Red;vertices
.Position = new Vector3(10f, 10f, 10f);vertices
.Color = Color.Yellow;//triangle 4, face droitevertices[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 arrèrevertices[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 arrièrevertices[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 gauchevertices[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 gauchevertices[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 basvertices[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 basvertices[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 hautvertices[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 hautvertices[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 de dessin de 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 faut créer deux variables de type
Vector3 afin de sauvegarder la taille courante (changement de taille en X, Y ou Z) et le position (déplacement sur X, Y ou Z).
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 += new Vector3(0f, 1f, 0f);if (Keyboard.GetState()[Keys.Down] == KeyState.Down) position += new Vector3(0f, -1f, 0f);if (Keyboard.GetState()[Keys.Left] == KeyState.Down) position -= new Vector3(1f, 0f, 0f);if (Keyboard.GetState()[Keys.Right] == KeyState.Down) position += new Vector3(1f, 0f, 0f);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. A noter que possède des méthodes qui nous auraient simplifié la vie en donnant le même résultat :
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);
Le code est d'ailleur plus lisible... En ce qui concerne la rotation du code, la transformation est réalisée là aussi à l'intérieur de la méthode Update :
long iTime = (long)(gameTime.TotalGameTime.TotalMilliseconds % 2000f);float fAngle = iTime * (2.0f * MathHelper.Pi) / 2000.0f; //la transformatio en elle mêmeMatrix world = Matrix.CreateRotationY(fAngle) * Matrix.CreateRotationX(fAngle) * Matrix.CreateScale(size) * Matrix.CreateTranslation(position);
effect.Parameters["xWorld"].SetValue(world);
Je prend le résultat de la division du nombre total de millisecondes écoulée depuis le lancement de l'application par 2000 (pour deux secondes). Je divise 360° (2PI) par 2000 pour obtenir 2000 petits angles, qui aditionnés forment une rotation complète. Je multiplie par le résultat de la division précédente et j'ai un angle en fonction du temps écoulé de manière à avoir une rotation complète de 360% pour mon cube toutes les 2secondes et ceci, quelque soit la vitesse de la machine qui fait fonctionner mon application.
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.
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 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...
Vous pouvez télécharger le sample ici.
A bientôt sur ce Blog !
Valentin Billotte
Retourner au sommaire des cours