Débutez le DirectX en C Sharp : Partie 3
Un article de Thanos.
Sommaire |
[modifier] Introduction
Bonjour les amis, le théâtre de Kitty est fier de vous présenter le troisième épisode de "Débutez le DirectX en CSharp". Dans cet épisode, on réalisera un projet depuis le début, pour valider nos acquis, et aller un peu plus loin en entrant dans l'univers 3D et aussi en manipulant mieux nos acquis pour mieux les comprendre.
[modifier] Devenir de ces tutoriaux
Je me posais la question récemment, "Quand arrêterais-je les tutos C# - DirectX ?". Je ne veut pas trop m'avancer mais plutôt définir un but à ces Tutos, en disant que je veux aller, jusqu'à un mini FPS.
Rien de très stimulant, ne vous emballez pas, je pense à une scène 3D, un personnage 3D (le joueur) que l'on manipulera pour qu'il interagisse avec son environnement, un autre personnage 3D (l'ennemi), et bien sur le flingue (sans quoi le FPS n'existerait pas). Maintenant seul l'avenir nous dira si nous arriverons jusque là.
[modifier] Remerciements
Je pense nécessaire de remercier deux amis qui m'aident beaucoup à l'élaboration de ces articles, Progi1984 qui sur son temps libre corrige toutes les fautes (qui sont énormes) de français que je fais [Edit de Progi1984 : Eh oui !]. Et bien sur MrCool qui nous à donné ce wiki. Sans oublier tous les membres du GCN qui sont toujours disponible dans mes moments de doute. Je tiens aussi à m’excuser si je ne suis pas toujours très clair.
[modifier] Présentation
Dans ce tutos nous allons :
- Créer un nouveau projet (vu dans la Partie 1)
- Initialiser DirectX (vu dans la Partie 1)
- Créer un objet 3D (tétraèdre) (vu en 2D dans la Partie 2)
- Donner un mouvement à cet objet grâce aux fléches du clavier (pas encore vu).
[modifier] Création du Projet
La création du projet se fait en trois étapes,
- Créer un nouveau Projet
- Mettre les références à DirectX
- Mettre les "using" pour ne pas avoir à écrire le chemin complet des fonctions DirectX.
- Initialiser le device
- Coder le Main.
[modifier] Créer un nouveau projet
Menu Fichier -> Nouveau Projet -> Windows Application
[modifier] Mettre les références de DirectX et les "using"s
Ajouter les références :
Microsoft.DirectX Microsoft.DirectX.Direct3D
Mettre les using
using Microsoft.DirectX; using Microsoft.DirectX.Direct3D;
[modifier] Initialiser le Device
Créer la variable globale :
private Device device = null;
Créer la méthode :
public void InitializeGraphics() { //On définit les paramètres de présentation PresentParameters Params = new PresentParameters(); //On spécifie que l'application sera dans une fenêtre Params.Windowed = true; Params.SwapEffect = SwapEffect.Discard; //Création du device device = new Microsoft.DirectX.Direct3D.Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, Params); }
[modifier] Initialiser le Main
static void Main() { //On instancie la fenêtre qui contient notre programme DirectX Form1 frm = new Form1(); //On initialise le device frm.InitializeGraphics(); //On fait apparaître la fenêtre frm.Show(); //Tant que la fenêtre existe while(frm.Created) { //on dessine notre objet (méthode pas encore faite) frm.Render(); //on passe à notre fenêtre tout les évènements. Application.DoEvents(); } //On dispose le device après utilisation frm.DisposeGraphics(); }
Vous allez me dire que votre classe Form1 n'a pas de méthode qui s'appelle Render, ni de méthode qui s'appelle DisposeGraphics. Justement pour cette dernière, on va la créer à l'instant.
Créer la méthode :
public void DisposeGraphics() { //on dit au garbage collector qu'il peut "disposer" du device device.Dispose(); }
[modifier] Créer le Tétraèdre
Nous attaquons maintenant le gros du problème. Je vous montrerai ici, d'autres méthodes (pour que vous les connaissiez) que les méthodes vues dans la Partie 2 (par exemple ici on n’utilisera pas de GraphicStream).
Nous avons notre device qui servira à faire le rendu. Mais il nous faut bien plus pour afficher un objet 3D.
Il nous faut donc :
- Un tableau contenant les coordonnées des sommets que l'on veut afficher. (le VertexBuffer)
- Informer DirectX que l'on veut dessiner quelque chose grâce à lui.
- Créer le dessin que l'on veut afficher.
- Faire le rendu à l'écran.
[modifier] Initialiser le Vertex Buffer
Dans un premier temps, on crée le VertexBuffer.
private VertexBuffer vertices;Puis après, nous allons créer une méthode qui se chargera de créer le vertexBuffer au début du programme.
private VertexBuffer CreateVertexBuffer(Device device) { device.VertexFormat = CustomVertex.PositionColored.Format; VertexBuffer buf = new VertexBuffer( typeof(CustomVertex.PositionColored), 7, device, 0, CustomVertex.PositionColored.Format, Pool.Default); return buf; }
Puis dans le InitializeGraphics nous devons dire que nous voulons Instancier le VertexBuffer. Donc à la fin de notre méthode InitializeGraphics nous allons appelé CreateVertexBuffer
vertices = CreateVertexBuffer(device);
Nous avons maintenant notre VertexBuffer, et vous avez remarqué que je l'ai informé que j'allais lui donner 7 sommets, il faut maintenant les créer ces 7 sommets.
[modifier] Création des sommets du Tétraèdre
Nous allons les créer dans notre méthode CreateVertexBuffer, juste avant le " return buf;"
//On crée le tétraèdre directement dans le VertexBuffer. CustomVertex.PositionColored[] tetra = (CustomVertex.PositionColored[])buf.Lock(0,0); //le corps du tétraèdre tetra[0].Position = new Vector3(0.67f, 0.33f, 0.44f); tetra[0].Color = System.Drawing.Color.White.ToArgb(); tetra[1].Position = new Vector3(1.0f, 1.0f, 0.11f); tetra[1].Color = System.Drawing.Color.Cyan.ToArgb(); tetra[2].Position = new Vector3(0.33f, 1.0f, 0.11f); tetra[2].Color = System.Drawing.Color.Magenta.ToArgb(); tetra[3].Position = new Vector3(0.67f, 1.0f, 0.78f); tetra[3].Color = System.Drawing.Color.Yellow.ToArgb(); tetra[4].Position = new Vector3(1.0f, 1.0f, 0.11f); tetra[4].Color = System.Drawing.Color.Cyan.ToArgb(); //la base du tétraèdre avec tetra[4] tetra[5].Position = new Vector3(0.33f, 1.0f, 0.11f); tetra[5].Color = System.Drawing.Color.Magenta.ToArgb(); tetra[6].Position = new Vector3(0.67f, 1.0f, 0.78f); tetra[6].Color = System.Drawing.Color.Yellow.ToArgb(); buf.Unlock();
Remarque 1 :
Vous remarquerez que nous utilisons ici des "CustomVertex.PositionColored" et non des "CustomVertex.TransformedColored".
La différence entre les deux est que les sommets Transformed sont des sommets de l'espace que l'on a créé mais nous ne pouvons pas leur appliquer des transformations matricielles lors du rendu, contrairement aux sommets Position.
La différence se traduit donc, que pour les Transformed on donne les coordonnées des sommets dans le plan.(exemple 150.0f, 200.0f). Et pour les Position, on leur donne des quantité (comme un pourcentage).(exemple: 1.0f, 0.5f etec ...).
Donc les coordonnées que je vous donne ont été faites sur une feuille de papier à la main et avec des équations mathématiques. (Vous pouvez vous amusez à créer d'autres objets de la même manière exemple : un carré).
Remarque 2 :
Vous me direz sûrement, "mais un Tétraèdre a 4 sommets, et non 7"
La raison est que on doit passez à DirectX les sommets dans un ordre précis (dans le sens des aiguilles d'une montre normalement) pour qu'il puisse :
- Dessiner les sommets dans le bon ordre, et dons les relier dans le bon ordre (et non pas relier le point x de la table avec le point y de la chaise).
- L'ordre dans lequel on définit les sommets sert aussi à directX, pour qu'il sache de quel coté est la normale de la face.(en gros si la face qu'il dessine est face à face avec nous ou non, car il est inutile d'afficher des faces que nous ne pouvons pas voir).
Maintenant que le tétraèdre est crée, nous devons le passer au device pour qu'il fasse son travail et donc le rendu.
[modifier] Créer le rendu
Vous vous souvenez de la méthode Render qui était dans le main. C'est elle qui va s'occuper de faire notre rendu. Nous allons la créer maintenant.
public void Render() { //Efface le Back Buffer et l'initialise en Bleu this.device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0); // Prépare Direct3D à dessiner device.BeginScene(); // Dessine la scène device.SetStreamSource(0, vertices, 0); device.DrawPrimitives(PrimitiveType.TriangleFan, 0, 3); device.DrawPrimitives(PrimitiveType.TriangleList, 4, 1); // Indique à Direct3D qu'on a fini de dessiner device.EndScene(); // Copie le back buffer à l'écran device.Present(); }
Normalement tout cela vous l'avez déjà vu dans la Partie 2 de ce tutorial.
La seule chose nouvelle est TriangleFan qui dessine comme ça :
Donc dans la ligne :
device.DrawPrimitives(PrimitiveType.TriangleFan, 0, 3);
Nous lui disons "tu va dessiner 3 triangles à la manière TriangleFan, à partir de l'élément 0 du VertexBuffer".
Puis nous créons la base du tétraèdre avec un TriangleList.

En lui disant, tu va dessiner un Triangle à la manière d'un TriangleList, à partir de l'élément 4 contenu dans le VertexBuffer.
Remarque :
Pour information, vous avez aussi le TriangleStrip qui est intéressant.
Maintenant que le rendu est fini, vous vous étonnerez de ne pas voir votre tétraèdre sur votre Form1, ne vous inquiétez pas c'est normal. Nous devons pour cela mettre le tétraèdre dans notre champ visuel.
[modifier] Les transformations Matricielles
Avant d'attaquer ce chapitre nous allons faire un peu de théorie. Je sais que c'est barbant, mais nécessaire pour une bonne compréhension du fonctionnement de DirectX.
[modifier] Introduction
Imaginez que vous vouliez modéliser votre salon, alors votre salon posséde une télé, une chaise, etc .... Si vous utilisez le système de coordonnées de l'espace, en mettant chaque objet à sa place, vous allez avoir beaucoup de mal pour définir la télé en (xxx, yyyyy, zzzz), alors comme vu précédemment, on va modéliser la télé dans un espace local (à l'objet) en (0,0,0) puis la déplacer jusqu'à l'espace (non local, ou se trouve la scène) souhaité. Donc nous devrons :
- Modéliser la télé en (0,0,0)
- Déplacer la télé d'un espace à l'autre
- Déplacer la télé pour la mettre en (xxx,yyyy,zzzz)
- Faire une rotation de la télé sur l'axe Y pour qu'elle soit positionné vers le canapé.
Tous ces "déplacements" sont en fait des transformation que l'on applique à la télé à travers des matrices (si vous savez pas ce que c'est, prenez des cours d urgence : sur google, vous en trouverez en quantité). Les matrices servent aussi à déformer l'objet et lui appliquer toutes sortes de transformations.
Heureusement, DirectX nous aide grâce à l'espace de nom :
- Microsoft.DirectX.Matrix
Nous allons utiliser pour notre code 3 méthodes de la Matrix
- LookAtLH
- PerspectiveFovLH
- RotationY
[modifier] LookAtLH
Le LH est pour Left Hand (main gauche) car en 3D, pour connaître l'orientation des axes x, y z, nous utilisons le moyen mnémotechnique de la main droite ou la main gauche. Car vous avez appris dans vos livres scolaires, le système de la main droite :
- x augment en allant vers la droite (pouce de la main droite pointé vers la droite)
- y vers le haut (index de la main droite pointé vers le haut)
- z vers nous (majeur de la main droite tendu vers nous)
Mais en Imagerie numérique en informatique, z va en s'éloignant de nous (pour respecter la vue écran) donc le système de la main gauche
- x augment en allant vers la droite (pouce de la main gauche pointé vers la droite)
- y vers le haut (index de la main gauche pointé vers le haut)
- z tendu en s'éloignant de nous (majeur de la main gauche tendu vers le sens opposé a nous)
La fonction LooAtLH est très utilisée, car elle permet de placer le point de vue utilisateur (c'est à dire la caméra, à travers laquelle on voit).
public static Matrix LookAtLH( Vector3 cameraPosition, Vector3 cameraTarget, Vector3 cameraUpVector );
- CameraPosition est un Vector3 qui indique où est situé la caméra (le point de vue)
- CameraTarget est un Vector3 qui indique vers ou regarde la Camera
- CameraUpVector est un Vector3 qui indique le sens du haut (généralement (0,1,0), qui veut dire que le sens du haut est pointé par l'axe y).
[modifier] PerspectiveFovLH
PerspectiveFovLH est une fonction qui sert à définir la perspective et la focale de la caméra.
public static Matrix PerspectiveFovLH ( float fieldOfViewY, float aspectRatio, float znearPlane, float zfarPlane );
- fieldOfViewY est le champs de vision dans la direction d'y, défini en radians (et non en degrés), en gros c'est la lentille de la caméra (normalement Math.PI/4.0f (45°))
- aspectRatio, définit le rapport entre la hauteur et la largeur (normalement 1.0f)
- znearPlane, définit la distance minimale à afficher, c'est à dire que dx n'affichera rien avant la distance znearPlane.
- zfarPlane, définit la distance maximale à afficher, c'est à dire que dx n'affichera rien après la distance zfarPlane.
[modifier] RotationY
Il y a rien de plus simple, on fait une rotation sur l'axe de Y.
public static Matrix RotationY ( float angle );
- Angle définit l'angle de rotation.
[modifier] Inclusion des Transformations Matricielles dans le code
Pour commencer nous allons créer une fonction qui se chargera de cela.
protected void SetupMatrices() { //CodeSM }
Nous allons également définir 3 variables globales
private float m_angleY = 0.0f; private float m_zoom = (float)Math.PI/4.0f; private float m_XeyePos = 0.0f;
(Je pense que les noms des variables indique bien leurs fonctions réciproques)
Puis dans la méthode Render, juste après le device.BeginScene(), nous allons appeler notre fonction
SetupMatrices();
Maintenant vous verrez qu'il n'y a rien de plus simple ,nous allons coder SetupMatrices :
//Place le point de vue utilisateur device.Transform.View = Matrix.LookAtLH( new Vector3(m_XeyePos, 0.5f, -3.0f), new Vector3(0.0f, 1.0f, 0.0f), new Vector3(0.0f, -1.0f, 0.0f)); //Zoom la scene device.Transform.Projection = Matrix.PerspectiveFovLH( this.m_zoom, 1.0f, 1.0f, 4.0f); //Rotation device.Transform.World = Matrix.RotationY(m_angleY/ (float)Math.PI); //On dit qu’on n’utilisera pas la lumière de DX device.RenderState.Lighting = false; //Dx par défaut n'affiche pas la face masquée d'un objet; en mettant à Cull.None , il le fera. device.RenderState.CullMode = Cull.None;
[modifier] Modification à l'aide du clavier
Dans les propriété de notre Form1, section évènements, nous allons double-cliquer sur Key_Down, pour faire apparaître :
private void Form1_KeyDown(object sender, KeyEventArgs e) { }
Cet évènement écoute les touches du clavier, pour nous dire quelle touche a été pressée. Nous implémenterons donc le corps de la méthode de la manière suivante.
if (e.KeyCode == Keys.Up) this.m_zoom = this.m_zoom / 1.1f; else if (e.KeyCode == Keys.Down) this.m_zoom = this.m_zoom * 1.1f; if (e.KeyCode == Keys.Left) this.m_angleY += 0.1f; else if (e.KeyCode == Keys.Right) this.m_angleY -= 0.1f; if (e.KeyCode == Keys.NumPad4) m_XeyePos += 0.1f; else if (e.KeyCode == Keys.NumPad6) m_XeyePos -= 0.1f;
En gros si les touches flèches sont appuyées, il génère une action, si les touches 4 et 6 du clavier numérique sont pressées, il génère également une action.
[modifier] Compilation et execution
Compilé et éxécuté
[modifier] Prochainement
Nous verrons au prochains épisode le texturing.
[modifier] Liens
- Debutez DirectX avec C# : Partie 1
- Débutez le DirectX en C Sharp : Partie 2
- Débutez le DirectX en C Sharp : Partie 4
- Débutez le DirectX en C Sharp : Partie 5
- Débutez le DirectX en C Sharp : Partie 6
- Débutez le DirectX en C Sharp : Partie 7
- Microsoft
- MSDN


(aucun commentaire actuellement)