Retourner au sommaire des cours
Le précédent programme que nous avons réalisé était rudimentaire : nous n'affichions qu'un triangle... Il faut bien avouer que nous pourrions obtenir le même résultat sous PowerPoint ou en Winform. L'objet de ce tutorial va nous repositionner pleinement dans le monde 3D. Nous continuerons à afficher un triangle mais cette fois-ci nous utiliserons les matrices afin de l'animer.
Nous seront donc amené à étudier un peu de géométrie dans l'espace, les matrices et la caméra.
Repère 3D
Le monde de jeu se trouve dans un espace 3D orthonormé. A l'intérieur de celui-ci tout point est situé par l'intermédiaire de trois composantes : sa position par rapport à l'abscisse X, sa position par rapport à l'ordonnée Y, sa position par rapport à la côte Z. En repère 2D X et Y sont facilement identifiables : l'axe X est horizontal et Y vertical. En 3D "XNA" son se base plutot par rapport à un repère dit de "main droite". L'image ci-dessous montre un repère main droite.

Ce nom vient du fait que vous pouvez reproduire ce repère à l'aide de votre main droite. Le pouce représente l'axe X, l'index l'axe Y et le majeure l'axe Z. A l'intérieur du monde de jeu ou World Space nous placerons désormais nos objets en utilisant des coordonnées 3D liées à cette représentation. Ainsi Z croît lorsqu'il se rapproche de l'observateur. L'axe X est orienté vers la droite, enfin l'axe Y croît avec la hauteur. Ainsi nous pourrions définir les vecteurs cardinaux Up, Down, Foarward, Backward, Left, Right de cette manière :
up = new Vector3(0f, 1f, 0f);
down = new Vector3(0f, -1f, 0f);
right = new Vector3(1f, 0f, 0f);
left = new Vector3(-1f, 0f, 0f);
forward = new Vector3(0f, 0f, -1f);
backward = new Vector3(0f, 0f, 1f);
Notre programme
Comme à l'accoutumé le programme du tutorial précédent va être repris et évolué pour prendre en compte les nouveautés de ce chapitre. Il sera renommé en "TroisièmeProjet".
Le but de cet article est donc de faire tourner le triangle sur lui-même. Là encore c'est les matrices qui vont nous aider. Nous allons faire coincider le taux de rotation du triangle en fonction du temps écoulé depuis la dernière frame affichée. De cette façon le triangle tournera toujours à la même vitesse, quelque soit la puissance de la machine où l'activité de son CPU. Nous avons vu précédemment que c'est la matrice World qui s'occupe de "réaliser" les transformations à effectuer sur un objet. Par transformation nous entendons translation, scale et ... rotation. Ecrire "à la main" une matrice pour qu'elle réalise une transformation n'est pas forcement très simple. Heureusement la classe Matrix possède un ensemble de membre statique (comme nous l'avons déjà vu) qui permettent de créer des instances très facilement.
Remplacez l'instruction d'affectation de la matrice World de l'effet à l'intérieur de la méthode Draw par cette ligne :
this.effect.Parameters["World"].SetValue(Matrix.CreateRotationY( (float)gameTime.TotalGameTime.TotalSeconds));
Nous appellons ici la méthode statique CreateRotationY qui renvoie une matrice configurée pour réaliser une rotation autour de l'axe Y. Le fichier effet auquel nous donnons la matrice va multiplier celle-ci par tous les vertices présent dans le flux envoyé à la carte graphique par l'intermédiaire de la méthode Draw. Les vertices ainsi multipliés vont voir leur position modifiée et vont donner l'impression d'être en rotation. Tout objet affiché entre les méthode Begin et End de l'effet seront ainsi soumis à cette transformation. Nous donnons à la méthode CreateRotationY le temps total écoulé depuis le lancement du jeu. Nous aurions pu donner une valeur constante type PI/4. Mais le triangle n'aurait pas été animé. Il aurait juste été legerement pivoté. Le fait d'utiliser le temps total écouté fait que notre triangle tourne à vitesse constante.
A l'exécution nous l'allons le voir tourner effectivement sur lui-même. Pourtant le programme comporte un problème : régulièrement le triangle disparait. Ce problème qui n'en est pas un est du au "back face culling".
Backface Culling
Le back face culling est un algorithme intelligent sur lequel repose la carte graphique pour réaliser ses rendu. Il permet d'enelver de l'affichage les faces (triangles) qui ne sont pas visible depuis la position de la caméra parceque situées dernière l'objet. Si vous regardez quelqu'un dans les yeux, nous ne voyez pas l'arrière de sa tête. Ici c'est le même principe. Comment la carte graphique sait-elle qu'une face n'est pas visible ? En fait c'est le développeur qui le lui indique au moment ou il créé le tableau de vertices. Si un triangle doit être affiché par l'intermédiaire de trois vertices placé dans l'ordre des aiguilles d'une montre alors il est visible, dans le cas contraire XNA ne l'affiche pas. Reciproquement si un triangle ne s'affiche pas c'est que les vertices qui le forment sont lus dans le sens inverse des aiguilles d'une montre. Auquel cas si vous placez la caméra de l'autre coté du triangle celui-ci sera visible. Nous n'allons évidemment pas déplacer continuellement la caméra. Il est plus simple de désactiver le culling. Ajoutez le code suivant à la méthode Initialize :
this.graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
Le device possède un membre RenderState qui gère les propriétés d'affichage 3D dynamiques et notamment le culling employé. L'énumération CullMode permet d'indiquer si nous ne voulons pas de culling (None), si nous voulons cachée les faces dont le svertices sont lu dans le sens des aiguilles d'une montre (CullClockwiseFace) ou bien dans le sens inverse (CullCounterClockwiseFace).
Amélioration du code
Nous plaçons à l'intérieur de la méthode Draw des instructions qui ne sont pas directement liées à l'affichage : les trois affectations des matrices World, View et Projection. Déplacez ainsi les instructions :
this.effect.Parameters["World"].SetValue(Matrix.CreateRotationY( (float)gameTime.TotalGameTime.TotalSeconds));
A l'intérieur de la méthode Update et les deux instructions :
this.effect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(0, 0, -2), Vector3.Zero, Vector3.Up));
this.effect.Parameters["Projection"].SetValue(Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, this.GraphicsDevice.Viewport.AspectRatio, 0.1f, 2.1f));
dans Load.
Tout simplement parceque la matrice World doit être mise à jour à chaque frame alors que les propriétés et caractéristiques de la caméra ne changent pas.
Pour terminer, et pour améliorer notre culture, modifiez le farPlaneDistance (dernier paramètre de la méthode CreatePerspectiveFieldOfView) qui définit la distance au delà de laquelle un objet n'est plus visible. Il est pour l'heure de 100f. Changez la valeur a 2.1f. Nous voyons à l'exécution notre triangle tourner complètement mais du fait de sa rotation, les vertices de sa base s'éloignent, et disparaissent au gré de la rotation (voir image ci-dessous).
Ceci est du à notre far plane. Le triangle lorsqu'il est tourné de 90 dégré dépasse en profondeur la distance maximale de vision que nous avons indiqué dans la matrice de Projection. Remettez la valeur 100 pour que le programme marche parfaitement.
Autre élément important : modifiez la taille de la fenêtre affichant le triangle. Après cette action vous devriez à nouveau être soumis au back-face culling. Ceci est simplement du au fait que le device a été reinitialité lors de cette action. Il reprend donc ses valeurs par défaut. Il est donc nécessaire de lui respecifier le culling à appliquer. A ce niveau de nos connaissances nous pourrions parer ce problème qu'en mettant l'instruction concernée dans la méthode Update qui est appellée à chaque frame. Ce ne serait pas très propre ni optimisé... Heureusement le device offre un ensemble d'evenements qui permettent de parer àc ette eventualité. Enregistrez vous sur l'événement reset dans le constructeur de cette manière :
graphics.DeviceReset += new EventHandler(graphics_DeviceReset);
On mettre alors à l'intérieur de la méthode graphics_DeviceReset tout ce qui est nécessaire à la configuration du device :
void graphics_DeviceReset(object sender, EventArgs e)
{
this.graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;
}
Conclusion
Nos sommes maintenant prets à aborder les choses sérieuses. A partir de là nos connaissances sont suffisantes pour répondre à un grand nombre de problématiques : nous savons afficher des formes, les déplacer et placer une caméra. Essayez comme exercice de positionner la caméra à un autre endroit de la scène. De même, essayez d'autres transformations (faites un redimensionnement, une translation, les deux ...).
Le prochain article sera consacré aux indices, un type de reliaison de vertices très performants.
Vous pouvez télécharger le sample ici.
A bientôt sur ce Blog !
Valentin Billotte
Retourner au sommaire des cours