XNA Tutorial 6 : Les indices

Retourner au sommaire des cours  


Nous avions vu dans le troisième tutorial (ici) qu’il y avait plusieurs type de reliasons. Toutes ont des avantages et des défauts. Si la reliaison TriangleList permet de créer n’importe quelle forme, elle demande un grand nombre de vertices. TriangleFan ne permet que de faire des formes coniques, mais avec peu de vertices. Enfin TriangleStrip est à préférer dans l’assemblage de triangles côtes à côtes. Certaines formes trop complexes nous obligent à utiliser TriangleList au prix d’un grand nombre de vertices. Le précédent tutorial illustre parfaitement ce type de problème : Pour un simple cube, nous avons du créer 36 vertices ! Les indices sont une solution très efficaces à ce problème.


Les indices


36 vertices pour faire un cube … c’est énorme. Encore plus si on regarde le tableau de vertices sous cet angle :


vertices[0].Position = new Vector3(-10f, -10f, 10f);
vertices[1].Position = new Vector3(-10f, 10f, 10f);
vertices[2].Position = new Vector3(10f, 10f, 10f);
vertices[3].Position = new Vector3(-10f, -10f, 10f);
vertices[4].Position = new Vector3(10f, 10f, 10f);
vertices[5].Position = new Vector3(10f, -10f, 10f);
vertices[ 6 ].Position = new Vector3(10f, -10f, 10f);
vertices[7].Position = new Vector3(10f, 10f, 10f);
vertices[ 8 ].Position = new Vector3(10f, 10f, -10f);
vertices[9].Position = new Vector3(10f, -10f, 10f);
vertices[10].Position = new Vector3(10f, 10f, -10f);
vertices[11].Position = new Vector3(10f, -10f, -10f);
vertices[12].Position = new Vector3(10f, -10f, -10f);
vertices[13].Position = new Vector3(10f, 10f, -10f);
vertices[14].Position = new Vector3(-10f, 10f, -10f);
vertices[15].Position = new Vector3(10f, -10f, -10f);
vertices[16].Position = new Vector3(-10f, 10f, -10f);
vertices[17].Position = new Vector3(-10f, -10f, -10f);
vertices[18].Position = new Vector3(-10f, -10f, -10f);

vertices[19].Position = new Vector3(-10f, 10f, -10f);
vertices[20].Position = new Vector3(-10f, 10f, 10f);
vertices[21].Position = new Vector3(-10f, -10f, -10f);
vertices[22].Position = new Vector3(-10f, 10f, 10f);
vertices[23].Position = new Vector3(-10f, -10f, 10f);
vertices[24].Position = new Vector3(-10f, 10f, 10f);
vertices[25].Position = new Vector3(-10f, 10f, -10f);
vertices[26].Position = new Vector3(10f, 10f, -10f);
vertices[27].Position = new Vector3(-10f, 10f, 10f);
vertices[28].Position = new Vector3(10f, 10f, -10f);
vertices[29].Position = new Vector3(10f, 10f, 10f);
vertices[30].Position = new Vector3(-10f, -10f, -10f);
vertices[31].Position = new Vector3(-10f, -10f, 10f);
vertices[32].Position = new Vector3(10f, -10f, 10f);
vertices[33].Position = new Vector3(-10f, -10f, -10f);
vertices[34].Position = new Vector3(10f, -10f, 10f);
vertices[35].Position = new Vector3(10f, -10f, -10f);


Nous avons mis en évidence les vertices qui possède une position identique en les regroupant par couleur. On remarque que seule 8 couleurs sont utilisées et donc que seuls 8 positions sont nécessaire pour créer un cube. Mais la contrainte du TriangleList nous oblige a utiliser un vertex pour chaque triangle qui l’utilise. L’image suivante se passe de commentaires :



Le vertex en surbrillance rouge, qui correspond au vertices 8, 10, 13 et 35 appartient à 3 faces dans ce cube. Il est ainsi relié à un triangle de la face supérieure (rose), à deux triangles de la face frontale (jaune) et un triangle de la face droite (bleue). Il sera donc à ce stade de nos connaissances répliqué 4 fois ! C’est autant de place de perdue dans la mémoire de la carte graphique lorsque nous appellons la méthode DrawUserPrimitive en lui passant un tableau aussi inutilement gros.


Les indices permettent de spécifier la reutilisabilité d’un point. Le système est assez comparable au jeu “reliez les points” :



Figure (a)


Appliquons ce système au cube. Le tableau de vertices n’aura maintenant que 8 cases correspondant au 8 vertices uniques que nous avons déterminé plus haut :


vertices[0].Position = new Vector3(-10f, -10f, 10f);
vertices[1].Position = new Vector3(-10f, 10f, 10f);
vertices[2].Position = new Vector3(10f, 10f, 10f);
vertices[3].Position = new Vector3(10f, -10f, 10f);
vertices[4].Position = new Vector3(10f, 10f, -10f);
vertices[5].Position = new Vector3(10f, -10f, -10f);
vertices[ 6 ].Position = new Vector3(-10f, -10f, -10f);
vertices[7].Position = new Vector3(-10f, 10f, -10f);


En image cela nous donne :



Figure (b)


Maintenant nous devons relier ces points. Non pas comme le père noel de la figure (a), en reliant tous les points par ordre croissant, mais en définissant des triplets correspondant à des triangles. Avant de passer à la pratique faisons un peu de theorie.


Le premier triplet sera (0,1,2), c’est à dire le premier vertice, le second, et le troisième (nous les spécifions dans l’ordre des aiguilles d’une montre pour le culling). Le premier triangle est relié :



Passons au second triangle le triplet sera : (0,2,3). Nous donnons ici deux indices (0 et 2) qui ont déjà été utilisé avant. Un bel exemple du gain en terme de nombre de vertices.



et ainsi de suite. Au final l’ensemble de triplet sera :


(0,1,2)//face devant
(0,2,3)
(3,2,4)//face droite
(3,4,5)
(5,4,7)//face arrière
(5,7,6)
(6,7,1)//face gauche
(6,1,0)
(6,0,3)//face bas
(6,3,5)
(1,7,4)//face haut
(1,4,2)


Amusez vous à relier les triangles à l’aide du schéma de la figure (b) et cet ensemble de triplet. Vous devirez avoir au final :



Figure (c)


Avec seulement 8 vertices nous créons une forme a 12 primitives (12 triangles) ! La mémoire de la carte graphique vous remerciera par de meilleures performances.


Maintenant que la théorie est aquise, passons à la pratique.


Notre code


Reprennez le code du projet précédent et commencez par définir deux nouvelles variables :


VertexBuffer vertexBuffer;
IndexBuffer indexBuffer;


Ces deux objets représentent des buffers. Ils sont destinés à receuillir respectivement des vertices et des indices. Ces buffers sont plus efficaces qu’un simple tableau pour gérer des ensembles de données. La carte vidéo sait très bien les appréhender et place leur contenu dans sa propre mémoire afin d’y accéder rapidement, et le développeur y trouve son compte par l’intermédiaire de méthodes clés en main qui lui permettent un contrôle sécurisé sur ces données. Nous devrons donc remplir vertexBuffer avec les vertices de notre cube et remplir indexBuffer avec les indices vers les vertices dans un ordre approprié permettant de lister les primitives de ce cube. Nous allons créer deux méthodes la première pour initialiser et charger le contenu du buffer de vertices et le second pour intiialiser et charger le buffer d’indices. La première méthode sera nommée InitializeVertices, elle se présente ainsi :


private void InitializeVertices()
{
    vertices = new VertexPositionColor[ 8 ];
    vertices[0].Position = new Vector3(-10f, -10f, 10f);
    vertices[0].Color = Color.Yellow;
    vertices[1].Position = new Vector3(-10f, 10f, 10f);
    vertices[1].Color = Color.Green;
    vertices[2].Position = new Vector3(10f, 10f, 10f);
    vertices[2].Color = Color.Blue;
    vertices[3].Position = new Vector3(10f, -10f, 10f);
    vertices[3].Color = Color.Black;
    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.Violet;
    vertices[ 6 ].Position = new Vector3(-10f, -10f, -10f);
    vertices[ 6 ].Color = Color.Orange;
    vertices[7].Position = new Vector3(-10f, 10f, -10f);
    vertices[7].Color = Color.Gray;
    this.vertexBuffer = new VertexBuffer(this.graphics.GraphicsDevice, typeof(VertexPositionColor), 8, BufferUsage.WriteOnly);
    this.vertexBuffer.SetData(vertices);
}


Nous créons ici un simple tableau à 8 cases (pour nos 8 vertices). Les deux dernières instructions créé et “remplissent” le vertexBuffer. Le constructeur de VertexBuffer prend en paramètre, le device (lien vers la carte graphique), le type des vertices, leur nombre enfin, en dernier paramètre nous donnons une valeur de type BufferUsage qui présice de quelle manière notre application va manipuler le buffer. BufferUsage prend trois valeurs :


  • None : indique que nous ne précisons pas de quelle manière le buffer doit être manipulé par notre code
  • Points : précise que les éléments présents dans le vertex buffer seront utilisés pour afficher des points et qu’il doit se préparer à des accès fréquent pour des mises à jours de position.
  • WriteOnly : précise que notre application n’accèdera jamais au buffer hormi lors de la phase de création (seule la carte graphique et le fichier effet y accéderont). Dans ce cas, la carte graphique va chercher le meilleur emplacement possible en terme de performance pour stocker le contenu du buffer. A noter que tout accès en lecture depuis notre application échouera.

Passons à la méthode qui sera nommée InitializeIndices :


private void InitializeIndices()
{
    short[] indices = new short[36]{ 
    0,1,2, //face devant
    0,2,3,
    3,2,4, //face droite 
    3,4,5,
    5,4,7, //face arrire 
    5,7,6,
    6,7,1, //face gauche
    6,1,0,
    6,0,3, //face bas 
    6,3,5,
    1,7,4, //face haut 
    1,4,2};
    this.indexBuffer = new IndexBuffer(this.graphics.GraphicsDevice, typeof(short), 36, BufferUsage.WriteOnly);
    this.indexBuffer.SetData(indices);
}


La première instruction créé et initialize un tableau de short. On y trouve 12 fois 3 entiers qui correspondent aux triplets formant des triangles (triplets explicités en début de tutorial). Vient ensuite la création de l’objet IndexBuffer. Le constructeur est similaire au constructeur de VertexBuffer nous ne reviendrons donc pas dessus. La dernière instruction rempli le buffer à l’aide des indices préalablement créés. Ces deux méthodes étant créées il nous reste à nettoyer la méthode Initialize de notre application pour supprimer toutes les instructions de remplissage de vertices issues du tutorial précédent. Nous remplacerons ces instruction par deux appels vers les méthodes que nous venons d’écrire :


protected override void Initialize()
{
    // TODO: Add your initialization logic here
    this.graphics.IsFullScreen = false;
    this.graphics.PreferredBackBufferWidth = 800;
    this.graphics.PreferredBackBufferHeight = 600;
    this.graphics.ApplyChanges();
    this.Window.Title = “Sixime Tutorial, CinquimeProjet : Les indices !”;
    this.Window.AllowUserResizing = true;

    this.InitializeVertices();
    this.InitializeIndices();

    base.Initialize();
}


Ne reste donc plus qu’à modifier l’affichage du cube. Nous devons spécifier au device le vertexbuffer et l’index buffer que nous utilisons et utiliser une autre méthode de rendu (autre que DrawuserPrimitive) conçue pour l’affichage de formes 3D à l’aide d’indices et utilisant des buffers. La méthode Draw se présente maintenant ains i :


protected override void Draw(GameTime gameTime)
{
    this.graphics.GraphicsDevice.Vertices[0].SetSource(this.vertexBuffer, 0, VertexPositionColor.SizeInBytes);
    this.graphics.GraphicsDevice.Indices = this.indexBuffer;
    this.graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(this.graphics.GraphicsDevice, VertexPositionColor.VertexElements);
    graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

    // TODO: Add your drawing code here 
    this.effect.Begin();

    foreach (EffectPass pass in effect.CurrentTechnique.Passes)
    {
        pass.Begin();
        this.graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);
        pass.End();
    }

    effect.End();

    base.Draw(gameTime);
}


Voyons maintenant les instructions qui nous sont inconnues.


L’objet device possède un membre Vertices qui permet de spécifier des VertexBuffer utilisés pour l’affichage. C’est une technique très utile pour passer plusieurs vertexbuffer en parallèle au device (le passage de plusieurs vertex buffer permet de simplifer des techniques d’affichage 3D comme le Morphing en soumettant au fichier effet plusieurs sources de données). Pour l’heure nous ne donnons qu’un seul vertexbuffer (en position 0 donc) à l’aide de la méthode SetSource. Celle-ci reçoit en paramètres le buffer, l’index à partir duquel on parcours le buffer et enfin la taille en octet du type VertexPositionColor :


this.graphics.GraphicsDevice.Vertices[0].SetSource(this.vertexBuffer, 0, VertexPositionColor.SizeInBytes);


La seconde instruction spécifie les indices à utiliser sous la forme d’un IndexBuffer :


this.graphics.GraphicsDevice.Indices = this.indexBuffer;


 Vient enfin l’instruction d’affichage modifiée :


this.graphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 8, 0, 12);


Nous sommes toujours en type de reliaison TriangleList mais en mode indexé (avec indices). Le second paramètre spécifie ainsi l’index à ajouter à chaque indices pour atteindre le bon vertex. Le troisième paramètre indique à partir de quel vertice on commence à parcourir le buffer de vertices. Le quatrième paramètre correspond au nombre de vertices à afficher. L’avant dernier indique à partir de quel indice on commence à parcourir le buffer d’indices. Enfin le dernier paramètre donne le nombre de primitives (triangles) à afficher.


A l’exécution on obtient le même cube, mais avec 28 vertices en moins à afficher et une carte vidéo heureuse :



Conclusion


Cette fois-ci notre maitrise des vertices est parfaite. Nous sommes en mesure d’afficher des formes avec le minimum de vertices possible. Il convient de rester conscient toutefois qu’il est impossible, même avec les indices de créer des formes complexes comme un visage humain, une voiture de course ou tout autre forme complexe. Nous devons passer par des outils de modélisations comme 3DSMax ou Maya qui sont spécifiquement conçu dans cette optique. Nous verrons d’ailleur dans un prochain tutorial comment charger de tel modèles générés par ces outils (sous la forme de fichiers) et les afficher.


telecharger Vous pouvez télécharger le sample ici.   


[Soon]


Valentin


Retourner au sommaire des cours 

XNA Tutorial 5 : Les matrices et les Transformations

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. 


Un Cube en 3D est l'assemblage de 12 triangles


 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. La première étape consiste à modifier notre triangle afin de le rendre rectangle (triangle à angle droit). Les trois vertices définis ainsi :


vertices = new VertexPositionColor[3];
vertices[0].Position = new Vector3(0.5f, -0.5f, 0f);
vertices[0].Color = Color.Yellow;
vertices[1].Position = new Vector3(0, 0.5f, 0f);
vertices[1].Color = Color.Green;
vertices[2].Position = new Vector3(-0.5f, -0.5f, 0f);
vertices[2].Color = Color.Red;


deviennent :


vertices = new VertexPositionColor[3];
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;


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 (les numéros en rouge correspondant à l’index des vertices dans le tableau vertices) :



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)… En outre nous sommes en train de construire un cube. Il n’est donc plus nécessaire d’annuler le backface culling les faces des triangles non visibles étant situées à l’intérieur du cube. Supprimer pour cela l’instruction :


this.graphics.GraphicsDevice.RenderState.CullMode = CullMode.None;


 De la méthode répondant à l’événement de reinisitalisation du device graphique (graphics_DeviceReset).


 Annulez les transformations que nous appliquions au triangle afin de le faire tourner en remplaçant dans la méthode Update l’instruction : 



this.effect.Parameters["World"].SetValue(Matrix.CreateRotationY((float)gameTime.TotalGameTime.TotalSeconds));


par :



this.effect.Parameters["World"].SetValue(Matrix.Identity); 


En outre, les dimensions de notre triangle sont bien plus importantes que dans le tutorial précédent. Il faut donc replacer la caméra en conséquence. Modifiez les instructions d’affectation aux matrices View et Projection de la méthode Load ainsi :


this.effect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(0, 10, -50), Vector3.Zero, Vector3.Up));
this.effect.Parameters["Projection"].SetValue(Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, this.GraphicsDevice.Viewport.AspectRatio, 0.1f, 100f));


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 20 unités.Si vous lancez le programme à ce stade vous obtenez :



Le premier triangle du cube est affiché. En fait aucun mérite à avoir, cela 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 devant
vertices[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. Schematiquement nous avons réalisé ceci :



Nous n’affichons donc plus une seule primitive (triangle) mais deux. Il faut donc aussi modifier l’instruction de dessin dans la méthode Draw :


this.graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 2);


Le “2″ à la fin de l’appel 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.  Pas de surprise, à l’execution nous obtenons :


 


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 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 devant
vertices[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 droite
vertices[ 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 droite
vertices[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 et lié à la position de la caméra. Nous sommes pile en face des deux triangles que nous avons créé au début. Les deux nouveaux triangles ne sont donc pas visibles. Modifiez la position de la caméra (dans la méthode LoadContent) de manière a la placer légèrement plus haut et à droite :


this.effect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(20, 30, 50), Vector3.Zero, Vector3.Up));


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 :


//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 devant
vertices[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 droite
vertices[ 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 droite
vertices[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 arrriere
vertices[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 arrirere
vertices[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 gauche
vertices[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 gauche
vertices[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 bas
vertices[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 bas
vertices[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 haut
vertices[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 haut
vertices[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 DrawUserPrimitive se 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 tout d’abord faut créer deux variables de type Vector3 afin de sauvegarder la taille courante (changement de taille en X, Y ou Z) et la position (déplacement sur X, Y ou Z) du cube.


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 += 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);


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. Vector3.Up/.Down.Left/.Right correspondent à des contants qui nous évitent d’écrire des “valeurs” dans notre code. Vector3.Up correspond ainsi à la valeur “new Vector3(0, 1, 0)”. Le code devient ainsi plus lisible. Les autres constantes suivent le même principe.


En ce qui concerne la rotation du code, la transformation est réalisée là aussi à l’intérieur de la méthode Update :


float fAngle = (float)gameTime.TotalGameTime.TotalSeconds;
//la transformation en elle mme
Matrix world = Matrix.CreateRotationY(fAngle) * Matrix.CreateRotationX(fAngle)
* Matrix.CreateScale(size)
* Matrix.CreateTranslation(position);


this.effect.Parameters["World"].SetValue(world);


Nous nous basons ici sur le temps écoulé depuis le lancement de l’application. La valeur est utilisé pour réaliser des rotation sur Y et X (les rotations étant cycliques prendre le temps écoulé est une façon de faire parfaite !). Celles-ci seront constantes quelque soit la vitesse de la machine qui fait fonctionner l’application. Les transformations (rotation Y, rotation X, Scale, Translation) sont multipliées ensemble pour obtenir une transformation globale qui les intègres toutes. Le resultat est donnée à la matrice World du fichier effet. Ce fichier effet étant utilisé pour afficher le cube, la transformation s’appliquera donc au cube.


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 dépassant la distance du far plane.


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 alors 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…


telecharger Vous pouvez télécharger le sample ici.   


[Soon]


Valentin Billotte


Retourner au sommaire des cours 

XNA Tutorial 4 : Les matrices

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).


 Le triangle est coupé 


 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.


telecharger Vous pouvez télécharger le sample ici.   


[Soon]


Valentin Billotte


Retourner au sommaire des cours 


 

Les MipMaps

Les mipmaps permettent une amélioration flagrante de la qualité de rendu des textures en fonction de leur distance par rapport à la position de la caméra qui filme la scène. Prenons la place d’un programme de jeu qui veut afficher un mur. Ce dernier utilisera un cube avec une texture de mur. Etant très maniaque ce programmeur va utiliser une texture de grande qualité afin d’obtenir un mur réaliste. Certes pour une vue de près l’effet voulu sera réussit, mais imaginez le gâchis pour une vue de loin : non seulement Direct3D va tenter d’afficher la forme avec une texture importante, en réduisant sa taille arbitrairement le tout pour un rendu moyen. Quelle perte de performance et de qualité !


Il aurait mieux valu, lorsque la caméra s’éloigne faire en sorte que une texture deux fois moins grande soit utilisée. Moins grande certes mais réalisée par le programmeur ou selon les désires du programmeur !


 


Pourquoi créer ses propres textures alors les filtres pouvaient aisément réduire une image. Tout simplement parce que les filtres ne sont pas intelligents : ils réalisent leur tâche bêtement sans se soucier du contenu de la texture ni de l’information qu’elle véhicule. Faites ce test : ouvrez votre logiciel de dessin, créez une image avec un texte et réduisez le de 50% avec un filtre bilinéaire.


Vous obtenez quelque chose de la sorte :


 


 
Figure 1 — En bas 50% en bilinéaire


 


L’altération de l’information est flagrante ! Aucun filtre ne peut avoir une vue globale de la texture dans la mesure où ils travaillent au niveau des pixels. Dans le même sens aucun filtre ne peut quantifier l’information véhiculée par la texture et tenter de la sauvegarder au détriment des informations moins importantes.


L’homme, lui, le peut … A lui de créer les différentes textures correspondant à cette perte d’information. Il doit déterminer à partir de quel point le message de la texture devient illisible et créer une nouvelle texture adaptée qui continue à donner le message dans une nouvelle échelle. Au processus de mipmapping de substituer la texture originelle par la nouvelle texture plus petite quand cela devient nécessaire.


 



Figure 2 — La texture réduite à la main est plus lisible.


  


1 Présentation


 


L’ensemble des textures successivement réduites forme un Mipmap. MIP correspond à « Multi in parvo » en latin, c’est-à-dire « de nombreuse chose au même endroit ». Chaque texture de cet ensemble est un niveau de MIPmap ou LOD (Level Of Detail). Chaque niveau doit avoir pour hauteur et largeur la moitié de la hauteur et largeur du niveau précédent. Le dernier niveau a bien évidemment au moins un coté égal à 1. Sachant que la taille conseillée pour une texture est de 256 pixels il y’a donc au maximum 9 LOD (256 puis 128 puis 64, 32, 16, 8, 4, 2 et 1).


 Le mipmapping est très utile dans la mesure ou il permet de préserver l’information dans le sens voulu par le développeur mais aussi en évitant à la carte graphique l’affichage de grosses textures lorsque ce n’est pas nécessaire.


Filtre trilinéaire et mipmapping


Les transitions entre les différents LOD sont souvent visibles lorsqu’on regarde au loin. Le processus de mipmapping est heureusement capable de filtrer deux niveaux de mipmap consécutif pour en calculer la moyenne de couleur. La transition devient pratiquement invisible. Il s’agit là du filtre bilinéaire, mais, dans la mesure où il porte sur une transition entre deux texture on parle de filtrage trilinéaire.


LOD bias[1]


Il arrive que pour la beauté du jeu il faille garder un LOD plus longtemps que prévu en retardant le passage à un LOD inférieur lorsque la caméra s’éloigne. C’est le LOD bias qui permet de réaliser cette opération. Si une surface s’éloigne de l’utilisateur, augmenter le LOD bias revient à retarder l’utilisation du LOD inférieur. Au contraire, en diminuant sa valeur, on passe plus rapidement aux LOD inférieurs.


 


2. Notre Sample


 


Pour mettre en évidence tout ce que nous venons d’apprendre, rien de tel qu’un bon exemple en 3D. Tout comme le code précédent, nous allons faire en sorte de pouvoir modifier toutes les propriétés liées au mipmapping par l’intermédiaire des touches F1 à F8. Un mipmap est un ensemble de texture dont la taille décroît. Il est nécessaire de créer cet ensemble. Pour cela les textures suivantes seront utilisées :


 


Figure 3—Textures de couleurs…


 


Il s’agit d’une série d’image en couleur dont la taille décroît. Les couleurs permettront d’aisément discerner les différents LOD lors des modifications effectuées par l’utilisateur dans le programme. De plus pour avoir une idée plus précise du rendu à l’écran, un autre mipmap sera utilisé, cette fois ci pas de couleur, mais un motif : un sol pavé.


 



Figure 4— …Textures de sol.


 


Pour l’affichage, un plan fixe formé de 25*25 cases sera dessiné à l’écran. De même , un petit moteur de déplacement à la manière des Quake like sera programmé dans le but de pouvoir déplacer la caméra et admirer les conséquences sur les modifications de LOD.


 


Prêt ? C’est parti !


 


3. Etude du code source 


 


 


Le fichier principal commence par la déclaration de deux structures : une structure Vertex définissant les propriétés des vertex utilisés dans le programme, et une énumération nommée Filtres qui nous permettra de donner un nom aux filtres voulus par l’utilisateur dans le programme.


 


Vient ensuite la déclaration de 4 Textures :


 


 


/// <summary>


/// Texture pour le mipmapping


/// </summary>


Texture textureMipMappingCouleur;


    


/// <summary>


/// Texture pour le mipmapping


/// </summary>


Texture textureMipMappingSolContour;


    


/// <summary>


/// Texture pour le mipmapping


/// </summary>


Texture textureMipMappingSolPur;


    


/// <summary>


/// Texture pour le mipmapping actuellement utilisée


/// </summary>


Texture texture;


 


 


Ces textures sont en fait des mipmap. Elles posséderont différents LOD. En fait une texture simple est un mipmap avec un seul LOD : le niveau 0. Nous aurons un mipmap pour l’étude sur les textures colorisées, un mipmap pour l’étude sur des textures de sol avec contour, et encore un pour les textures de sol sans contour. La dernière texture est en fait le mipmap actuellement affiché par le programme. Il pointera sur l’un des trois mipmap précédemment énumérés.


Terminons les déclarations par différentes variables de sauvegarde des paramétrages de l’utilisateur dans sa découverte du mipmapping :


 


 


/// <summary>


/// Filtres de base, on commence en linéaire


/// </summary>


Filtres           magFilterType  = Filtres.Linear;


Filtres           minFilterType  = Filtres.Linear;


Filtres           mipFilterType  = Filtres.Linear;


 


/// <summary>


/// Niveau d’anisotropy


/// </summary>


int   anisotropy    = 1;


 


/// <summary>


/// Level de détail à utiliser


/// </summary>


float mipMapLodBias = 0.0f;


 


/// <summary>


/// Inidique qu’une mise à jour doit être faite


/// </summary>


bool mettreAJour = true;


 


 


Trois variables de type Filtre pour les filtres utilisés avec la magnification, minification et le mipmap. Le niveau de l’anisotropie, la valeur du LOD bias. Un booléen enfin pour déterminer si l’utilisateur a modifié quelque chose.


 


Ce sont ChargementTexturesEtMipMap() et ChargementTexturesMipMap() qui vont initialiser les mipmap.


La première se présente ainsi :


 


 


    /// <summary>


    /// Charge les mipmap couleur, sol contour, et sol pur


    /// </summary>


     public void ChargementTexturesEtMipMap()


     {


          this.textureMipMappingCouleur = CreateTexture( device, “base.bmp”, Format.A8R8G8B8);


          this.textureMipMappingSolContour = CreateTexture( device, “scbase.bmp”, Format.A8R8G8B8);


          this.textureMipMappingSolPur = CreateTexture( device, “sbase.bmp”, Format.A8R8G8B8);


 


          ChargementTexturesMipMap(textureMipMappingCouleur, “”);


          ChargementTexturesMipMap(textureMipMappingSolContour, “sc”);


          ChargementTexturesMipMap(textureMipMappingSolPur, “s”);


 


 


          this.texture = this.textureMipMappingCouleur;


     }


 


 


 


 


Elle créé les trios textures vue précédemment avec base.bmp, scbase.bmp et sbase.bmp. Chaque texture dans le programme possède le même nom mais avec un préfixe différent :


 


 


  • Pas de préfixe pour les textures du mipmap colorisé
  • « sc » pour le mipmap sol avec contour
  • « s » pour le sol pur.

 


 


Ces trois initialisations ne servent qu’à créer la texture car en fait leur contenu sera effacé par les différents LOD du mipmap.


 


Les trois instructions suivantes chargent le mipmap de chacun d’entre elles :


 


 


ChargementTexturesMipMap(textureMipMappingCouleur, “”);


ChargementTexturesMipMap(textureMipMappingSolContour, “sc”);


ChargementTexturesMipMap(textureMipMappingSolPur, “s”);


 


 


 


La méthode ChargementTextureMipMap créée dans notre programme accepte en paramètre, la texture a charger en mipmap et le préfixe des textures à charger.


 


La méthode se termine enfin en faisant pointer la propriété texture sur le mipmap qui sera affiché au démarrage.


 


 


Analysons ChargementTexturesMipMap :


 


 


     /// <summary>


     /// Chargement des textures mipmap


     /// </summary>


     public void ChargementTexturesMipMap(Texture mipmap, string préfixe)


     {


          Texture[] textures = new Texture[6];


 


 


          // Charge nos 6 textures


          textures[0] = CreateTexture( device, préfixe +”256.bmp”, Format.A8R8G8B8);


          textures[1] = CreateTexture( device, préfixe +”128.bmp”, Format.A8R8G8B8);


          textures[2] = CreateTexture( device, préfixe +”64.bmp”, Format.A8R8G8B8);


          textures[3] = CreateTexture( device, préfixe +”32.bmp”, Format.A8R8G8B8);


          textures[4] = CreateTexture( device, préfixe +”16.bmp”, Format.A8R8G8B8);


          textures[5] = CreateTexture( device, préfixe +”8.bmp”, Format.A8R8G8B8);


    


 


          //création des surfaces pour le copier/coller


          Surface destination = null;


          Surface source  = null;


 


          int i;


 


          for( i = 0; i < 6; ++i )


          {


                //pointer sur la surface texture du LOD i


                destination = texture.GetSurfaceLevel(i);


 


                //pointer vers la surface de la texture i


                source = textures[i].GetSurfaceLevel(0);


 


                //charger dans destination la source, sans filtre


                SurfaceLoader.FromSurface(destination, source, Filter.None, 0);


          }


 


          //Libérer les textures temporaires utilisées


          for( i = 0; i < 6; ++i )


                textures[i].Dispose();


     }


 


 


 


La méthode commence par charger dans un tableau de 6 textures les six LOD du mipmap. Elle va ensuite, charger à chaque LOD du mipmap passé à la méthode les 6 textures chargées :


 


 


          for( i = 0; i < 6; ++i )


          {


                //pointer sur la surface texture du LOD i


                destination = mipmap.GetSurfaceLevel(i);


 


                //pointer vers la surface de la texture i


                source = textures[i].GetSurfaceLevel(0);


 


                //charger dans destination la source, sans filtre


                SurfaceLoader.FromSurface(destination, source, Filter.None, 0);


          }


 


 


Pour les 6 itérations, on fait pointer destination sur le LOD du mipmap. On fait pointer source sur le LOD 0 de la texture de la case du tableau lu. Chaque texture de ce tableau n’est pas un mipmap et une seule texture, il n’y a donc qu’un LOD : celui du niveau 0. A l’aide de la méthode FromSurface de la classe utilitaire SurfaceLoader on place dans destination le contenu de source. Le tout sans filtrage et avec une clé de couleur de transparence noire (la valeur 0). En gros cette boucle chaque dans le mipmap a chacun des 6 LOD, les 6 textures chargées au départ.


 


Cette opération étant effectuée, on peut libérer la mémoire prise par le tableau.


 


 


La création de mipmap est une opération relativement facile (on perd plus de temps à créer les textures des LOD !). Pourquoi s’en passer quand on voit le gain de performances et de qualité visuelle ?


 


Remarque : Pour déterminer le nombre de level d’un mipmap utilisez la propriété LevelCount de cette manière :


           int nombreLOD = mipmap.LevelCount;


 


   


Trois méthodes, travaillent sur le sampler. Elles se présentent comme ceci :


 


 


private void SetMagnificationFilter()


{ 


     if( this.magFilterType == Filtres.None )


          this.device.SetSamplerState(0, SamplerStageStates.MagFilter, (int)TextureFilter.None);


    


     if( this.magFilterType == Filtres.Point )


          this.device.SetSamplerState(0, SamplerStageStates.MagFilter, (int)TextureFilter.Point);


 


     if( this.magFilterType == Filtres.Linear )


          this.device.SetSamplerState(0, SamplerStageStates.MagFilter, (int)TextureFilter.Linear);


 


     if( this.magFilterType == Filtres.Anisotropic )


          this.device.SetSamplerState(0, SamplerStageStates.MagFilter, (int)TextureFilter.Anisotropic);


}


 


 


 


Il existe aussi 2 autres méthodes similaires SetMinificationFilter et SetMipMapFilter qui travaillent de la même manière.


 


Les modifications sont effectuées par le groupe d’instructions :


 


 


this.SetMinificationFilter();


this.SetMagnificationFilter();


this.SetMipMapFilter();


device.SetSamplerState(0, SamplerStageStates.MaxAnisotropy, anisotropy );


device.SetSamplerState(0, SamplerStageStates.MipMapLevelOfDetailBias, mipMapLodBias );


 


this.Text = “Minification = ” + this.minFilterType + ” | Magnification = ” + this.magFilterType + ” | MipMap = ” + this.mipFilterType + ” | MaxAnisotropy = ” + anisotropy + “|LOD = ” + mipMapLodBias;


 


 


Les trois appels de methodes vues précédemment, suivit par deux modifications du sampler pour mettre à jour l’anisotropie et le LOD bias.


La dernière instruction mets à jour le titre de la fenêtre pour pouvoir connaître l’état des filtres.


  


  


4. Touches de l’exécutable


  


Page Haut/Bas : Déplacement en hauteur.


Flèches gauche/droite : Tourner la tête.


Flèches haut/bas : avancer/reculer.


+/- : Regarder en haut/en bas


Touche numérique 1/2/3 : Mipmap colorisé/sol avec contour/sol pur.


F1 : modification du filtre de minification.


F2 : modification du filtre de magnification.


F3 : modification du filtre de mipmapping.


F4/F5 : modification du LOD bias.


F6/F7 : modification de la valeur de l’anisotropie.


 


5. Etude de l’exécutable


  


Le code du programme étant compris voyons ce qu’il peut nous apporter. Lancez le, l’écran au démarrage devrait être le suivant :


 


 
Figure 5—On remarque bien les plans translatés et le mipmapping


 


En se déplaçant en avant en arrière on remarque les ondes des LOD suivre et s’avancez ou reculer. De même en s’approchant du sol, les LOD avec les index les plus petit (dont les plus détaillés) apparaissent (notamment la texture rouge et bleu).


 


En hauteur, la touche F1 qui modifie le filtre de minification agit. Le filtre point effectue ainsi une opération de mauvaise qualité. La minification agit car en hauteur les différents plans du sol ont une taille inférieure à la taille de la texture. Au sol si la minification ne fait rien, au contraire la magnification agit. Même remarque : le filtre point dégrade l’affichage (dommage, c’est le plus rapide).


 



Figure 6—Point et Lineaire … le choix est aisé.


 


Jouons maintenant avec le mipmapping (touche F3). Si nous prenons le filtre None (qui desactive le mipmapping) évidemment le sol devient rouge : il n’y a plus qu’une texture dans le mipmap. Avec le filtre point, on obtient un affichage certes hideux, mais très intéressant dans la mesure où on détermine bien les différents LOD. On peut passer en mode sol pur pour discerner au mieux l’effet dans un jeu.


 



Figure 7—Le mipmapping en mode point est … à éviter.


 


Avec un filtre Linéaire ou Anisotropique les différences de LOD s’estompent. Jouons maintenant pour terminer avec le LOD bias. Il permet de retarder ou s’accélérer le passage à un autre LOD.




Figure 8 —LOD bias = 0


 


Figure 9—LOD bias = 1 : on est plus rapidement à des LOD moins détaillés


 


Figure 10—LOD bias = -3 : le LOD détaillé en face reste affiché à une distance plus élevé de la caméra.


 


 


Ce petit programme d’exemple devrait encore avoir éclaircit nombre de point difficiles à assimiler (notamment les filtres). Quelqu’un qui maîtrise bien les fonctionnalités de cet utilitaire pourra tenter de discerner les nuances des filtres plus seulement avec le mipmap colorisé.


 


 


Télécharger le Sample 


 


telecharger Vous pouvez télécharger le sample ici.




[1] Bias peut être traduit par  correction ou affinage ici..

Le multitexturing

Le multitexturing est le processus qui permet le mélange contrôlé de plusieurs textures sur une même surface 3D. Direct3D permet de mélanger jusqu’à huit étapes (ou étage/stage) correspondant à 8 textures superposées. Chacune correspond à un processus de texture blending qui détermine la couleur d’un pixel en mélangeant les couleurs de la texture de l’étape courante aux couleurs de l’étape précédente.


Quelque soit le stage sur lequel il s’effectue, le blend est une opération qui prend en entrée 8 paramètres :


  • Trois arguments pour le calcul de la couleur RGB.
  • Trois arguments pour le calcul de la composante Alpha,
  • Une opération pour calculer la couleur RGB
  • Une opération pour la composante alpha.
     

En résultat on obtient deux valeurs :


  • Une couleur RGB.
  • Une composante alpha (transparence).

Les arguments appartiennent à l’énumération TextureArgument, les opérations à TextureOperation. Ses membres les plus courants sont :


Subtract : Soustrait les données graphiques du second argument au premier argument.


SRGBA = Arg1 – Arg2


Lerp : Réalise une interpolation linéaire entre le second et le troisième argument suivant une proportion décrite dans le premier argument.


RGBA = (Arg1) * Arg2 + (1- Arg1) * Arg3.


MultiplyAdd : Réalise une multiplication de deux derniers arguments sur 3 trois donnés à laquelle on additionne le premier argument pour créer la sortie.


SRGBA = Arg1 + Arg2 *Arg3


DotProduct3 : Multiplie chaque composante RGB des deux arguments et les additionne entre eux.


SRGBA = (Arg1R * Arg2R + Arg1G * Arg2G + Arg1B * Arg2B)


BlendTextureAlpha : Réalise un mélange linéaire des composantes alpha de l’étage courant avec le résultat des étapes supérieures.


RGBA = Arg1 * (Alpha) + Arg2 * (1 – Alpha)


BlendDiffuseAlpha : Réalise un mélange linéaire entre l’étage supérieur et l’étage courant.


RGBA = Arg1 * (Alpha) + Arg2 * (1 – Alpha)


AddSmooth : Additionne les arguments 1 et 2 puis soustrait leur produit pour donner la sortie.


RGBA = Arg1 + Arg2 – Arg1 * Arg2


AddSigned2X : Additionne les texels des de l’étage supérieur avec l’étage courant pour la sortie avec une correction de 0.5 et un décalage de bits de 1 vers la gauche (multiplication par 2).


RGBA = (Arg1 + Arg2 – 0.5) << 1


AddSigned : Additionne les texels des de l’étage supérieur avec l’étage courant pour la sortie avec une correction de 0.5.


RGBA = Arg1 + Arg2 -0.5


Add : Additionne les texels des de l’étage supérieur avec l’étage courant pour la sortie. RGBA = Arg1 + Arg2


Modulate4X : Multiplie les texels des de l’étage supérieur avec l’étage courant puis décale les bits de 2 vers la gauche (provoquant une multiplication par 4) pour la sortie.


RGBA = (Arg1 * Arg2) << 2


Modulate2X :  Multiplie les texels des de l’étage supérieur avec l’étage courant puis décale les bits de 1 vers la gauche (provoquant une multiplication par 2) pour la sortie.


RGBA = (Arg1 * Arg2) << 1


Modulate : Multiplie les texels des de l’étage supérieur avec l’étage courant pour créer la sortie. RGBA = Arg1 * Arg2


SelectArg2 : Prend en compte le second argument de l’étage courant portent sur la couleur ou la composante alpha pour créer la sortie. RGBA = Arg2


SelectArg1 : Prend en compte le premier argument de l’étage courant portent sur la couleur ou la composante alpha pour créer la sortie. RGBA = Arg1


Disable : Désactive la sortie de l’étage courant et de tous les étages d’un niveau supérieur. Pour désactivier le mapping de texture, il suffit de donner ce drapeau pour l’étage 0. Les opération sur la composante alpha ne peut être désactivée lorsque les opérations sur les couleurs sont activée.


Reportez vous à l’aide du SDK pour les autres membres non explicités ici. Les membres les plus courants de TextureOperation sont :


TextureColor : Contient les couleurs de l’étage courant.


Current : Renvoie l’élément de l’étage precedent (si un seul étage dans le multitexturing, l’argument est equivalent à Diffuse).


Diffuse : Pixels dont la valeur est obtenue via une interpolation bilinéaire des sommets de la primitive en gouraud.


Reportez vous à l’aide du SDK pour les autres membres non explicités ici. Les possibilités de calculs et de mélange sont donc infinies, la difficulté de comprend les possibilités des calculs est importante. Là encore, comme pour l’usage des filtres, on peut s’en sortir facilement au départ en tâtonnant jusqu’à obtenir l’effet souhaité. Avec le temps, les mécanismes suivront. Plutôt que de s’engager dans une pléthore d’exemples pour les mettre en valeur, j’ai développé un petit programme fort utile qui permet, via une interface Windows très simple de s’amuser en testant toutes les opérations avec tous les arguments possibles pour déterminer quel résultat cela pouvait avoir à l’écran.


Une application qui simplifiait la vie
Figure 1


Cerise sur le gâteau, notre programme donne le code source à mettre dans son programme pour avoir un multitexturing paramétré. C’est une copie, en C# d’un utilitaire présent dans les précédentes versions du SDK DirectX qui a été curieusement supprimé aujourd’hui. Le gain de temps que ce logiciel procurait au développeur était faramineux : avant, il fallait sans arrêt compiler son programme après avoir modifié une étape du multitexturing pour observer le résultat à l’écran. On perdait beaucoup de temps. Avec lui, un simple click suffit pour tester.


 Le programme

Au lancement nous avons l’écran suivant :

 

...en C#
Figure 2 

 

Seules trois étages peuvent donc être paramétrées sur les 8 possibles. En fait 99% des multitexturing portent sur un nombre d’étages inférieur ou égal à 3. C’est donc bien suffisant.

 

On voit en faisant défiler le contenu du combo box ColorOP ou AlphaOP qu’il y’a 25 fonctions de mélange (énumération TextureOperation). Pour ces opérations de 1 à trois arguments doivent être fournis prenant une valeur parmi 10 choix possibles (TextureArgument). Tout ceci existe pour chaque étape du multitexturing. Les possibilités sont donc trop nombreuses pour pouvoir être toutes données dans ce tutorial. Nous ne verrons que quelques effets.

 

Pour reproduire l’effet lumineux d’un sport sur une texture, il faut charger en texture de stage 2 spolite.bmp du répertoire de l’exécutable et configurer le second stage comme ceci :

 

 Figure 3— les texels blancs du spot se superposent aux texels du mur et les eclaircissent : le mur semble éclairé !

 

En cliquant sur le bouton « View code » on obtient :

 

  

public void SetTexturesStages(Microsoft.DirectX.Direct3D.Device device)

{

     //texture stage : 0 Fichier image : env2.bmp

     /////////////////////

     device.TextureState[0].ColorOperation = TextureOperation.SelectArg1;

     device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;

 

     device.TextureState[0].AlphaOperation = TextureOperation.SelectArg1;

     device.TextureState[0].AlphaArgument1 = TextureArgument.TextureColor;

 

     device.TextureState[0].ResultArgument = TextureArgument.Current;

 

 

     //texture stage : 1 Fichier image : spotlite.bmp

     /////////////////////

     device.TextureState[1].ColorOperation = TextureOperation.Modulate;

     device.TextureState[1].ColorArgument1 = TextureArgument.TextureColor;

     device.TextureState[1].ColorArgument2 = TextureArgument.Current;

 

     device.TextureState[1].AlphaOperation = TextureOperation.Disable;

 

     device.TextureState[1].ResultArgument = TextureArgument.Current;

 

 

     //texture stage : 2 Fichier image : env3.bmp

     /////////////////////

     device.TextureState[2].ColorOperation = TextureOperation.Disable;

 

     device.TextureState[2].AlphaOperation = TextureOperation.Disable;

 

}

 

  

Il s’agit du code généré par l’application pour créer le multitexturing, visible dans l’utilitaire, à l’intérieur de nos programmes.  Nous spécifions l’étage pour lequel les arguments et opérations donnés sont destinés (ici donné sous la forme d’un index de tableau), l’opération (ColorOperation et AlphaOperation ) de type TextureOperation  et les arguments liés de type TextureArgument (voir la figure 3).

 

Pour reproduire une case de monde de Warcraft avec une portion de terre et une portion d’herbe au dessus comme ceci :

 

Trouver comme faire un tel affichage avec cette application est simple

 Figure 4

 

 On chargera en texture stage 2 :

 

On voit le résultat dynamiquement

 Figure 5 

 

  

Dans tous les cas pas d’inquiétudes face à toutes ses possibilités de calculs. Personne ne peut vraiment prétendre maîtriser toutes ces opérations ou les comprendre. Seul le temps peut permet de s’en sortir avec plus ou moins de facilité. Toujours se reporter aux exemples de code source trouvé sur Internet ou bien à l’utilitaire présenté ici pour trouver l’effet voulu.

 

Sample

 

telecharger Vous pouvez télécharger le Sample ici.

Conclusion

[Soon]

Valentin Billotte


XNA Tutorial 3 : Introduction aux vertices et effets

Retourner au sommaire des cours  


Il est maintenant temps d’afficher quelque chose à l’écran.


Le but de ce tutorial est de nous apprendre à dessiner à l’écran en 3D à l’aide de Vertices, et de commencer à assimiler la notion d’effet. Il s’agit là d’un cours primordial dans la mesure où un développeur de jeux XNA  aura toujours à faire à ces deux notions, et ce, dans n’importe quel projet qu’il réalisera. Il convient donc de bien assimiler ce que nous allons réaliser ici (où bien de me contacter le cas échéant). Nous allons réaliser ici l’affichage d’un triangle coloré.


Le vertex


 Si en 2D afficher un objet revient à “plaquer” une image à l’écran, en 3D la done est tout autre. Les objets ne sont pas dessinées à partir d’une image mais plutot a partir de vertices (un vertex, des vertices). Un vertex peut être vu comme un point dans l’espace auquel on associe des propriétés (couleur, position …). Le pipeline 3D va relier ces points pour former un objet. Le développeur a pour tache de bien placer ces points et d’indiquer au device comment les relier.


L’image suivante illustre cela. Nous voyons un tube et un cube en trois dimension. On remarque qu’ils sont tout simplement formés à partir de points qui sont reliés entre eux.



La forme géométrique la plus simple en 3D est le triangle. Trois vertices sont nécessaires pour la former. Toute forme géométrique en 3D est formée de triangles. C’est à dire d’une multitude de vertices qui, reliés, permettent de donner une apparence à certains objets. La encore, l’image ci dessus le montre très bien.


Plusieurs solutions existent pour relier les vertices entre eux afin de former des objets 3D. Chaque solution possède ses propres avantages en fonction la forme de l’objet . C’est pourquoi il convient d’utiliser le celle qui s’avère la plus adapté dans un seul but : réduire au maximum le nombre de vertices. L’association de plusieurs vertices entre eux permet à XNA de former des triangles (assimilables à des surfaces). L’association de ces surfaces permet alors de former des objets dans l’espace. XNA résonne en ternaire ; Il lui faut trois point pour former la plus petit figure géométrique possible : le triangle. 


Lorsqu’on place des points dans l’espace il est donc nécessaire de spécifier à la carte graphique la manière dont il faut relier ces points afin de créer une forme 3D. Les différentes manières possibles sont les suivantes :


Triangle List
Le mode de liaison de vertex le plus simple. Celui-ci lit les points trois par trois afin de former des triangles qu’il affiche. On comprendra donc il est nécessaire de créer un nombre de vertices multiple de trois. Les trois premiers vertices représenteront le premier triangle, les trois suivant le second triangle et ainsi de suite… C’est le mode de définition le plus simple …  mais aussi le plus coûteux.


Triangle Fan
Ce mode (comme le suivant) est plus intelligent ; il réutilise des vertices déjà utilisé pour les liaisons. On gagne ainsi de précieux octets en mémoire libérant ainsi le pipeline 3D. Dans ce mode le premier vertex est relié à tous les autres. Là aussi, les possibilités sont limités ; on ne peut faire que des formes rectangulaires et/ou cylindrique comme le montre le schéma ci-contre.


Triangle Strip
Ce troisième mode est un chouïa plus compliqué. On relie ici aussi les vertices par trois. Mais en considérant que le dernier vertex créé doit être relié aux deux précédents. Le gain de place en mémoire est très important, mais la difficulté de créer des formes complexes est multipliée.


Primitive indexée
Nous verrons dans quelques pages la création de primitives indexées. Ce mode de liaison de vertices est la solution à tous les problèmes précédemment listés pour créer des formes complexes en créant un minimum de vertices.


L’image ci-dessus tente de montrer les différences que l’on peut trouver avec les trois premier modes pour l’affichage de triangles :



a) Triangle List :
3 triangles : Vertices (0, 1, 2) puis Vertices (3, 4, 5), puis Vertices (6 7, 8) : 9 vertices pour afficher 3 triangles.
b) Triangle Fan :
3 triangles : Vertices (0, 1, 2) puis Vertices (0, 2, 3), puis Vertices (0, 3, 4) : 5 vertices pour afficher 3 triangles.
c) Triangle Strip :
6 triangles : Vertices (0, 1, 2) puis Vertices (1, 2, 3), puis Vertices (2, 3, 4), puis Vertices (3, 4, 5), puis Vertices (4, 5, 6), puis Vertices (5, 6, 7) : 8 vertices pour afficher 6 triangles.


Au vu de cette image on remarque bien les différences entre les types de reliaisons. D’autres reliaisons sont possible :


Point qui ne fait aucune liaison et laisse les points tels quels (très utile pour les effets de particules),


LineList qui relies les points deux à deux (utile pour dessiner des lignes)


LineStrip qui relie un point à son prédécesseur.


En C#, c’est l’énumération PrimitiveType qui contient les différents types d’énumérations possibles.


Rappelons que nous avons pour but d’afficher un triangle dans ce tutorial, il faudra donc créer 3 vertices, leur donner une position et, au moment de l’affichage spécifier la meilleure méthode de liaison.


Les effets


La première grande différence entre l’API DirectX et le Framework XNA auquel on est confronté vient de l’obligation d’associer un effet à toute opération de rendu. Si vous n’avez jamais développé à l’aide de DirectX vous devez vous demander ce qu’est un effet.


Nous venons de voir que les objets sont présentés sous la formes de triangles (surface élémentaire). Nous spécifions par l’intermédiaire de notre code la position des points du triangle et d’autres informations comme la couleur de ces points. Un effet accompagne ces informations en indiquant à la carte graphique comment afficher ces triangles. Il peut être vu comme un ensemble de technique qui peuvent être appliquées (ou non) au rendu d’une surface. Par techniques on peut entendre instructions données à la carte graphique pour lui “expliquer” de quel manière l’objet, situé dans un espace 3D, doit être rendu sur l’écran 2D du joueur. 


Il s’agit là, il ne faut pas se le cacher, d’une technique encore trop complexe pour être abordée à ce stade de notre apprentissage. Nous utiliserons donc un fichier effet basique fourni par Microsoft et nous ne l’aborderons que succintement.


 Modification du code source


Nous allons continuer sur notre lancée et modifier notre précédent programme nommé “PremierProjet”. Nous appliquerons au projet de cet article nommé à juste titre “SecondProjet” les modifications et evolutions apportées dans le précédent tutorial.


La première étape consiste à ajouter un fichier effet au projet et à le gérer dans le code.


Pour ce faire cliquez droit sur le répertoire Content du projet (destiné à gérer les ressources du jeu). Selectionnez Ajouter -> Nouvel Element.



Dans la fenêtre qui s’ouvre alors selectionnez Effect File et donnez le nom Effect.fx au fichier à ajouter :



Validez. Le fichier se trouve alors ajouté au répertoire Content.


Si vous double cliquez dessus vous pourrez voir le code suivant :


float4x4 World;
float4x4 View;
float4x4 Projection;
 
// TODO: add effect parameters here.
 
struct VertexShaderInput
{
    float4 Position : POSITION0;
 
    // TODO: add input channels such as texture
    // coordinates and vertex colors here.
};
 
struct VertexShaderOutput
{
    float4 Position : POSITION0;
    // TODO: add vertex shader outputs such as colors and texture
    // coordinates here. These values will automatically be interpolated
    // over the triangle, and provided as input to your pixel shader.
};
 
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
 
    float4 worldPosition = mul(input.Position, World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);
 
    // TODO: add your vertex shader code here.
 
    return output;
}
 
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    // TODO: add your pixel shader code here.
 
    return float4(1, 0, 0, 1);
}
 

technique Technique1
{
    pass Pass1
    {
        // TODO: set renderstates here.
 
        VertexShader = compile vs_1_1 VertexShaderFunction();
        PixelShader = compile ps_1_1 PixelShaderFunction();
    }
}
 
 
Nous reviendrons sur sa signification plus tard. Pour l’heure nous allons charger l’effet en mémoire à l’intérieur d’un object C# pour pouvoir l’utiliser. Ajoutez pour celà le membre suivant à la classe Game1 :


Effect effect;


Ajoutez l’instruction suivante à la méthode Load de la classe Game1 :


this.effect = Content.Load<Effect>(“Effect”);


Cette instruction va utiliser la méthode Load de l’objet content pour charger la ressource nommée “Effect”. La ressource renvoyée sera de type Effect et affectée au handle effect. Pour rappel, il est spécifié par défaut dans le constructeur que le répertoire où trouver les ressources est Content :


Content.RootDirectory = “Content”;


L’objet Content, utilisé dans l’instruction précédente correspond au Content Manager, c’est à dire un objet dont le rôle est de gérer les ressources associées au projet. Sa méthode Load permet de charger une ressource dans un objet dont les membres vous nous permettre d’exploiter la dite ressource dans notre code. Ici on peut voir que notre fichier d’effet est manipulé maintenant par l’intermédiaire d’une instance de la classe Effect.


La string “Effect” est un identifier de ressource qui a été automatiquement associé à notre effet lorsque nous l’avons ajouté au projet. Selectionnez le fichier Effect.fx et appuyez sur F4. La fenêtre de propriété apparait alors :



Vous pouvez y voir que l’une des propriété se nomme AssetName et correspond à cette string. Vous êtes bien evidemment libre de modifier cette propriété pour lui donner la valeur de votre choix. Toutefois il est préférable de laisser le Xna Game Studio et Visual Studio effectuer ces choix à votre place.


Modifiez ensuite la méthode Draw pour lui ajouter de nouvelles instructions :


this.effect.CurrentTechnique = effect.Techniques["Technique1"];
this
.effect.Begin();
foreach (EffectPass pass in this
.effect.CurrentTechnique.Passes)
{
    pass.Begin();

    pass.End();
}
this.effect.End();


La première instruction sélectionne une technique nommée “Technique1″ parmi celles contenues dans l’effet. Nous utilisons cet identifier tel quel en tant qu’index dans le tableau de technique. Notons juste qu’il s’agit là d’un code très mauvais ; il aurait été préférable d’utiliser une constante de type int. Trouver un objet à l’aide d’un indexeur de type string est toujours plus long qu’avec un indexeur de type int (la comparaison de deux int est plus rapide que deux strings). Or perdre du temps dans la méthode Draw est vraiment la dernière chose à faire. Il y’a une explication à celà : je trouve le code plus lisible ainsi pour les débutants :).


Alors qu’est ce qu’une technique ? Un fichier effet peut contenir une ou plusieurs technique qui s’apparentent à un ensemble d’affichage possible pour une forme 3D.L’appel à la méthode Begin indique que l’effet de la technique “Technique1″ commence à partir de ce point et s’applique à l’affichage de tous les objets jusqu’à ce que la méthode End soit rencontrée. Entre ceux deux méthodes tout affichage sera soumi à la technique “Technique1″.


Enfin, on a jamais vu quelqu’un peindre un chef d’oeuvre d’un seul coup de pinceau ; Appliquer un effet peut demander plusieurs couches ou “passes”. Là encore une méthode Begin et End indiquent la zone de code où l’effet de la passe courante est pris en compte. C’est entre ces deux méthodes que nous effectuerons l’affichage de notre triangle. Vous pouvez au passage revenir sur le code contenu dans le fichier Effect.fx. Vous trouverez à la fin de celui-ci :


technique Technique1
{
    pass Pass1
    {
        // TODO: set renderstates here.
 
        VertexShader = compile vs_1_1 VertexShaderFunction();
        PixelShader = compile ps_1_1 PixelShaderFunction();
    }
}


On y trouve la définition de la technique  et on peut voir qu’elle se compose d’une passe redirigeant vers deux fonctions. Nous aborderons le HLSL plus tard, ne vous inquietez pas si le code de ce fichier vous parait obscur.


Nous sommes justement maintenant prets à afficher notre première forme 3D !


Première forme 3D !


Nous avons vu au tout début de ce cours qu’un vertex était un point dans l’espace auquel on associait un ensemble d’information liées à son affichage dans l’espace. Le but de ce tutorial est d’afficher un triangle coloré à l’écran. Chaque point dans l’espace (vertex) doit donc être associé à une position et un couleur donc. Cerise sur le gateau nous n’aurons pas besoin de créer une structure de Vertex pour encapsuler ces deux informations : XNA en possède une qui répond à nos besoins : la structure VertexPositionColor.


Un triangle est composé de trois points, il faut donc créer un tableau de trois cases de type VertexPositionColor. Ajoutez la déclaration suivante en début de classe :


 VertexPositionColor[] vertices;


Puis, dans la méthode Initialize nous allons créer trois vertices et l’affecter aux trois cases de ce tableau.


vertices = new VertexPositionColor[3];
vertices[2].Position =
new Vector3
(-0.5f, -0.5f, 0f);
vertices[2].Color =
Color
.Red;
vertices[1].Position =
new Vector3
(0, 0.5f, 0f);
vertices[1].Color =
Color
.Green;
vertices[0].Position =
new Vector3
(0.5f, -0.5f, 0f);
vertices[0].Color = Color.Yellow;


Ici le code est plutot simple : trois vertices sont créés. Chacun deux se voit affecter une position et une couleur. Nous sommes dans l’espace 3D. Une position correspond donc à trois composantes X, Y, Z (repère euclidien orthonormé dans l’espace). Le constructeur de Vector3 accepte en paramètre ces trois composantes.Reste maintenant l’affichage dudit triangle. La méthode Draw possède deux nouvelles instructions.


this.graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(this.graphics.GraphicsDevice, VertexPositionColor.VertexElements);
this.graphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 1);
 


Dans la première ins truction nous précisions la structure des vertices qui sont passés au device graphique. la structure VertexPositionColor  contient un membre statique nommé VertexElements qui précise que la structure contient une information de position, et une information de couleur. De cette manière, la carte graphique optimise la gestion mémoire en vue de l’arrivée dans le pipeline d’un flux de vertices contenant ce type de données.


Dans la seconde instruction justement, nous passons à la carte graphique une instruction de dessin en précisant, la manière dont on va relier les vertices, le flux de vertices, l’index du premier vertex à lire dans le flux et le nombre de primitives (triangles) à afficher. Ici nous utilisons la technique TriangleList, nous donnons un flux de trois vertices (le tableau que nous venons d’initialiser), nous lisons le flux à partir du premier vertex, et nous n’affichons qu’un seul triangle.


Si vous lancez le code vous obtenez la fenêtre :



A priori on s’attendait à avoir un triangle affiché et non un écran bleu. Il est en effet nécessairede spécifier la façon dont on veut afficher la scène 3D à l’écran.


L’affichage d’un objet 3D à l’écran se réalise généralement par l’intermédiaire de trois matrices. Une matrice est une « boite magique » mathématique qui permet de réaliser des équations linéaires avec peu de traitements. Elles sont utilisées en 3D pour réaliser de nombreux calculs fastidieux de manière extremement rapide et efficaces. Ces trois matrices sont les suivantes :


1.   La première matrice est la matrice World, elle spécifie où placer un objet dans un repère 3D (translation), son orientation (rotation), et sa taille (homothetie).


2.   La matrice View précise la façon dont est « filmé » la scène. On parle alors de caméra.


3.   La troisième matrice, nommée Projection donne les caractéristiques de la caméra, comme l’angle d’ouverture, la taille de l’écran, etc.


Ajoutez ces trois instructions à la méthode Draw, juste avant le premier appel à Begin :

this.effect.Parameters["World"].SetValue(Matrix.Identity);
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, 100f));

La propriété Parameters de la classe Effect permet d’accéder aux variables présentes dans le fichier effet et de leur affecter une valeur ou bien de la lire. Le fichier effet expose, comme on va le voir bientôt trois variables, World, View et Projection. Nous leur donnons ici, à chacune, la valeur d’une matrice.


Pour la matrice World, nous indiquons qu’il n’y aura pas de transformation, la valeur Identité de la matrice (matrice n’effectuant aucun calcul) est est ainsi passé à cette variable.


Pour la matrice View nous utilisons la méthode CreateLookAt de la classe Matrix afin de préciser la position de la caméra (ici la caméra est simplement reculée de deux unités en profondeur), l’endroit vers lequel on regarde (ici on regarde vers l’origine (0, 0, 0) ) et l’angle de la caméra avec l’horizontale (ici angle droit).


Enfin la matrice Projection spécifie un angle d’ouverture de 45° (PI/4), le ratio (largeur de l’écran sur sa hauteur), et enfin deux valeurs importantes : la distance à la caméra à partir de la quelle on peut voir un objet, et la distance au delà de laquelle un objet n’est plus visible (front plane et far plane). Là encore une méthode de la classe Matrix nommée CreatePerspectiveFieldOfView permet de créer une matrice facilement. Le ratio permet de garder un aspect cohérent pour notre scène en cours d’affichage quelque soit la taille de la zone d’affichage.


Ces trois matrices sont multipliées entre elles afin de passer d’une scène 3D à un écran 2D. C’est la carte graphique qui s’occupe de celà. On peut donc voir ces calculs comme le moyen de passer d’une position dans l’espace (X, Y Z) avec des propriétés de couleur, de texturing, d’illumination à une position à l’écran (X, Y) d’un pixel coloré.


 Si vous relancez le programme maintenant vous obtenez l’écran suivant :



Mieux, mais toujours insuffisant ! A priori nous devions avoir un triangle avec plusieurs couleurs, or là, seul le rouge est visible. Il va nous falloir, pour aboutir à ce résultat, étudier et comprendre le code du fichier effet afin de pouvoir le modifier.


Courage ! Ouvrez donc maintenant le fichier effect.fx en double cliquant dessus. Vous devriez tomber sur le code précédemment affiché dans cet article. A première vue le code semble assez proche du language C. C’est enfait du HLSL (High Level Shader Language). Il est donc assez “lisible”, mais nous allons tout de même l’expliciter. Etudions le.


Au tout début du fichier se trouve les déclarations de variables globales du fichier effet. On y retrouve justement les trois déclarations de matrices que nous avons affecté précédemment :


float4x4 World;
float4x4 View;
float4x4 Projection;


On affecte des valeurs ou on récupere la valeur d’une de ces variables à l’aide de la propriété Parameters de l’objet effect :


this.effect.Parameters["View"].SetValue(Matrix.CreateLookAt(new Vector3(0, 0, -2), Vector3.Zero, Vector3.Up));


Passons à la suite du fichier. On trouve après ces trois variables la déclaration de deux structures :


struct VertexShaderInput
{
    float4 Position : POSITION0;
 
    // TODO: add input channels such as texture
    // coordinates and vertex colors here.
};
 
struct VertexShaderOutput
{
    float4 Position : POSITION0;
    // TODO: add vertex shader outputs such as colors and texture
    // coordinates here. These values will automatically be interpolated
    // over the triangle, and provided as input to your pixel shader.
};


Généralement, un effet réalise l’affichage d’un objet auquel il est lié en deux étapes. La première étape, consiste à manipuler les vertices de l’objet à leur appliquer une suite de transformations et de calculs afin de pouvoir passer de la représentation 3D de l’objet à un écran 2D. Cette étape correspond à ce qu’on appelle le vertex shader. Dans une seconde étape, nommée Pixel Shader chaque triangle de la scène est convertie en pixels et chaque pixels est coloré à sa juste valeur. A priori, si notre problème vient de la couleur du triangle, c’est sur le pixel shader qu’il va faloir travailler. A quoi servent donc les deux structures ci-dessus ? Elle permettent de réaliser plusieurs types d’échanges de données :


  • D’abord entre l’extérieur (notre programme C#) vers le premier shader de l’effet (le vertex shader)
  • Puis entre le vertex shader et le pixel shader
  • Et enfin entre le pixel shader et la carte graphique pour l’affichage final.

La première structure, nommée à juste titre VertexShaderInput permet à l’effet de recevoir tous les vertices que nous avons passé à l’aide de la méthode DrawUserPrimitives. Il ne recevra ici qu’une information de Position :


struct VertexShaderInput
{
    float4 Position : POSITION0;
 
    // TODO: add input channels such as texture
    // coordinates and vertex colors here.
};
 


On remarque donc ici un premier problème : il n’y a pas de membre pour stocker la couleur du vertex ! La seconde struture nommée VertexShaderOutput est la structure de donnée échangée entre le Vertex Shader et le Pixel Shader.


Un shader dans un effet peut donc être vu comme une fonction qui, renvoie ou reçoit une structure (comme celles que nous venons de décrire) effectue des traitement sur les données de celle-ci, et renvoie une autre structure de données contenant le resultat de ces traitements.


Etudions la première fonction : 
 
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
 
    float4 worldPosition = mul(input.Position, World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);
 
    // TODO: add your vertex shader code here.
 
    return output;
}


Elle prend en entrée une valeur de type VertexShaderInput et renvoie une valeur de type VertexShaderOutput. La première instruction de la fonction vise à créer une variable nommée ouput de type VertexShaderOutput. Elle multiplie ensuite à l’aide la méthode mul la position du vertex courant (reçu directement depuis le flux envoyé par la méthode Draw) par la matrice Word afin de placer le vertex dans le monde 3D. Elle multiplie ensuite le resultat de cette opération par la matrice View afin d’avoir la représentation du vertex dans l’espace depuis notre “camera”. Enfin une multiplication est réalisée avec la matrice Projection pour passer du monde 3D à l’écran 2D. La valeur obtenue est affecté à un des membre de la variable ouput qui est elle-même renvoyée.


La seconde méthode se présente comme ceci :


float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    // TODO: add your pixel shader code here.
 
    return float4(1, 0, 0, 1);
}


Elle prend une valeur de type VertexShaderOuput (reçue depuis la fonction précédente) et renvoie un tableau de 4 flottants. Nous reviendrons sur la signification exacte du type float4 plus tard. Quoi qu’il en soit à ce stade nous pouvons dire que les 4 flottants correspondent aux composantes RGBA (rouge, vert, bleu, alpha) de la couleur à attribuer au pixel. 1 correspondant à la valeur maximale de la composante et 0 à la minimale. Ici la couleur renvoyée est donc le rouge. On remarque donc ici un second problème : tout pixel sera rouge !


Enfin le fichier se termine par la définition de la technique :


technique Technique1
{
    pass Pass1
    {
        // TODO: set renderstates here.
 
        VertexShader = compile vs_1_1 VertexShaderFunction();
        PixelShader = compile ps_1_1 PixelShaderFunction();
    }
}


Le fichier ne contient donc qu’une seule technique nommée Technique1. Celle-ci réalise son affichage en une seul passe nommée Pass1.


La passe se caractérise par un vertex shader et un pixel shader. On retrouve un pointeur vers nos deux méthodes. Les mots clés “compile vs_1_1″  et “compile ps_1_1″ indiquent que les shaders doivent être compilés dans la version 1.1 du language.


Pas d’inquiétudes s’il reste des zones d’ombre, nous avons parcouru le fichier très rapidement sans nous attader sur certaines spécificités syntaxiques. Revenons à notre problème : le triangle rouge.
Premier soucis, la structure en entrée ne contient qu’une information de Position. Or la structure que nous manipulons dans le programme C# contient Position et Couleur. Modifiez la structure ainsi :


struct VertexShaderInput
{
    float4 Position : POSITION0;
    float4 Color        : COLOR0;
 
    // TODO: add input channels such as texture
    // coordinates and vertex colors here.
};

Nous avons rajouté un nouveau champs nommé Color de type COLOR0. Réalisez la même opération avec la structure de sortie :


struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float4 Color        : COLOR0;
 
    // TODO: add vertex shader outputs such as colors and texture
    // coordinates here. These values will automatically be interpolated
    // over the triangle, and provided as input to your pixel shader.
};

Reste maintenant à modifier les shaders. Commencez par le vertex shader. Nous avons juste ici à ajouter une ligne d’affection pour récupérer la couleur du vertex entrant et la passer à la valeur envoyée en sortie de méthode :


VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
 
    float4 worldPosition = mul(input.Position, World);
    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);
    output.Color = input.Color;
     // TODO: add your vertex shader code here.
 
    return output;
}

Reste le pixel shader. Nous n’avons plus à renvoyer la couleur rouge mais plutôt à renvoyer celle de la valeur en entrée qui est la sortie de la méthode précédente :


float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    // TODO: add your pixel shader code here.
 
    return input.Color;
}


Relancez le programme. Nous obtenons enfin la sortie voulue :




Conclusion


De nombreuses choses auraient pu être vues ici : les effets plus en profondeurs, un peu de mathématiques 3D, les différentes méthodes d’affichage du Device… Tout cela sera en fait assimilé au fil des tutoriaux. Pas d’inquiétudes à avoir donc. Pour l’heure nous pouvons être fier : nous savons créer un effet, l’appliquer. Nous sommes capable de créer une forme et de l’afficher (avec couleurs s’il vous plait !). Comme exercice en attendant de lire le prochain tutorial, essayez d’afficher un carré et une maison. Essayez de même de reproduire le carré avec les méthodes TriangleFan et TriangleStrip.


Pas d’inquiétude à avoir si il vous reste de nombreuses zones d’ombre sur les effets et le code qu’ils contiennent, nous apprendrons au cours d’un long tutorial à maitriser le HLSL.


 Vous pouvez télécharger le sample et les correction des exercices ici. 


Retourner au sommaire des cours 


 


 


 


 

XNA Tutorial 1 : Installation et Configuration de l’environnement

Retourner au sommaire des cours 


Avant que nous commençions nos tutoriaux sur XNA il est évidemment nécessaire de configurer son environement par l’intermédiaire d’une série d’application à installer. Ces applications seront les principaux outils mis à notre disposition par Microsoft pour développer en XNA. Trois installations seront nécessaires dans l’ordre qui suit :


  1. Visual Studio 2008
  2. Les derniers runtimes DirectX
  3. Le XNA Game Studio

Après ces installations nous lancerons notre toute première application XNA sous Windows (nous pourrions viser la Xbox 360 ou le Zune mais nous commencerons simple).


 


Installation de Visual Studio 2008 


Toutes les version de Visual Studio 2008, de l’Express, la plus simple, à la Team Système la plus complète en passant par la profesionnal peuvent être utilisées pour développer des applications à l’aide du Xna. Pour ma part je conseille fortement l’installation de la version Express. Même si vous avez déjà une version 2008 supérieure installée., L’Express édition est largement suffisante pour le Xna, bien plus rapide à charger que les autre éditions et plus légère. En résumé, elle n’a que des avantages.


Cette version de Visual Studio a tout d’”Express”. Même l’installation est rapide. Deux possibilités pour la télécharger, soit à partir de ce site (officiel), soit à partir de la MSDN (pour les abonnés). Si vous êtes derrière un proxy et n’êtes pas abonnés MSDN : bon courage.


Ca change de Visual Studio Team System ! (pour ceux qui connaissent...)


 


Installation des Runtimes DirectX


L’installation du SDK DirectX n’est pas nécessaire. Le runtime (sur lequel fonctionne XNA) est suffisant. Vous pouvez le télécharger ici


Néanmoins, le SDK offre au développer et de manière gratuite un ensemble d’outils, de cours et d’exemples très complets et très utiles au développeur travaillant sur le Xna Framework. Il est à mon avis important d’avoir ces outils. 


 


Installation du Xna Game Studio


Pour Game Studio Express enfin, cliquez ici (inutile de télécharger le Framework, il est inclu dans le XGS).


Courage bientot nous lancerons notre premier jeu XNA !


 Test d’un premier jeu


 Passons maintenant aux choses sérieuses. Ouvrez Visual Studio 2008 via le menu demarrer.


Cliquez sur “Fichier”, Puis sur “Nouveau projet”. Choisissez dans la fenêtre qui s’ouvre, à gauche tout d’abord, les types de projets Xna Game Studio 3.0 et droite ensuite le starter kit nommé “Platformer” comme le montre l’image ci-dessous :


 


Validez. Visual Studio Charge alors un projet clé en main : un starter kit qui ne demande qu’à etre compilé. On remarque qu’il existe une copie du projet pour toutes les plateformes visées par le Xna Game Studio (Windows, Xbox 360 et Zune). Nous nous contenterons pour ce premier contact de la version Windows.


Un Starter Kit est essentiellement un modèle de projet amélioré qui peut être partagé avec d’autres membres de la communauté. Un Starter Kit comprend, entre autres ressources, des exemples de code compilables et une documentation visant à vous aider dans l’utilisation de nouveaux outils et l’apprentissage de nouvelles techniques de programmation tout en générant des applications utiles et réelles.



Lancez la compilation par l’intermédiaire de la commande CTRL + F5. Un jeu apparait alors :


Un jeu apparait


 


Conclusion


 Vous êtes maintenant prêts pour apprendre à développer en XNA. Ce sera justement le sujet de notre prochain tutorial.


[Soon]


 Valentin Billotte


Retourner au sommaire des cours 

Tutoriaux XNA : Introduction

 

Ceci est une traduction de la FAQ sur le site officiel. J’ai traduit ça suite à de nombreuses questions que j’ai reçu. Si vous avez encore besoin de réponses, cliquez ici

XNA Foire Aux Questions

Questions fréquemment posée sur le Framework XNA Framework et le XNA Game Studio Express

Q: Qu’est ce que le XNA Game Studio Express?
R: XNA Game Studio Express est une nouvelle solution de développement de jeu destinée principalement aux étudiants, passionnés et développeurs de jeux indépendants. XNA Game Studio Express fonctionne avec Visual C# Express 2005 et permet aux développeurs de créer des jeux à la fois pour Windows et pour la XBOX 360. XNA Game Studio Express inclu :

  • Le Framework est un ensemble de bibliotèques de développement qui permet aux développeurs d’être plus productifs dans la création de jeux pour Windows et Xbox 360.
  • Le Framework  XNA Framework Content Pipeline est un ensemble d’outils qui permet aux développeurs incorporer facilement des contenus 3D à l’intérieur de leurs jeux.
  • XNA Game Studio Express contient une documentation complète, des cas pratiques et des starter kits qui mettent en évidence les bonnes pratique de développement.
  • XNA Game Studio Express cohabite sans problèmes avec les autres versions de Visual Studio.
  • XNA Game Studio Express est disponible ici. Il supporte à la fois le développement Windows et Xbox 360.

Q: XNA est l’acronyme de ?
R: XNA’s Not Acronymed

Q: Combien coûte XNA Game Studio Express ? Il y’a t’il une différence de prix entre le développement pour Windows and et celui pour Xbox 360 ?

R: Visual C# Express, XNA Game Studio Express pour Windows sont gratuits. Neanmoins, pour développement,débugger et ou joue sous Xbox 360, vous devez souscrire un abonnement au XNA Creators Club directement à partir du Xbox Live Marketplace. Deux abonnements sont disponibles : $99 par an ou $49 pour quatre mois.

Q: Il y a t’il des pré requis pour faire fonctionner  XNA Game Studio Express?
R: Oui. Vous devez télécharger et installer Visual C# Express et les dernières updates des runtimes DirectX (le SDK complet n’est pas obligatoire). De même, vous devez avoir une carte vidéo compatible Direct3D 9.0 supportant le modèle Shader 1.1. Nous recommendons une carte supportant le modèle 2.0 sachant qu’une partie des starter kits l’utilisent. Referez vous au site du constructeur de votre carte vidéo pour plus d’informations à ce propos.

Q: Dois je avoir un disque dur sur ma console Xbox 360 pour faire tourner des jeux XNA-based ?
R: Oui. L’environnement XNA pour la console Xbox 360 nécessite la présence d’un disque dur sur votre console.

Q: Quelles versions de Windows XNA Game Studio supporte t’il ? J’utilise Windows Vista, puis je faire fonctionner XNA Game Studio Express?
R: A ce jour  XNA Game Studio Express a uniquement été testé sous Windows XP SP2. Windows Vista et Windows Server 2003 ne sont pas encore supportées, sachant que nous sommes en train de travailler pour offrir un support pour vista.

Q: Comment puis je partager mes jeux Xbox 360 fait avec le XNA Game Studio Express avec d’autres utilisateurs de la Xbox 360 ?
R: Pour partager vos jeux Xbox 360 avec vos amis, quatres points doivent être respectés:

  • Vous devez etre connecté au Xbox Live et avoir un abonnement valide au XNA Creators Club .
  • Le destinataire doit avoir téléchargé le runtime du XNA Framework pour sa console Xbox 360.
  • Le destinataire doit avoir le XNA Game Studio Express installé sur son PC.
  • Le projet de jeu incluant tout le code et les ressources doit être partagé avec le destinataire. Ce dernier compile alors et déploie le jeu sur sa console.

Nous travaillons activement sur un moyen de distribuer vos jeu encore plus simple.

Q: Puis je stocker mes jeux XNA Game Studio Express game sur ma carte mémoire ou sur un CD/DVD pour le partager avec un ami ?
R: Non. Les jeux développés avec le XNA Game Studio Express ne peuvent pas être partagés de cette manière actuellement.

Q: Comment puis je contacter l’équipe XNA à propos d’un Bug que j’ai trouvé où d’une fonctionnalité dont j’ai beson ? 
R: Vous pouvez nous soumettre les buts et les demandes de fonctionnalités par l’intermédiaire du site Microsoft Connect. Enregistrez vous à l’aide de votre Windows Live ID, cliquez sur “Available Connections” et selectionnez “XNA” dans la liste. Si vous êtes déjà connecté sous XNA allez directement sur le site https://connect.microsoft.com/feedback/Search.aspx?SiteID=226.

Q: Où puis je en apprendre plus à propos de XNA Game Studio?
R: Nous allons continuer de faire vivre la communauté de developpement de jeux par l’intermédiaire de d’événements ou via le site XNA (http://msdn.com/xna). Vous pouvez participer à la communauté par l’intermédiaire du site http://msdn.com/xna/forums. De plus vous pouvez envoyer un email à xna@microsoft.com si vous avez des question, nous ne pouvons pas garantir une réponse à chacun des mails que nous recevrons.

Q: Serais je capable de développer un jeu et le faire fonctionner à la fois sous Windows et sur Xbox 360 ?
R: Vous serez capable de compiler une seule fois un jeu à la fois pour la plateforme Windows et la plateforme Xbox 360. Pour l’heure créez simplement un projet séparament pour chaque plateforme et compilez les. Notre but au final est réduire au maximum la quantité de code à écrire spécifiquement pour une plateforme.

Q: Puis je utiliser le XNA Game Studio Express ou la framework XNA pour développer un jeu commercial sur Xbox 360 ?
A: XNA Game Studio Express vous permet de développer des jeux Windows et Xbox 360 très simplement. Ces jeux sont limités à un usage non commercial pour ceux destinés à la console Xbox 360. Par contre, XNA Game Studio Express  peut être utilisé pour créer des jeux commerciaux destinés à Windows. Nous planifions de sortir une version pofessionnelle de XNA Game Studio Professional  l’année prochaine qui permettra aux développeurs de créer un jeu commercial pour Xbox 360.

Q: Comment pouvez vous débugger un jeu XNA tournant sur une Xbox 360?
A: Le debuggage sur une console est possible par l’intermédiaire d’une connection à distance entre la console et un Windows sur lequel tourne un XNA Game Studio Express.

Q: Le framework XNA permet il l’utilisation du Xbox Live?
A: La release initiale du framework XNA sur la console Xbox 360 n’offre aucune fonctionnalité pour le jeu réseau. Nous sommes conscient qu’il s’agit là d’un domaine particulièrement interessant pour les développeurs de jeu et travaillons activement sur une solution disponible dès la prochaine release.

Q: Par quel moyen le framework XNA supporte il l’audio?
R: Le framwork XNA fourni un support audio par l’intermédiaire de managed XACT.

Q: Est ce que le framework XNA supporte XINPUT ou DirectInput?
R: Le framework XNA fourni un support vers les devices d’entrés par l’intermédiaire de managed XINPUT.

Q: Quel est le contrat de support autour de XNA Game Studio?
R: Les utilisateurs XNA Game Studio Express peuvent trouver un support à propos du framework XNA et du XNA Game Studio Express vias les forums accessibles sur le site http://msdn.com/xna/forums.

Q: Dois je avoir installé le SDK DirectX pour utiliser les outils audio XACT?
R: Non, XACT est désormais inclu dans le téléchargement de XNA Game Studio Express. Par contre, si vous voulez utiliser d’autres outils comme PIX,vous devrez télécharger le dernier DirectX SDK.

Q: Quelle est la différence entre XNA Game Studio Express et le Framework XNA ?
R: XNA Game Studio Express est un ensemble d’outils basés sur Visual C# Express 2005. Le framework XNA est inclu avec le XNA Game Studio Express et est un ensemble de bibliothèques managées (.NET) basées sur le Framework 2.0.Vous pouvez bien entendu télécharger les composants runtime du framework XNA séparement dans le but, par exemple de patager vos jeux XNA avec vos amis.

Q: Est ce que le Framework XNA tourne en émulation sur une Xbox 360?
R: Le framework XNA utilise une implémentation native de la Compact Framework .Net 2.0 CLR sur la console Xbox 360.

Q: Est ce que le framework XNA est disponible sur les plateformes non Microsoft ?
R: A ce jour, le framework XNA est uniquement disponible sous Windows et Xbox 360.

Q: Le code managé du XNA Framework n’est il pas interprété et donc forcément lent ?
R: Non, il n’est pas interprété. L’intermediate language (IL) est compilé en code natif lors de son premier lancement (compilation juste à temps ou just-in-time (JIT) ). Ceci permet des optimisation de code natif propre au hardware sur lequel l’application est exécutée (architectures PC et Xbox 360).

Q: Qu’est ce que le framework XNA exactement ?
A: Le framwork XNA permet aux développeurs de créer des jeux modernes en utilisant le langage C# et un ensemble complet de bibliothèques de développement. Il utilise son propre pipeline de contenu rendant l’accès aux contenus 3D, 2D, sonores … simple et rapide. Il fourni une API de haut niveau indépendante des plateformes Windows et 360, éliminant par ce fait le coût et l’effort à fournir pour porter les jeux entre ces deux plateformes.

Q: Quelle est la différence avec le framework .NET ?
A: Le Framework .Net est la principale API de développement pour réaliser des application Windows. Le frameworkd XNA a été conçu pour profiter de ce framework en ajoutant un ensemble de fonctionnalités propre au développement de jeux. 

Q: Avons nous accès au réseau ou à Xbox Live par l’intermédiaire du framework XNA ?
A: Sous windows vous pouvez Utiliser le namespace System.Net ou n’importe quelle bibliothèque réseau. Sous Xbox 360 il n’y a aucun support réseau à ce jour. Le jeu multiplayer local est toutefois supporté.

Q: Puis je créer des application autres que des jeux (comme un Media Center ou un lecteur de vidéos) à l’aide de XNA Game Studio Express?
A: Sous Windows cela est possible, mais la release initiale sous Xbox 360 est spécifiquement tournée vers la création de jeu. Il s’agit là d’un domaine sur lequel nous nous penchons activement en nous référant sur les retours de la communauté à propos des applications que les membres désires écrire pour leur console.

Q: Est ce que le Framework XNA est inclu dans le SDK DirectX ?
A: Pour l’heure, le framework XNA pour Windows est inclu dans le XNA Game Studio Express et non dans le SDK DirectX. Le framework XNA pour Xbox 360 est uniquement disponible depuis la Xbox Live Marketplace une fois qu’un abonnement XNA Creators Club a été acheté.

Q: Retrouvera t’on les fonctionnalités de D3DX dans le framework XNA ?
A: Nous avons implémenté une large partie des fonctionnalités de Direct3D X à l’intérieur du framework XNA. Nous travaillons actuellement à ajouter d’autres fonctionnalités elles aussis issues de D3DX. Nous prennons en compte vos retours à ce propos par l’intermédiaire des newsgroupe et des forums ainsi que par mails à xna@microsoft.com.

Q: Est ce que le Framework XNA remplace ma version du Framework .net ?
A: Non. Le Framework XNA ne remplace pas et n’interfère pas avec le Framework installé sur votre machine. Le Framework XNA sous Windows est un ensemble de bilibothèques de classes construites sur le .NET Framework 2.0.

Q: Est ce que le Framework XNA supporte les devices Windows et Pocket PC ?
A: Le Framework XNA ne supporte pas aujourd’hui les devices Windows Mobile et Pocket PC, mais suivant les retours des différents utilisateurs du framework nous pourrions être amené à l’etendre dans le futur. Nous sommes conscient que le développement de jeux mobiles est un secteur en pleine expension que nous voulons supporter.

Q: Est ce que le XNA Game Studio Express est accessible à tous ? Planifiez vous une traduction locale ?
R: XNA Game Studio Express est accessible à tous n’importe où via le site http://msdn.com/xna. L’abonnement XNA Game Studio Express Creators Club pour la Xbox 360 est disponible dans la majeure partie des pays où la console Xbox 360 et où l’on a accès au réseau Xbox Live connectivity. La version actuelle des outils du XNA Game Studio Express  est accessible uniquement en anglais.

Q: Quelle est la différence entre le XNA Game Studio Express et le XNA Game Studio Professional?
R: XNA Game Studio Express et XNA Game Studio Pro qui ciblent respectivement les développeurs de jeux non-professionnels et les professionnels. Les deux produits sont intégrés à Microsoft Visual Studio. XNA Game Studio Express est plus destinés aux amateurs ou aux groupes de passionnés de la programmation de jeu. Il a été conçu spécifiquement dans cette optique. XNA Game Studio Professional incluera des fonctionnalités supplémentaires néccessaires pour les développeurs de jeux progressionnels voulant créer des jeux commerciaux.

Q: Pour quoi le Framework XNA ne supporte t’il pas mon outil de création favoris ?
R: Nous estimons avoir choisis les formats de fichiers les plus robutes pour alimenter le content pipeline afin de vous permettre de travailler avec le plus grand nombre d’outils possible. Avec le temps viendra d’autre supports de formats.

Q: Qu’est ce que le langage C# ?
R: C# est un langage moderne, orienté objet conçu spécfiquement pour améliorer la productivité des développeurs. C# est utilisé par des millions de développeurs informatique à travers le monde. Il exploite toutes les fonctionnalités du .Net Framework pour la réalisation d’application basées sur le framework .Net, le Compact Framework et bien sûr framework XNA.

Q:Quel est la part du C# dans l’industrie du jeu ?
R: La grande majorité des studios de développement de jeux reconnait dans le C# un bénéfice indéniable de productivité et l’utilisent déjà pour la création d’outils internent. Il y’a pourtant peu de grands jeux écrits dans ce langage sous Windows. Ce constat intervient avant l’arrivée du Framework XNA et son approche indépendante de la plateforme (Windows et Xbox 360). C’est pourtquoi nous croyons que le framework XNA représente une très grande opportunités pour les studios de développement de jeux.

Tutorial Managed DirectX/Direct3D : Les lumières

Les Lumières et les matériaux

Les lumières et les matériaux constituent les deux éléments principaux de tout jeu en 3D pour créer une ambiance. Sans eux, un jeu est morne, terne sans saveur. Ils sont la clé de voûte pour tout jeu misant particulièrement sur l’ambiance.


Figure 1 — Tomb Raider : Ici l’ambiance fait tout


Ce tutorial aborde tout ce qui tourne autour des lumières et des matériaux sous DirectX. Nous allons créer comme programme d’exemple un système astronomique avec comme source lumineuse le soleil, une comète, et un interrupteur pour allumer le tout ! Pour illustrer de manière plus complète les matériaux, un autre exemple (beaucoup plus simple) sera réalisé pour mettre en valeur les différent type de réflexion possible face à une source lumineuse. 


1 Et la lumière fut …

Plusieurs types de lumières peuvent être créées sous DirectX pour créer un monde plus réaliste. Malheureusement toute effet lumineux créé par lui ne sera jamais aussi réaliste que celui du monde réel. Un lumière est soumise à plusieurs contraintes : l’atténuation avec la distance (une bougie ne peut être vue a 1 kilomètre), la réflexion, un objet illuminé peut être lui-même source d’illumination ‘regardez comme la lune éclaire la nuit). De même un objet qui réfléchit la lumière peut atténuation sa valeur avant de la renvoyer. Tous les objets que nous côtoyons ne renvoient pas la lumière de la même manière, un manteau de velours noir absorbera toute source lumineuse, une porcelaine blanche vernie, en renverra une grande partie. DirectX ne peut donc pas prendre tous ces paramètres en compte au risque de créer des applications d’une lenteur toute rédhibitoire mais il arrive toutefois, grâce à d’excellentes approximations à créer des univers lumineux tout à fait réalistes.

Jusqu’ici les programmes d’exemple ne géraient absolument pas la lumière et étaient parfaitement éclairés sous tous les angles. En fait avec l’instruction :

         dev.RenderState.Lighting = false;


Dans les programmes précédents, tout élément de la scène était éclairé parfaitement, sans nuances dans les même proportions. Lorsque cette propriété vaut true, on demande à Direct3D de nous laisser la gestion du module d’illumination.  On peut ainsi prendre la gestion ou refuser la gestion avant l’affichage de certains objets dans la méthode Render.

Remarque : Faite le test suivant : donnez true à cette instruction dans les programmes 3D précédent. Al’affichage tous les objets sont noirs : ne connaissant pas la gestion des lumières, nous n’avons créé aucune source lumineuse.


 
1.1 Qu’est ce qu’une lumière pour Direct3D ?


  


Une lumière pour Direct3D est un ensemble de propriétés que le programmeur peut modifier. Tous les types de lumières ne les utilisent pas forcément tous.


 


 


Position


Position dans l’espace de la source de lumière.


 


Direction


Direction vers laquelle la lumière est émise.


 


Portée (Range)


Distance maximum à partir de la source lumineuse que la lumière peut parcourir. Tout objet dont la distance à la source est supérieure à cette distance ne sera pas éclairé par la source.


 


Atténuation


Indique la variation de luminosité sur la distance. La lumière peut s’éteindre brusquement passé la distance Range ou bien diminuer progressivement, ou bien encore s’accentuer


 


Lumière diffuse


C’est la couleur principale émise par une lumière. La lumière diffuse est une lumière qui a été dispersée, mais elle a toujours une direction par opposition à la lumière ambiante qui n’en a pas.


 


Lumière ambiante


C’est la couleur de la lumière ambiante. La lumière ambiante est lumière générale de fond. La lumière ambiante a été tellement dispersée qu’elle n’a aucune direction ou source. Elle produit la même intensité lumineuse en tout point de la scène.


 


Lumière spéculaire


C’est la couleur de la lumière spéculaire qui est émise par la lumière. La lumière spéculaire est l’opposé de la lumière diffuse. La lumière spéculaire n’est pas dispersée du tout, vous peut employer la lumière spéculaire pour créer des points culminants sur vos objets.

Toutes ces caractéristiques peuvent être paramétrées via les propriétés d’un objet de type Light.


 


1.2 Quels types de lumières ?


  


Il y a quatre types de lumières qui peuvent être créée dans une scène. Chaque type utilise une partie des propriétés définies dans le point précédent.


Toutes les lumières ajoutent des frais généraux processeurs à votre application. La lumière ambiante est la plus simple à mettre en place, suivie des lumières directionnelles, puis les lumières ponctuelles et enfin les lumières spot.


Pensez à ceci quand vous décidez quelles lumières à employer dans votre application.


  


Lumière ambiante


Cette lumière n’a ni position ni direction. L’intensité qu’elle génère est constante en tout point de la scène.


 


dev.RenderState.Ambient = Color.Green;


 


 


Ici la lumière ambiante donnée est verte.


  


Lumière ponctuelle


Il s’agit là de la même source de lumière qu’une ampoule. Il y’a certes une source, l’ampoule, mais pas de direction : la lumière est émise dans tous les sens. Un exemple d’une lumière de point est une ampoule. Il a également les attributs de couleur, de gamme et d’atténuation qui peuvent être placés. Figue 7.1 ci-dessous, expositions comment la lumière est émise d’une lumière de point.


Une lumière ponctuelle se paramètre grâce à une position, une direction, une atténuation, une portée, une couleur et une couleur réfléchie.


  



Figure 2


 


Lumière Directionnelle


Les lumières directionnelles ont la direction et colorent mais ne disposent d’aucun source.  Un peu comme la lumière du soleil (s’il n’y avait pas le soleil de visite). Grâce à ce type de lumière, tous les objets dans la scène reçoivent la lumière de la même direction. Les lumières directionnelles n’ont pas des attributs de gamme ou d’atténuation. Figue 7.2 ci-dessous, expositions comment la lumière est émise d’une lumière directionnelle.


Une lumière directionnelle se paramètre grâce à une direction, une couleur et une couleur réfléchie.


 



Figure 3


 


Spot / Projecteurs


Pas besoin de décrire cette lumière, son nom la définit. On voit dans la vie réel ce genre de source lumineuse sur les réverbères, ou les spots de théâtres qui, en haut d’une scène, éclairent l’acteur.


Les projecteurs se paramètrent grâce à une position, direction, couleur, couleur de réflexion, l’atténuation et portée. Pour un projecteur vous pouvez définir en plus un cône intérieur et externe par l’intermédiaire de deux angles. Una variable nommée FallOff indique alors avec quelle intensité la lumière va tendre de 0 depuis le bord du cône interne jusqu’au bord du cône externe. Le cône interne est la lumière puissante sans atténuations


L’angle du cône intérieur est connu comme thêta et l’angle du cône externe est connu comme phi.


 


Figure 17.4


 


2 Matériaux


 


L’utilisation d’une source de lumière n’a aucune raison d’être si le point qui se trouve illuminé sur un objet n’est pas doté de propriétés qui décrivent son comportement face à la lumière. Cet ensemble de propriété est nommé matériau (material).


 


2.1 Qu’est ce qu’un matériau?


  


Un matériau décrit comment la lumière est réfléchie sur objet (polygone). Vous pouvez indiquer la quantité de lumière réfléchie, faire briller l’objet ou le rendre mat ou encore donner une couleur.


Ces arrangements sont énumérés ci-dessous :


  


La réflexion diffuse


Quantité de lumière diffuse que l’objet réfléchira. C’est une valeur de couleur. Il est possible d’indiquer que l’objet réfléchira seulement la lumière diffuse du rouge. Ceci fera paraître l’objet en rouge.


  


La réflexion ambiante


Quantité de lumière ambiante que l’objet réfléchira. C’est une valeur de couleur qui permet d’indiquer que l’objet réfléchit ou non la lumière ambiante. Il est ainsi possible de cacher l’objet à moins qu’il reçoive un autre type de lumière telle que la lumière diffuse.


  


La réflexion spéculaire et sa puissance


Quantité de lumière spéculaire qui est réfléchie. Vous pouvez employer la réflexion et les puissances spéculaires pour créer les points spéculaires qui feront briller l’objet.


  


Émission


Possibilité de rendre un objet émetteur de lumière. Si l’objet semble émettre de la lumière il n’en émet pas réellement ;  les autres objets dans la scène ne seront pas affectés par cet arrangement.


 


3 La Normale


  


La normale en un point est un vecteur de norme 1 dont les coordonnées quantifient son orientation dans l’espace 3D.Cette notion étonnante (orienter un point ?!) permet de calculer la capacité de ce point à absorber la lumière, suivant l’angle d’arrivée de celle-ci sur le point.


Etudions le schéma suivant :


 

Figure 5 —Une surface, un point, une normale, une lumière et un angle


 


 


L’angle a que fait le rayon lumineux L avec la normale N de cette surface permet de calculer l’intensité lumineux en ce point. L’intensité lumineuse décroît lorsque a croît.


 



Figure 6 —Modulation de l’intensité résultante suivant l’angle a.


 


 


C’est le même principe que lorsqu’on est face à un soleil couchant ou se levant : nos yeux peuvent le regarder sans protections. Au zénith, il veut mieux éviter.


  


4 Premier exemple : Lumière Spot, Directionnel et Point


  


Toutes les notions liées à la gestion de la lumière ayant été acquises un exemple s’impose pour mettre en application sous DirectX ce que nous venons d’apprendre.


   


4.1 Quid exemplum ?


  


Quel meilleur exemple pour parler de lumière que de recréer une partie du système solaire?


 


Les cinq premières planètes et la lune (mercure, vénus, terre, mars, jupiter) ainsi que le soleil.


Nous ferons partir une lumière directionnelle et/ou lumière point du soleil pour comparer. Un spot sortira de la terre pour éclairer toutes planètes se trouvant à proximité.


Bien entendu, comme d’habitude, plusieurs touches clavier permettront de modifier divers paramètres dans le jeu.


Ouvrez le programme d’exemple intitulé « Direct3D Lumières ». Au lancement du programme nous avons l’écran suivant :


 



Figure 7 —Vers l’infini et au dela !


Rien qu’à voir cet écran, on comprend mieux le terme « ambiance » employé dans l’introduction de ce chapitre. On se croirait presque dans l’espace ! Il ne manque plus que les étoiles (nous verrons cela avec les particules à la fin de cet ouvrage).


 


Une terre et 4 autres planètes apparaissent gravitant autour du soleil. Une lune peut être aperçue autour de la terre.


Toutes les planètes sont éclairées par le soleil. Seule une partie de leur surface est donc éclairée (tout comme la lune).


 


Remarque : Le programme ne respecte pas scrupuleusement les dimensions et distances réelles. Que les puristes pardonnent cet exemple modifié pour simplifier la vue. Une simple visite sur les nombreux sites astrologie permettra de corriger le programme. [1]. Néanmoins il respect les degrés d’obliquité, les temps de révolution et les temps de rotations.


  


Le programme fonctionnera principalement sur les matrices et les calculs qui en découlent. Chaque rotation autour d’un axe de planète, chaque rotation autour d’une planète, chaque translation, obliquité, demandera une matrice et un calcul ceci multiplié par le nombre de matrices.


 


Dans les nouveautés nous aurons ici :


  


·        Une gestion de lumières.


·        Une gestion de matériaux.


·        Une nouvelle classe pour la forme 3D sphérique


·        La classe cube modifié pour pouvoir être associée à un matériau.


 


4.2 Les matrices pour mettre en mouvement


  


Avant de regarder comment les lumières sont gérées, il nous faut commencer par la gestion des objets graphiques.


Il existe dans le code source plus d’une trentaine de matrices. Nous étudierons seulement celles qui donnent un mouvement à la terre. La gestion des matrices a été simplifiée au maximum pour rendre le programme simple à lire (que les puristes me pardonnent :) ).


 


La déclaration se fait en tout début de fichier :


 


 


      Matrix matriceTerre, matriceDéplacementTerre, matriceTerrePivot, matriceRotationTerreAutourDeSonAxe, matriceRotationTerreAutourSoleil, matriceTerreNuages;


 


La matrice nommée matriceTerre est la résultante de tous les calculs à effectuer pour donner un mouvement réaliste à notre planète. Les matrices suivantes servent à calculer (dans l’ordre) : la distance de la terre par rapport au soleil, l’ubiquité de la terre (elle penche), la rotation de la terre autour de son axe, la révolution qu’elle effectue autour du soleil et, enfin une matrice pour la masse nuageuse qui entoure la planète.


Les calculs sont réalisés très simplement dans SetupMatrice :


  


 


//Calculs de différentes échelles de temps


float tempsJour = -this.vitesseTemps;


float tempsAnnée = tempsJour * 365.0f;


float tempsMois = tempsAnnée / 12.0f;


 


 


//////////////////////////////////////////////////////////


//mise en place des matrices de transformation pour la terre


matriceRotationTerreAutourDeSonAxe = Matrix.RotationY(Environment.TickCount/tempsJour);//une rotation par jour


matriceRotationTerreAutourSoleil = Matrix.RotationY(Environment.TickCount/tempsAnnée);//365 rotation par an


matriceDéplacementTerre = Matrix.Translation(20.0f,0.0f,0.0f);//km de distance du soleil


matriceTerrePivot = Matrix.RotationYawPitchRoll(0.0f,0.0f, 23.44f* (float)Math.PI/180);//obliquité de l’axe de rotation


matriceTerre = Matrix.Scaling(1f, 1f, 1f)*matriceRotationTerreAutourDeSonAxe*matriceTerrePivot;//création d’une seule matrice pour tous ces calculs


matriceTerre *= matriceDéplacementTerre;


matriceTerre *= matriceRotationTerreAutourSoleil;


 


 


Pour plus de clarté nous allons énumérer instruction par instruction ce qui est fait :


Les trois premières sauvegarde pour une utilisation ultérieure des temps précis sur lesquels se baseront les calculs futurs.


Le premier calcul de matrice réalise une rotation d’une durée d’un jour (rotation autour de l’axe de la terre). Le second calcul effectue ue autre rotation mais pour le temps d’une année. Une translation de 20 en abscisse par rapport à l’origine est effectuée. L’ubiquité est sauvegardée dans une nouvelle matrice. Nous avons à ce stade 4 matrices initialisées décrivant chacune un mouvement précis de la terre. La dernère matrice nommée matriceTerre va être calculée à partir d’elles. Un scalling est réalisé : la taille de la terre fait 1 en standard ici multiplié par la rotation autour de son axe et l’ubiquité. Le résultat est multiplié dans l’instruction suivante par la distance au soleil et enfin dernière phase, prise en compte de la rotation de la terre.


  


Remarque : on voit bien ici que l’ordre des multiplications de matricesdoit être scrupuleusement respecté : si nous avions fait une translation de matriceDéplacementTerre après la dernière rotation au lieu d’avant, nous aurions eu une terre qui tournerai plus vite que la normale sur elle-même et qui n’effectuerait pas de révolution autour du soleil. Dans ce code par contre, la seconde rotation est effectuée après la translation, c’est donc un cercle d’une distance de 20 que la terre parcours !


 


 Chaque matrice finale de chaque planète, satellite ou enveloppe nuageuse est affectée à la matrice World avant l’affichage de la forme correspondante dans Render :


 


 


//afficher la terre


device.Transform.World = matriceTerre;


this.sphereTerre.Render(device);


   


4.3 Gérer les matériaux


  


Autre étape importante : la gestion des matériaux. Pour une bonne luminosité, donner une matériau à sa forme éclairée est une bonne chose ! Leur gestion est enfantine. Nous avons une classe Cube et Sphere qui acceptent un materiau en entrée dans le contructeur :


  


                       


ColorValue rgbaDiffuse  = new ColorValue(0, 0, 0, 0);


ColorValue rgbaAmbient  = new ColorValue(0, 0, 0, 0);


ColorValue rgbaSpecular = new ColorValue(0, 0, 0, 0);


ColorValue rgbaEmissive = new ColorValue(0,0, 0, 0);


            


this.DéfinirMaterial(rgbaDiffuse, rgbaAmbient, rgbaSpecular, rgbaEmissive, 200);


 


 


DéfinirMaterial se présente ainsi dans Cube et Sphere :


 


 


public void DéfinirMaterial(Color rgbaDiffuse, Color rgbaAmbient, Color rgbaSpecular, Color rgbaEmissive, float rPower)


{


            this.material.Diffuse = rgbaDiffuse;


            this.material.Ambient = rgbaAmbient;


 


            this.material.Specular = rgbaSpecular;


            this.material.SpecularSharpness = rPower;


 


            this.material.Emissive = rgbaEmissive;


}


 


  


Rien de compliqué si on a lu le point 17.2 de ce chapitre.
 


4.4 Créer des normales


  


Avant dernière étape avant les lumières, la gestion des normales permet de rendre visible un objet. Ajouter une normale à une surface / vertex est une opération compliquée si l’on souhaite créer un vecteur spécial. Dans le cas contraire, c’est une opération simple.


 


Il faut d’abord modifier le format de vertices pour ajouter les normales :


 


 


public struct SphereVertex


{


     public float X, Y, Z;


 


     public float Nx, Ny, Nz;


 


     public int Couleur;


 


     public float tu, tv;


 


     public const VertexFormats Format = VertexFormats.Position | VertexFormats.Normal | VertexFormats.Diffuse | VertexFormats.Texture1;


};


 


  


Les trois flottants ajoutés (Nx, Ny, Nz) définit par le drapeau VertexFormats.Normal spécifient un format de vertices acceptant un vecteur normal.


L’initialisation se fait dans la méthode qui rempli de vertex buffer (OnCreateVertexBuffer) dans la classe Cube ou Sphere:


 


 


Vector3 vNormal;


 


vNormal.X = x0;


vNormal.Y = y0;


vNormal.Z = z0;


   


vNormal = Vector3.Normalize(vNormal);


 


 


C’est la méthode Normalize de Vector3 qui créé une normale dans le point donné de coordonnées (x0, y0, z0).


  


4.5 Gérer les lumières


  


Tout est maintenant prêt pour la gestion des lumières !


La première étape est de supprimer la lumière artificielle qui éclaire toute la scène.


Deux aspects graphiques sont aussi ajoutés via render stage :


 


 


dev.RenderState.Lighting = true;


 


 


Suit alors, dans Render la création des lumières.


 


 


 


if (this.typeLumière == TypeLumière.Directionnelle)


{


      this.device.Lights[0].Diffuse= Color.FromArgb(255,255, 255);


      this.device.Lights[0].Type =LightType.Directional;


      this.device.Lights[0].Direction = Vector3.TransformCoordinate(Vector3.Empty,  Matrix.Translation(15.0f,0.0f,0.0f)*matriceRotationTerreAutourSoleil);


}


else if (this.typeLumière == TypeLumière.Ponctuelle)


{


      this.device.Lights[0].Diffuse= Color.FromArgb(255,255, 255);


      this.device.Lights[0].Type =LightType.Point;


      this.device.Lights[0].Range = this.portéeLumière;


}


else


{


      this.device.Lights[0].Diffuse= Color.FromArgb(255,0, 0);


      this.device.Lights[0].Type =LightType.Spot;


      this.device.Lights[0].Direction = Vector3.TransformCoordinate(Vector3.Empty,  Matrix.Translation(15.0f,0.0f,0.0f)*matriceRotationTerreAutourSoleil);


      this.device.Lights[0].Falloff = 0.0f;


      this.device.Lights[0].InnerConeAngle = 0.025f*tailleCone;


      this.device.Lights[0].OuterConeAngle = 0.05f*tailleCone;


}


this.device.Lights[0].Position = Vector3.Empty;


this.device.Lights[0].Attenuation0 = 1.0f;


this.device.Lights[0].Attenuation1 = 0.0f;


this.device.Lights[0].Attenuation2 = 0.0f;


this.device.Lights[0].Enabled = true;


 


 


 


Généralement la création de lumière se fait une fois, mais dans notre cas elle peut avoir à être mise à jour à chaque nouvelle frame, son emplacement dans Render se trouve donc nécessaire.


TypeLumière est une énumération que nous avons créé nous même pour définir le choix de l’utilisateur quand à l’éclairement de la scène.


Lights est un tableau de Light appartenant à l’objet device dont la taille est infinie. Nous utilisons ici l’index 0 mais nous aurions pu utiliser 400. Null besoin d’initialiser un objet Light. Il faut juste valider la lumière paramétrée avec un Enable = true comme pour la dernière instruction. Il est possible dans le programme d’utiliser trois types de lumière : directionnel, ponctuel et Spot. Ponctuel est la lumière standard du soleil qui éclaire dans toutes les directions à partir d’un point source. Directionnel va au contraire donner un sens à la lumière : la droite passant par l’origine et par le centre de la terre. Toutes les planètes dans ce cas se verront éclairée dans le sens soleil-terre. Enfin Spot va diriger la lumière vers une planète prise (ici la terre). L’utilisateur pourra chaner la taille du cone du Spot pour n’éclairer qu’une partie de la terre, toute la terre ou une vaste zone de l’espace. Un if else est donc obligatoire pour paramétrer différemment ces trois lumières. Pour la directionnel une direction est donnée :


 


 


Matrix.Translation(15.0f,0.0f,0.0f)*matriceRotationTerreAutourSoleil


 


 


C’est à dire la position de la terre,  L’expression :


 


 


Vector3.Empty


est le vecteur origine (0, 0, 0). Vector.TransformCoordinate transforme un vecteur dans l’espace en se basant sur le calcul matriciel passé en second argument.


Dans le else suivant, point de direction mais une portée pour la lumière directionnelle : définie par l’utilisateur à l’aide du clavier.


Enfin le dernier else pour le Spot définit le fallOff, l’innerCone, l’outerCone[2].


Suit un paramétrage commun pour la position et l’atténuation. Les trois valeurs d’atténuation présentes ici (0, 1 et 2) sont des facteurs différents pour une atténuation :


 


 


Attenuation0

Facteur d’atténuation linéaire.

Attenuation1

Facteur d’atténuation au carré.

Attenuation2

Facteur d’atténuation Exponentiel.


 


  


La propriété Enable active la lumière.


  


4.6 La ou la lumière n’éclaire rien …


  


Bien des points dans cet exemple peuvent sembler encore obscurs. Voyons les en détails.


 


Effet sur les planètes?


  


Deux beaux effets viennent couronner ce programme d’aide. La terre est entourée d’une atmosphère, le soleil semble se consumer. Ces deux effets sont réalisés à l’aide de l’alphablending[3].


La terre est entourée d’une sphere légèrement plus grosse qu’elle sur laquelle est plaquée la texture suivante :


  



 


Lorsque la forme sphérique est affichée avec le blending suivant :


  


this.device.RenderState.AlphaBlendEnable = true;


this.device.RenderState.SourceBlend = Blend.SourceAlpha;


this.device.RenderState.DestinationBlend = Blend.One;


 


Le noir ajouté aux couleurs de la terre disparaît et le blanc efface les couleurs de la terre, les couleurs se marient donc parfaitement


La même opération est réalisée pour le soleil, mais avec deux sphères. L’opération de blending est légèrement différente.


  


this.device.RenderState.AlphaBlendEnable = true;


this.device.RenderState.DestinationBlend = Blend.DestinationColor;


this.device.RenderState.SourceBlend = Blend.SourceColor;


  


Ombres?


  


Les rayons de Direct3D sont tellement puissants qu’ils traversent les objets ! Trève de plaisanterie, le module d’illumination traite les points qu’on lui adresse sans jamais se référer aux formes auxquels ils appartiennent. Si une forme est cachée derrière une forme éclairée, elle sera elle aussi éclairée. La gestion des ombres incombe donc au programmeur …


 


La lune peut être ainsi vue éclairée lorsqu’elle se trouve derrière la terre


 



Figure 17.8 —Vieux dicton : Lune éclairée à la novembre, terre transparente !


 


  


De même on peut d’apercevoir que la terre dans sa partie sombre est visible parceque les nuages de son atmosphère ne sont pas cachées :


 


 


 Figure 17.9


 


L’alpha blending donne pour pixels colorés en sortie le résultat d’une opération qui prend en compte les texels de la texture de la sphère de nuage et ceux de la surface de la sphère terre. Dans la mesure où la sphère terre est noire car non touchée par la lumière, et que la couleur noire est l’élément neutre dans le blending exécuté ici, on obtient la restitution parfaite de la texture nuage… Dommage mais facilement réparable. « Tatillonnez » vous trouverez.


 


 Fil de fer ?


 


Le mode fil permet l’affichage d’une forme 3D sous la forme d’un grillage complexe. Afficher les modèles dans ce mode est simple et ne demande qu’une instruction :


  


this.device.RenderState.FillMode = FillMode.WireFrame;


  


L’instruction permettant un affichage classique étant


  


this.device.RenderState.FillMode = FillMode.Solid;


 


Dither ?


Méthode pour afficher des données graphiques avec une palette de couleurs limitée. Chaque Pixel de l’image source est étalé  (habituellement une zone de 2×2) sur l’image de destination.


A distance, l’oeil mélange les pixels dans une couleur qui offre plus de nuances que la palette originale. La technique a comme conséquence un meilleur aspect visuel que la suppression arbitraire de pixels.


Pour l’activer :


  


dev.RenderState.DitherEnable = true;


  


4.7 Touches de l’exécutable


   


Page Haut/Bas : Déplacement en hauteur.


Flèches haut/bas : avancer/reculer.


+/- : Vitesse temporelle du système


F1 : Modification de la couleur de fond.


F2 : Annuler/ajouter les lumières.


F3 : Prendre place sur la lune pour observer la terre.


F4/F5 : affichage en fil de fer ou en mode solide.


F6 : Une lumière directionnelle dans le sens soleil/terre  ou  lumière ponctuelle avec pour centre le soleil


F7/F8 : Modifier le matériau de la terre (lumière spéculaire ou non)


F9/F10 : augmenter la portée de la lumière ou la diminuer de 1 (si lumière ponctuelle)


F11/F12 : diminuer ou augmenter la taille du cône de la lumière en mode Spot


 


5 Second exemple : Matériaux


  


Dernier exemple du chapitre portant sur les matériaux. Le programme sur les matrices faisant tournoyer des cubes va être repris. Une lumière ponctuelle sera utilisée pour les éclairer. L’aspect intéressant résidera dans le fait que chaque cube aura un matériau propre qui lui donnera un aspect différent face à une source de lumière :


  


Le code source de cet exemple (intitulé Direct3D Lumières (2) ) s’avère fort simple. Quatre sphères créées avec une forme rudimentaire, 4 calculs de matrices pour les faire tournoyer, une lumière ponctuelle créée et, enfin 3 matériaux ajoutés au trois premières sphères.


La première sphère à gauche sera mat et renverra une lumière douce.


 


sphere1.material.Diffuse = Color.FromArgb(255, 128, 64, 255);


  


Seule la couleur diffuse est donc précisée.


La troisième sphère à droite sera brillante.


 


sphere2.material.Diffuse = Color.FromArgb(0, 255, 255, 0);


sphere2.material.Ambient = Color.FromArgb(128, 255, 128, 255);


sphere2.material.Specular = Color.FromArgb(128, 255, 128, 255);


  


Une lumière diffuse évidemment mais aussi une lumière ambiante (réfléchie) et une lumière spéculaire (brillante).


La troisième sphère en haut sera réfractant et semblera être une source lumineuse en renvoyant une vive lumière.


 


sphere3.material.Diffuse = Color.FromArgb(128, 128, 128, 255);


sphere3.material.Ambient = Color.FromArgb(255, 255, 255, 255);


sphere3.material.Emissive = Color.FromArgb(24, 24, 24, 255);


sphere3.material.Specular = Color.FromArgb(255, 255, 255, 255);


  


Ici une lumière spéculaire est définie pour rendre l’objet lumineux.


La dernière sphère enfin sera éclairée complètement afin de pouvoir faire une comparaison.


  


6 Conclusion


Les deux exemples de ce tutorial l’ont bien montré : les lumières sont une composante primordiale de tout jeu. Elles interviennent principalement dans l’ambiance du jeu mais aussi dans son réalisme au même titre que les textures. Leur utilisation étant triviale rien ne saurait empêcher quiconque de les utiliser dans ses productions ! Dans tous les cas toujours bien étudier le sens même de tous les objets créés dans une scène afin de leur octroyer un matériau propre à sa finalité.


 


telecharger Vous pouvez télécharger les samples ici.




[1] Notamment le site http://www.ens-lyon.fr/Planet-Terre/Infosciences/Planetologie/Description/Articles/planetcompar.html.

[2] Voir la figure 4 pour comprendre l’utilité de ces 3 paramètres.

[3] Voir le post …

Free X : Click here !

 ___________________________________________________________________________

 ___________________________________________________________________________

 ___________________________________________________________________________

 ___________________________________________________________________________


ET PAF ! Encore un ! 


 [6] On arrête de révasser, de trainer et de cliquer n’importe où et on se remet à l’apprentissage de XNA SVP ! [6]


Pour du X gratuit cliquez ici néanmoins.


 ___________________________________________________________________________


 ___________________________________________________________________________


 ___________________________________________________________________________


 ___________________________________________________________________________