Astuces DotNet (Sébastien Courtois)

05/10/2010

[XNA 4] Tutoriel 3D 1 : Hello World 3D

Filed under: .NET, 3D, C# 4, Débutant, Intermediaire, XBOX 360, XNA — Étiquettes : , , , , , , , , — sebastiencourtois @ 16:31

Après une série de tutoriels sur la programmation 2D en XNA, nous allons passer à une partie plus intéressante et surtout plus complexe … la 3D. Qui n’a pas rêvé de recréer soi-même un Crysis ou encore un Gran Tourismo.

new-crysis-dx10-screenshot-20061110001326035 gran_turismo_5
Copie d’écran de Crysis Copie d’écran de Gran Tourismo 5

Il faut bien se rendre compte que ces jeux, magnifique graphiquement, ont été réalisés par des équipes composés de dizaines de programmeurs, artistes 2D/3D  qui ont travaillé pendant des mois pour arriver à un tel réalisme. Même si être ambitieux/optimiste  aide dans ce métier, il faut se rendre à l’évidence que vous ne pourrez pas seul et avant de nombreuses années arriver à un tel résultat.

Toutefois, si cela peut vous consoler, nous allons utiliser des outils/concepts proches de ce que les développeurs de ces jeux utilisent et nous seront confrontés (à notre niveau) aux mêmes types de problèmes inhérent à la création de jeu vidéo.

  • Un peu de vocabulaire/concept

Le monde de la 3d est un monde qui parait assez obscur et mathématiques avec son propre vocabulaire et ses propres concepts. Toutefois, ceux-ci peuvent être rattachés à des éléments du quotidien (on vit dans un univers en 3d, non :)).

Un Vertex (pluriel : vertices) : Un vertex est un point dans un univers 3d. C’est l’élément de base dans la construction de formes 3D primitives (lignes, triangle). Il est généralement décrit par sa position dans l’univers 3d ainsi que sa couleur, normals et d’autres informations nécessaires au rendu 3d que nous verrons plus tard.

Un ligne : Primitive 3D composé de deux vertices

Un triangle : Primitive 3d composé de 3 vertices. C’est la primitive la plus évolué qu’une carte graphique peut dessiner. Les modèles 3d que vous pouvez voir dans les jeux vidéo ne sont généralement qu’une accumulation de triangles mis cote à cote.

eames_wireframe Remarque : Les cartes graphiques peuvent aussi dessiner des primitives à 4 vertices mais cette fonctionnalité n’est pas disponible dans XNA 4.

Graphics Device : Ce terme représente l’accès à la carte graphique. Toute les opérations graphiques passeront par des appels au graphics Device (notamment la partie de rendu 3d).

  • Premier projet 3D en XNA

Dans ce tutoriel, nous allons créer un triangle.

Pour commencer, il faut créer un nouveau projet XNA (Windows Game). XNA nous crée déjà le graphics Device nécessaire à la communication avec la carte graphique ainsi que les outils pour la gestion du contenu (ContentManager).

Pour une scène 3d, il est nécessaire d’avoir au minimum 2 choses :

    • Un modèle 3D

Dans notre exemple, notre modèle 3D va être relativement simple car il s’agira d’un triangle. Pour cela, il nous suffit de créer un tableau de 3 vertices. Chaque vertex aura une position et une couleur. Dans notre classe Game, nous allons créer un tableau pour contenir les vertices ainsi qu’une méthode créant les données du triangle.

VertexPositionColor[] vertices;
//Création du triangle
private void CreateTriangle()
{
   this.vertices = new VertexPositionColor[]
   {
       new VertexPositionColor( new Vector3(-1,-1,0), Color.Red),
       new VertexPositionColor( new Vector3(0,1,0), Color.Green),
       new VertexPositionColor( new Vector3(1,-1,0), Color.Blue),
   };
}

Pour contenir les données d’un vertex, XNA fournit une série de classe de base permettant de stocker correctement ces données. Dans notre cas, nous souhaitons définir seulement la position et la couleur des points. Nous utiliserons donc la structure VertexPositionColor prenant en paramètre une position dans un univers 3D (sous la forme d’une structure Vector3) et une couleur. Nous stockons le tout dans un tableau défini au préalable dans notre classe Game.

Remarque : D’autres types de structures de stockage de vertices existent dans XNA (VertexPositionColor / VertexPositionColorTexture / VertexPositionNormalTexture / VertexPositionTexture). Vous pouvez créer votre propre classe en implémentant l’interface IVertexType. Nous verrons dans un  prochain tutoriel la création de notre propre structure de stockage.

    • Une visualisation (ou caméra)

Une fois le modèle 3D défini, il est nécessaire d’indiquer comment on souhaite le voir. Pour cela, il est nécessaire de fournir la position de l’observateur, l’endroit qu’il regarde et d’autres informations comme l’angle de vision. Ces informations seront ensuite stockées sous forme de matrices mathématiques qui serviront à la carte graphique pour le rendu 3D.

Matrix View;
Matrix Projection;
Matrix World;

Vector3 CameraPosition = new Vector3(0.0f, 0.0f, 5.0f);
Vector3 CameraTarget = Vector3.Zero;
Vector3 CameraUp = Vector3.Up;

//Création de la caméra
private void CreateCamera()
{
    View = Matrix.CreateLookAt(CameraPosition, CameraTarget, CameraUp);
    Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, this.GraphicsDevice.Viewport.Width / this.GraphicsDevice.Viewport.Height, 0.01f, 1000.0f);
    World = Matrix.Identity;
}

La première matrice est la matrice View (dite de “Visualisation”). Elle sert à positionner la caméra et la façon dont celle-ci regarde la scène. On utilise une méthode d’aide fourni par XNA (CreateLookAt) qui prend en premier paramètre la position de l’utilisateur dans l’univers 3D (sous la forme d’un Vector3). Le paramètre suivant est l’endroit que regarde l’utilisateur. Le troisième paramètre indique “l’orientation” de la caméra. Dans notre exemple, nous souhaitons que le dessus de la camera soit orienté vers le haut (coordonnées {0,1,0} ce qui équivaut au Vector3.Up).

La deuxième matrice est la matrice de projection. Celle-ci indique les caractéristique de la caméra (lentille, distance focale). Une méthode d’aide XNA permet de créer cette matrice facilement (CreatePerspectiveFieldOfView). Le premier paramètre est l’angle d’ouverture (ou angle de vision) de la caméra. Par exemple, un humain a un angle de vision allant de 1° (angle d’attention) jusqu’à 180 ° (angle de perception).Cela veut dire qu’il peut, sans bouger la tête voir tout ce qui l’entoure à 180 °. Arbitrairement, nous allons décider d’utiliser un angle de vision de 45° ou PI/4 (car les angles sont décrits en radians en XNA). Le paramètre suivant est le ratio d’aspect. Cela indique le format de la caméra (16/9 comme au cinéma, 4/3). Cela correspond tout simplement à la largeur de la zone visible divisé par la hauteur. Les deux derniers paramètres correspondent à la distance minimale et maximale d’affichage. Ainsi tous les objets trop proche ou trop loin de la caméra ne seront pas affichés.

La troisième matrice nous permettra de placer les objets dans l’univers 3d. Pour l’instant, nous lui assignons la valeur de base (matrice identité).

    • Méthode d’initialisation

Nous avons maintenant tous les éléments nécessaire pour réaliser un scène 3d. Nous allons maintenant appelé les deux méthodes décrites plus haut dans la méthode Initialize().

protected override void Initialize()
{
   this.CreateTriangle();
   this.CreateCamera();
   base.Initialize();
}
  • Affichage d’un triangle

Afin de réaliser l’affichage, nous allons utiliser un shader. Un shader est un programme créé spécifiquement pour être exécuté sur la carte graphique. En effet, la carte graphique permet la réalisation de calcul mathématique très rapidement. La création de shader sera l’objet de plusieurs tutoriels mais, dans ce premier article sur la 3d, nous allons utilisé celui fournit par XNA : BasicEffect.

BasicEffect effect;

protected override void LoadContent()
{
  spriteBatch = new SpriteBatch(GraphicsDevice);
  this.effect = new BasicEffect(this.GraphicsDevice);
}

BasicEffect se crée en fournissant uniquement le graphicsDevice et il sera utilisé lors de la partie affichage (méthode Draw).

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    effect.View = View;
    effect.Projection = Projection;
    effect.World = World;
    effect.VertexColorEnabled = true;

    foreach(EffectPass pass in effect.CurrentTechnique.Passes)
    {
        pass.Apply();
        this.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3);
    }

    base.Draw(gameTime);
}

Première étape dans la méthode Draw. Nettoyer l’écran avec une couleur de fond de la même façon que pour la 2D. On fournit ensuite les trois matrices de projections au shader BasicEffect. On active aussi le booléen VertexColorEnabled pour indiquer que l’on souhaite que les couleurs définis sur les vertices soient prises en compte. On utilise ensuite la propriété CurrentTechnique pour utiliser les différentes passes du shaders (le fonctionnement exacte des shaders sera expliqué dans un autre tutoriel et sa compréhension n’est pas indispensable pour celui-ci).

La méthode la plus importante de ce morceau de code est le DrawUserPrimitivers qui va prendre en paramètre le type de primitive. Il s’agit de la façon dont nous allons associer les vertices.

Initialising_Article_triangles

Dans notre exemple, nous utiliserons TriangleList. Cette primitive prend 3 vertex pour en faire un triangle puis les 3 vertex suivants pour un autre triangle et ainsi de suite.

Le deuxième paramètre de DrawUserPrimitives est le tableau de vertices proprement dit. Le paramètre suivant est l’index de départ du tableau de vertices. Dans notre cas, on souhaite utiliser tous les vertices donc on indique que le premier vertex est en position 0. Le dernier paramètre indiquent le nombre de primitives (ici triangle) que l’on souhaite dessiner. On prend le nombre total de vertices de notre tableau (3) et on divise par le nombre de vertex nécessaire pour faire un triangle (3). On indique que l’on souhaite dessiner un seul triangle.

Si vous lancer le projet, vous devriez obtenir l’écran suivant :

xna_tuto3d1_1

On remarque que le fond est bleu ce qui correspond à la couleur d’effacement au début de notre méthode Draw (Clear(Color.CornFlowerBlue)). Le triangle est visible mais on remarque que les couleurs, correctes aux sommets, fusionnent entre elles en s’écartant des sommets. Il s’agit du comportement de base de DirectX. Si l’on souhaite faire un triangle uni, il faut que les sommets aient la même couleur.

  • Un peu de mouvement

Nous allons introduire une rotation sur ce triangle afin de prouver que nous sommes bien dans un univers 3D. Pour cela, on va rajouter une variable de rotation que nous allons incrémenter dans la méthode Update.

float RotateZ = 0.0f;
protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
        this.Exit();

    RotateZ += MathHelper.ToRadians(2.0f);

    base.Update(gameTime);
}

Nous incrémentons donc la variable de 2° par appel à la méthode Update (60 x par secondes par défaut sur XNA). Il est à noter que la valeur est toujours à stocker en Radians. MathHelper fournit des méthodes d’aides pour traduire les angles de degré en radians et inversement.

Une fois la variable modifié, il suffit de modifier la matrice World qui est chargé de placer le triangle dans l’espace.

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    effect.View = View;
    effect.Projection = Projection;
    effect.World = World * Matrix.CreateRotationY(RotateZ);
    effect.VertexColorEnabled = true;

    foreach(EffectPass pass in effect.CurrentTechnique.Passes)
    {
        pass.Apply();
        this.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3);
    }

    base.Draw(gameTime);
}

Il suffit de multiplier la matrice World avec une matrice de rotation créé à partir de l’angle RotateZ pour réaliser cette effet. Si vous relancer l’application le triangle tourne…

  • Mon triangle disparait puis réapparait sans arrêt.

Lorsque vous lancez l’application le triangle commence à tourner mais il disparait un moment au bout d’un demi-tour puis réapparait. Cela est dû à une optimisation de la carte graphique nommé Backface Culling. En effet, les cartes graphiques n’affiche pas les faces (triangles) ne leur faisant pas face. Vous trouverez une explication plus complète sur le fonctionnement  ici. Il est possible de désactiver cette optimisation en modifiant la propriété RasterisationState du graphicsDevice. Cela se réalise généralement dans la méthode Initialize().

this.GraphicsDevice.RasterizerState = new RasterizerState()
{
   CullMode = CullMode.None,
   FillMode = FillMode.Solid
};

Remarque : Vous pouvez aussi régler par la propriété FillMode si vous souhaitez une vision des objets “solides” ou en fil de fer.

Vous pouvez remarquer que CullMode à 3 valeurs possibles : CullClockwiseFace ,CullCounterClockwiseFace, None. Par défaut, le CullMode est à CullClockwise ce qui veut dire que les vertex doivent être définis dans l’ordre des aiguilles d’une montre pour que la face soit visible.

TriangleAll
Représentation d’un triangle dont les vertices sont dans le sens des aiguilles d’une montre (tiré d’un tutoriel anglais XNA : http://www.toymaker.info/Games/XNA/html/xna_simple_triangle.html).

Lorsque le triangle a fait son premier demi-tour, les vertex passent dans le sens inverses. N’étant plus dans le sens des aiguilles d’une montre, le triangle n’est plus affiché.

Une autre solution, qui permet de conserver le Backface Culling, serait de créer un deuxième triangle avec les vertices dans l’ordre inverse. Ainsi, si on modifie le tableau de vertices comme suit  :

//Création du triangle
private void CreateTriangle()
{
    this.vertices = new VertexPositionColor[]
    {
        //Triangle 1
        new VertexPositionColor( new Vector3(-1,-1,0), Color.Red),
        new VertexPositionColor( new Vector3(0,1,0), Color.Green),
        new VertexPositionColor( new Vector3(1,-1,0), Color.Blue),
        //Triangle 2
        new VertexPositionColor( new Vector3(1,-1,0), Color.Blue),
        new VertexPositionColor( new Vector3(0,1,0), Color.Green),
        new VertexPositionColor( new Vector3(-1,-1,0), Color.Red),

    };
}

On obtient un triangle (en fait deux triangles qui s’interchange) qui tourne correctement.

  • Conclusion

Après ce tutoriel un peu long et complexe, je pense que vous aurez compris que la route est longue avant de pouvoir faire une jeu aussi techniquement abouti que les jeux présentés plus haut. Vous savez maintenant faire un triangle ce qui est la base de toutes les applications 3D. Dans les prochains tutoriels, nous verrons comment afficher un cube, le texturer puis se déplacer dans un univers 3d. Nous verrons aussi quelques astuces pour optimiser l’affichage.

N’hésitez pas à donner vos commentaires et poser vos questions sur ce tutoriel.

Lien vers la solution

19 commentaires »

  1. Merci pour ce tuto clair avec un exemple qui fonctionne. Et en français en plus🙂

    Petite observation, le « RasterizerState » ne semble pas avoir d’effet dans la fonction « Initialize() » alors je l’ai inséré dans la fonction « Draw » avant l’affichage du triangle et ça marche (reste à savoir si c’est propre.

    Commentaire par Forrest — 09/10/2010 @ 23:03

    • Bonjour,

      Sur l’exemple du triangle simple, j’arrive à avoir l’effet voulu en modifiant RasterizerState dans Initialize comme suit :
      protected override void Initialize()
      {
      this.CreateTriangle();
      this.CreateCamera();
      this.GraphicsDevice.RasterizerState = new RasterizerState()
      {
      CullMode = CullMode.None,
      FillMode = FillMode.WireFrame
      };
      base.Initialize();
      }

      Par contre l’utilisation de certains trucs (comme l’utilisation de spriteBatch) désactive ces fonctionnalités. Une solution est de se créer un RasterizerState au moment du Initialize et de sauvegarder l’état dans une variable. Puis, à chaque Draw juste avant l’affichage de la 3D, utiliser cette variable pour modifier la propriété RasterizerState de GraphicsDevice.

      Utiliser this.GraphicsDevice.RasterizerState = new RasterizerState() { … } en faisant donc un new à chaque Draw va ralentir le programme car on va créer une nouvelle classe 60 fois par secondes. Donc la RAM va monter jusqu’à ce que le garbagecollector se déclenche => Gros baisse de performances (peut être pas visible sur un triangle mais c’est un principe à retenir quand on fait ce type de projet).

      En espérant que cela t’aidera.

      Commentaire par sebastiencourtois — 10/10/2010 @ 09:01

      • Bonjour,

        Merci pour ta réponse. Effectivement ca fonctionne bien de cette manière car j’avais des éléments 2D avec un spriteBatch dans mon projet. Merci du conseil.

        Commentaire par Forrest — 10/10/2010 @ 09:43

  2. Bon tutoriel pour démarrer !

    Commentaire par Aybe — 08/12/2010 @ 20:13

  3. Bonjour !

    Petite erreur dans le texte. Vector3.Up = (0, 1, 0). Ne pas oublier que lorsqu’on regarde l’écran, le Z représente la profondeur entre l’observateur et le fond de l’espace 3D représenté. Sinon, ce tuto me plaît bien !🙂 Merci de l’avoir réalisé.

    Commentaire par DiOxy — 04/01/2011 @ 21:28

    • Merci pour la remarque. Une petite erreur lors de l’écriture/relecture de l’article. Corrigé !!!
      3 nouveau articles XNA vont arriver bientôt (dès que je me serai installé aux US… donc ce week end normalement :))

      Commentaire par sebastiencourtois — 04/01/2011 @ 22:37

  4. bonjour j’ai fait mon jeux je l’ai terminer et j’ai plus les ligne de code j’ai juste le jeux et j’ai dessiner un nouveau fond d’ecran et j’ai voulu e changer donc je suis parti sur visual j’ai mis une image j’ai clické sur la fleche vert je suis parti dans le dossier ou ya l’image xnb et quand je le remplace par l’ancien le jeux ne démarre pas je comprend pas et j’ai pas envi de refair tout le code pour changer un fond d’ecran aidé moi please

    Commentaire par yassine — 08/01/2011 @ 23:56

    • Je viens de tester avec deux xnb ayant une image différentes et cela marche correctement. Il faut que les assets des deux xnb aient les mêmes assetName afin de pouvoir être charger => Plantage. Pour le code source, tu peux toujours utiliser Reflector pour le récupérer.

      Commentaire par sebastiencourtois — 09/01/2011 @ 00:25

  5. bonsoir mercii pour avoir ton image xnb tu a fait comement? peut etre que je l’ai mal fait ? et si j’utilise la xna 4.0 et mon jeux a été fait en xna 3.1 c’est grave? merci pour ton aide🙂

    Commentaire par yassine — 09/01/2011 @ 00:48

    • J’utilise que la version 4.0 (je n’aime pas mélanger les versions). Il est possible que le problème vienne des différences de versions de xna entre les deux XNB. J’avoue ne pas savoir s’ils ont fait des modifs là dessus entre 3 et 4.

      Si tu peux essaie de générer un xnb en XNA 3 avec les assets de ton xnb XNA 4.ça marchera peut être.

      Commentaire par sebastiencourtois — 09/01/2011 @ 00:51

  6. j’ai pas tres bien compris tu peut m’expliqué tres rapidement ce que ta fait pour avoir l’image xnb?

    Commentaire par yassine — 09/01/2011 @ 00:55

    • Tu crée un nouveau « Empty Content Project » dans Visual Studio. Tu rajoute les différents assets faisant bien gaffe au assetName puis tu compile avec Visual et tu devrais avoir ton package XNB prêt.

      Commentaire par sebastiencourtois — 09/01/2011 @ 01:22

  7. mercii t’est un boss parce que oi je clickai sur le content deja fait j’ai fai un dossier gfx et je mettai mes image dedan et sa marchai pour mon jeu mais la sa marche pas bizar mais mercii bcp

    Commentaire par yassine — 09/01/2011 @ 10:52

  8. Bravo Sébastien,

    je cherchais un tutorial pour XNA 4.0 et parmi tous ceux que j’ai trouvé le tiens est le meilleur!

    Keep up the good work!

    Benoit

    Commentaire par Benoit — 23/11/2011 @ 22:27

    • Merci. En dehors des articles publies, il ne devrait plus avoir d’article XNA 4. Par contre, je pense faire des articles/librairies DirectX 11 manage tres bientot avec beaucoup de ressemblances au niveau architecture avec XNA.

      Commentaire par sebastiencourtois — 23/11/2011 @ 22:29

  9. Merci pour ce super tuto🙂

    Commentaire par zunk86 — 27/09/2012 @ 12:01

  10. Comme les autres tuto celui ci est tres bon, felicitation! Seul point noir au tableau, le site est quand a lui tres brouillon. La recherche et la navigation pour trouver le bon tuto est tres difficile, dommage…

    Commentaire par david — 17/07/2013 @ 01:05

    • Oui c’est pour ca que j’ai arrête de poster depuis un moment car la façon de diffuser l’info ne me plaisait pas.
      J’envisage de faire quelque chose de plus clair et formel et de plus general sur la creation de jeu vdideo mais pas trouver la motivation de m’en occuper.

      Commentaire par sebastiencourtois — 17/07/2013 @ 01:08

      • Je comprends et j espere que tu trouvera la motivation de t y remettre. Personnellement je viens juste de me mettre a la programmation de jeux videos et je trouve que tes tutos sont vraiment tres bien foutus. Ils sont complets, facile d acces pour un debutant comme moi et agreable a lire (et je peux te dire que j en ai teste des tas meme anglais). Tu pourrai dans un premier temps juste tranferer les tutos deja existant sur un « vrai » site avec des menus qui vont bien et tout ca.
        Tu pourrai aussi eventuellement utiliser comme support pour tes tutos « le site du zero » (http://www.siteduzero.com/) que tu dois surement connaitre et qui offre aussi de tres bon tutos.
        En esperant pouvoir lire tes futur tutos je te souhaite une bonne continuation.

        Commentaire par david — 17/07/2013 @ 10:23


RSS feed for comments on this post. TrackBack URI

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Propulsé par WordPress.com.

%d blogueurs aiment cette page :