Retourner au sommaire des cours
Le précédent programme que nous avons réalisé était rudimentaire. Nous n'affichions qu'un triangle en utilisant des coordonnées prétransformées (relatives à l'écran). Il faut bien avouer que nous pourrions obtenir le même résultat sous PowerPoint ... L'objet de ce tutorial va nous repositionner pleinement dans le monde 3D. Nous continuerons à afficher un triangle mais cette fois si dans un réel espace monde en 3D et nous utiliserons les matrices afin de le faire tourner.
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. Y croît avec la distance, Z croit avec la hauteur et X permet de se déplacer sur l'horizontale.
Notre programme
Comme à l'accoutumé j'ai repris le précédent projet nommé "SecondProjet" et je l'ai renommé en "TroisièmeProjet". Jusqu'à présent nous utilisions des coordonnées pré transformée. Cela avait pour conséquence de placer tous les objets par rapport à l'écran de jeu et non par rapport au monde de jeu. Nos premières modifications porterons sur ce point. Le triangle affiché précédemment sera maintenant placé dans un World Space.
Nous n'utiliserons donc plus la technique "Pretransformed" mais la technique "Colored".
this.effect.CurrentTechnique = effect.Techniques["Colored"];
De même la méthode Initialize spécifie de nouvelles coordonnées pour les trois points du triangle.
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;
Si vous lancez l'application à ce stade vous obtiendrez une fenêtre avec un fond bleu ... mais aucune trace de triangle. Pourquoi ? La réponse est toute simple : Nous visualisons une scène 3D sur un écran ... 2D. Il y'a donc forcement une étape de transformation qui vise à passer pour chaque point dans l'espace monde d'une coordonnée 3D à une coordonnée 2D. Jusqu'ici avec le Pretransformed les objets étaint plaqués face à l'écran. Aucun besoin de transformation donc : nous avions déjà un affichage sur une surface plane. Maintenant que nous plaçons nos objets n'importe où dans l'espace nous sommes obligés de nous placer de manière à les voir. Se placer cela implique donner une position d'où regarder une scène mais aussi un point vers lequel regarder (si possible regarder dans la direction de l'objet à afficher). C'est grâce à la position et la direction que nous aurons spécifié que chaque coordonnées 3D dans le monde va être transformé en une coordonnée 2D. C'est les matrices qui vont permettre ces transformations.
Matrice
Une matrice peut être vu comme une boite noire paramétrée qui effectue une transformation. Si vous multipliez un vecteur 3D ou une position dans l'espace par une matrice vous obtenez en retour un vecteur ou une position transformée. Cette boite peut être paramétrée afin d'effectuer plusieurs sorte de transformations : une translation (déplacement), une homothétie (redimentionnement) et/ou une rotation. Une matrice peut aussi représenter un ensemble de calculs mathématiques comme une série de transformations ou bien, ce qui nous interesse ici, les propriétés d'une caméra (position, direction, angle de vision, distance de vision ...).
Deux nouvelles instructions vont être ajoutées à notre méthode Initialize :
Matrix viewMatrix = Matrix.CreateLookAt(new Vector3(0, 10f, -40f), new Vector3(0, 0, 0), new Vector3(0, 0, 1));Matrix projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, this.Window.ClientBounds.Width / this.Window.ClientBounds.Height, 1.0f, 45.0f);
Ici nous créons deux matrices nommées viewMatrix et projectionMatrix.
La première matrice va contenir une information à propos de la position de la caméra, le point vers lequel elle regarde et enfin l'angle qu'elle effectue avec l'horizontale. La méthode CreateLookAtstatic de la classe Matrix permet cela. Le premier paramètre correspond à la position. Nous nous plaçons à une hauteur de 10 unités (Y = 10), en et nous reculons de 40 unités (Z = 40). Le second paramètre correspond au point vers lequel regarde la caméra (ici vers l'origine (0, 0, 0)). Enfin la caméra est tenue droite (le Y = 1 indique que la perpendiculaire de la caméra se confond avec l'axe Y).
La seconde matrice paramètre la caméra. Nous spécifions tout d'abord par l'intermédiaire de la méthode CreatePerspectiveFieldOfView de la classe Matrix que la caméra à un angle d'ouverture de PI/4. Nous donnons ensuite l'aspect ratio. Il s'agit du rapport de la largeur sur la hauteur de la zone d'affichage. Cela permet d'éviter de voir les objets affinés ou grossis si la taille de la zone d'affichage n'est pas carrée. Les deux derniers paramètres sont le near plane et le far plane. C'est à dire la plus proche position visible à l'écran et la plus éloignée.
Ces deux matrices étant définies il faut les affecter pour qu'elles soient prises en compte. Nous ferons cela par l'intermédiaire de notre effet. Intialize contient pour cela les instructions suivantes :
effect.Parameters["xWorld"].SetValue(Matrix.Identity); effect.Parameters["xView"].SetValue(viewMatrix);effect.Parameters["xProjection"].SetValue(projectionMatrix);
D'une manière générale l'instruction :
effect.Parameters["variable"]
donne accès à une variable déclarée dans un fichier effet. La méthode SetValue permet d'affecter une valeur à une variable. Nous affectons donc ici trois matrices à trois variables : xWorld, xView et xProjection. Ces trois variables correspondent aux trois variables majeures qui régissent l'affichage des objets en trois dimension. Nous connaissons déjà l'utilité de View et Projection. World permet de placer les objets dans le monde. Nous reviendons sur son utilisation plus tard. Pour l'heure nous lui affectons la valeur Matrix.Identity. C'est la valeur identité pour les matrices (comme le 1 pour lamultiplication ou le 0 pour l'addition).
Si vous lancez le programme à ce stade vous obtenez l'affichage suivant :

Transformations
Nous sommes bientôt au bout de notre périple. La prochaine étape consiste à faire tourner ce 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. Ajoutez le code suivant à la méthode Update :
Matrix world = effect.Parameters["xWorld"].GetValueMatrix() * Matrix.CreateRotationY(MathHelper.Pi/1000f*gameTime.ElapsedGameTime.Milliseconds);effect.Parameters["xWorld"].SetValue(world);
La première instruction rappatrie la valeur de la variable xWorld (de type matrice) du fichier effet . Elle la multiplie par une matrice effectuant une rotation sur l'axe Y. Nous effectuons une rotation d'un angle de Pi/1000 que nous multiplions que le temps écoulé depuis le dernier affichage.
Affecter une matrice à World va affecter tous les objets qui seront affichés par la suite. Ici nous n'affichons qu'un triangle. A l'exécution nous l'allons le voir tourner 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 intégré à XNA qui 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 XNA sait-il 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 lues 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).
Nous voyons à l'exécution notre triangle tourner complètement. Il reste toutefois un léger problème. Lorque le triangle, du fait de sa rotation, s'éloigne, il semble être tronqué (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. Remplacez l'instruction
Matrix projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, this.Window.ClientBounds.Width / this.Window.ClientBounds.Height, 1.0f, 45.0f);
par
Matrix projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, this.Window.ClientBounds.Width / this.Window.ClientBounds.Height, 1.0f, 100.0f);
cette fois-ci, le programme marche parfaitement.
Conclusion
Nos sommes maintenant prets à aborder les choses sérieuses. A partir de la 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. A partir de la nous seront capable d'aborder les lumières, l'affichage de modèles 3D et la création de terrains.
Vous pouvez télécharger le sample ici.
A bientôt sur ce Blog !
Valentin Billotte
Retourner au sommaire des cours