Le dessin de primitives avec Managed DirectX en C#

Ce tutoriel vous apprendra à afficher des primitives avec Managed Direct X en C#.

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Un objet affiché par DirectX aussi complexe soit-il est toujours composé de triangles. Le triangle sera donc la forme géométrique à la base de tout rendu de scène 3D. Nous allons voir dans ce tutoriel comment générer un triangle puis comment l'afficher.

II. Les Triangles

Comme nous venons de l'apprendre, le triangle est la forme géométrique de base utilisée par DirectX et les cartes graphiques 3D. Chaque sommet d'un triangle est appelé "Vertex" (Vertices au pluriel) dans le jargon de la programmation 3D, et donc "Sommet" pour sa version française. J'utiliserais les termes anglais dans ce tutoriel dans la mesure ou c'est la dénomination utilisée par DirectX. Nous allons donc commencer par nous pencher sur l'initialisation de ces vertices.

II-A. Les Vertices

Il existe beaucoup de types de vertices mais je vais essayer d'être le plus concis possible pour vous les présenter. Tout d'abord vous avez deux grands types :

  • les vertices de type "Transformed" ;
  • les vertices de type "Position".

Les vertices de type "Transformed" sont des vertices dont la position est définie par leurs coordonnées à l'écran. Par exemple si nous déclarons un vertice "Transformed" à la position x:100 y:100 z:1 le vertice sera effectivement placé à cette position dans la fenêtre de rendu. Remarquez que la valeur attribuée à la composante z n'a que peut d'intérêt pour l'instant... Sachez quand même que lors de l'utilisation de ce type de vertice une valeur 0 correspond à un vertice sur le devant de la scène et une valeur de 1 à un vertice tout au fond de la scène. Quand nous initialiserons donc un vertice "Transformed" nous indiquerons par la même ses coordonnées à l'écran.

Les vertices de type "Position" sont des vertices dont la position est définie par leurs coordonnées dans la scène 3D. Si nous reprenons notre exemple de tout à l'heure, le vertice "Position" ne sera certainement pas placé aux coordonnées écran x :100 y:100 dans la mesure ou sa position variera avec le point de vue que nous aurons dans la scène. Pour faire simple si nous avions une caméra, le vertice "Position" bougerait à l'écran lors des mouvements de caméra au contraire du vertice "Transformed".

Nous utiliserons donc dans cette première partie du tutoriel des vertices "Transformed". Voyons maintenant les autres options offertes par les différents types de vertices. Pour les vertices, autant les "Transformed" que les "Position", il est possible d'appliquer un certain nombre de paramètres de rendu. Voyons ces possibilités :

  • "Transformed" et "PositionOnly" : rien de particulier le vertice indique seulement une position ;
  • "TransformedColored" et "PositionColored" : on ajoute à la position une information de couleur ;
  • "TransformedTextured" et "PositionTextured" : on ajoute à la position une information de texture ;
  • "TransformedColoredTextured" et "PositionColoredTextured" : on ajoute à la position une information de couleur et de texture.

Voilà pour cette brève présentation des vertices... passons maintenant à leur initialisation

II-B. Initialisation des Vertices

Comme dit précédemment, nous allons utiliser des vertices de type "Transformed" auquel nous voulons ajouter une information de couleur. Nous utiliserons donc des vertices de type "TransformedColored" pour créer les trois points qui composeront notre triangle. Voyons tout d'abord la syntaxe du constructeur qui va nous intéresser :

Constructeur d'un vertice 'TransformedColored'
Sélectionnez
public TransformedColored(
    float xvalue,
    float yvalue,
    float zvalue,
    float rhwvalue,
    int c
);
Paramètre Description
xvalue La position sur l'axe x de l'écran
yvalue La position sur l'axe y de l'écran
zvalue La position sur l'axe z de l'écran (on mettra 0.5 par défaut pour ce genre de vertices pour l'instant)
rhwvalue La valeur du "reciprocal homogeneous w". C'est une valeur qui va permettre à DirectX de projeter le vertex (sommet) à l'écran. Si la valeur de ce paramètre est différente de 1 alors nous aurons une projection erronée. Dans presque tous les cas (en tout cas pour un affichage simple comme nous le réalisons ici) nous initialiserons ce paramètre à 1.
c La valeur de la couleur attachée au vertice

Maintenant que nous avons vu le constructeur, créons nos trois vertices :

Déclaration des vertices de notre triangle
Sélectionnez
// On commence par déclaré un tableau pour nos vertices
private CustomVertex.TransformedColored[] Vertices;

// On initialise nos vertices
Vertices = new CustomVertex.TransformedColored[3];
Vertices[0] = new CustomVertex.TransformedColored(200.0f, 125.0f, 0.5f, 1f, Color.Beige.ToArgb());
Vertices[1] = new CustomVertex.TransformedColored(275.0f, 275.0f, 0.5f, 1f, Color.Orange.ToArgb());
Vertices[2] = new CustomVertex.TransformedColored(125.0f, 275.0f, 0.5f, 1f, Color.Red.ToArgb());

Nos vertices étant déclarés il ne nous reste plus qu'à les afficher… c'est ce que nous allons voir dans le chapitre suivant...

III. Les Vertex Buffers

III-A. Initialisation

Les Vertex Buffers (Tampon de sommets en français ;)) sont des objets qui vont nous permettre de lier les informations contenues dans nos vertices vers la fonction de rendu de primitive du device Direct3D. Commençons comme d'habitude par regarder le constructeur d'un vertex buffer.

Le constructeur de l'objet VertexBuffer
Sélectionnez
public VertexBuffer(
    Type typeVertexType,
    int numVerts,
    Device device,
    Usage usage,
    VertexFormats vertexFormat,
    Pool pool
);
Paramètres Description
typeVertexType Le type de vertex que nous voulons stocker
numVerts Le nombre de vertex que nous voulons stocker
device Un device direct3D
Usage Combinaison de valeur indiquant pour quel usage spécial nous utiliserons ce vertex buffer
vertexFormat Le format des vertex que le vertex buffer va stocker
Pool Le mode de management de mémoire que Direct 3D doit utiliser pour ce vertex buffer

Passons maintenant à la création de notre VertexBuffer

Création du Vertex Buffer
Sélectionnez
// On commence par le déclarer
private VertexBuffer vBuffer;

// On appelle le constructeur
vBuffer = new VertexBuffer(typeof(CustomVertex.TransformedColored), Vertices.Length,
    device, 0, CustomVertex.TransformedColored.Format, Pool.Default);

Voilà notre Vertex Buffer est maintenant prêt à accueillir des données de nos vertices (ou vertex).

III-B. Remplir le VertexBuffer avec des Vertices

Il nous reste maintenant à stocker les vertices de notre triangle dans le vertex buffer que nous venons de créer. La fonction "SetData" du vertex buffer permet de lui indiquer l'endroit où sont stockées les vertices. Voyons la syntaxe de cette fonction :

La fonction SetData de la classe 'VertexBuffer'
Sélectionnez
public void SetData(
    object data,
    int lockAtOffset,
    LockFlags flags
);
Paramètres Description
data Représente la structure contenant les informations de nos vertices
lockAtOffset Indique l'offset dans la structure des vertices qui doit être verrouillé. Pour l'instant ce paramètre n'est pas très important. En effet, nous n'utiliserons pas un unique verrouillage de notre vertex buffer. En effet nous sommes obligés de verrouiller un VertexBuffer dès que nous voulons lui faire stocker des vertices ou dès que nous voulons mettre à jour leur géométrie (coordonnées par exemple). Dans ce tutoriel, nous ne déplacerons pas les sommets de notre triangle donc nous n'aurons besoin de verrouiller le VertexBuffer qu'une seule fois, en l'occurrence, lors de son remplissage avec nos vertices. Dans ce cas nous utiliserons l'offset 0. À noter pour plus tard que verrouiller les VertexBuffers fait chuter drastiquement les performances. En effet un verrouillage oblige un transfert GPU->CPU puis CPU->GPU ce qui prend un certain temps... mais nous verrons ça plus tard.
flags Indique le type de verrouillage à réaliser sur le vertex buffer. Pour l'instant nous utiliserons le flag "None" dans la mesure ou ne nous voulons justement pas verrouiller le vertexbuffer

Et maintenant le code :

Remplissage du VertexBuffer avec nos Vertices
Sélectionnez
// Déclaration du VertexBuffer
private VertexBuffer vBuffer;

// Remplissage du VertexBuffer avec nos Vertices
vBuffer.SetData(Vertices, 0, LockFlags.None);

Nous avons donc terminé avec l'initialisation des composants dont nous avions besoin pour afficher notre triangle... passons maintenant au rendu de ce triangle.

III-C. Le Rendu

Le rendu va se décomposer en trois étapes :

Les trois étapes du rendu d'un Vertex Buffer

  1. Indiquer au device Direct3D le VertexBuffer à lire ;
  2. Indiquer au device Direct3D le type des vertices stockés dans ce VertexBuffer ;
  3. Lancer la fonction de rendu.

Pour indiquer le VertexBuffer à lire au device Direct3D, nous utiliserons la fonction "SetStreamSource" du device.

La fonction SetStreamSource du device Direct3D
Sélectionnez
public void SetStreamSource(
    int streamNumber,
    VertexBuffer streamData,
    int offsetInBytes
);
Paramètres Description
streamNumber Indique l'index du VertexBuffer à lire
streamData Indique l'objet VertexBuffer que nous allons lire
offsetInBytes Indique à quel offset dans le VertexBuffer le device Direct3D doit commencer à lire

En maintenant le code :

 
Sélectionnez
device.SetStreamSource(0, vBuffer, 0);

Seconde étape indiquer au device Direct3D le type de Vertex que nous avons stocké dans le VertexBuffer. Pour cela rien de plus simple :

 
Sélectionnez
device.VertexFormat = CustomVertex.TransformedColored.Format;

Dernière étape... le rendu. Comme je vous l'ai précisé au début du tutoriel, DirectX ne se base que sur des triangles pour effectuer le rendu d'une scène. Donc une scène est une multitude de triangles. Pour dessiner un triangle il faut donc définir de quelle façon nous allons lier les vertices les unes aux autres... ces liaisons composeront l'objet final. Il existe beaucoup de types de liaison possibles avec Direct3D mais je ne vais que vous en présenter trois. Ce sont les trois que vous utiliserez dans 99 % des cas : le "TriangleList", le "TriangleStrip" et le "TriangleFan". Comme un bon dessin vaut mieux qu'un beau discours, je vous propose ces trois illustrations pour que vous compreniez la différence de logique de liaison entre ces types :

Image non disponible

Vous remarquerez sans mal que le "TriangleList" est sans contexte celui qui sera le plus adapté à notre rendu de triangle.

Pour dessiner notre triangle nous ferons appel à la fonction "DrawPrimitives" du device Direct3D.

La fonction de rendu de primitives du device Direct3D
Sélectionnez
public void DrawPrimitives(
    PrimitiveType primitiveType,
    int startVertex,
    int primitiveCount
);
Paramètres Description
primitiveType Le type de primitive
startVertex Index du vertex de départ
primitiveCount Nombre de primitives

La seule précision que j'apporterais concerne le nombre de primitives. Dans notre exemple ce sera le nombre de vertices stockés dans notre VertexBuffer divisé par 3. Si nous avions stocké par exemple des lignes dans notre VertexBuffer nous diviserions le nombre de vertices par 2... Voyons maintenant le code :

 
Sélectionnez
device.DrawPrimitives(PrimitiveType.TriangleList, 0, Vertices.Length / 3);

IV. Récapitulatif

Nous avons donc appris jusqu'à présent à :

  • initialiser des vertices (ou vertex) ;
  • initialiser un VertexBuffer ;
  • remplir un VertexBuffer ;
  • afficher les données d'un VertexBuffer à l'écran.

Voyons le récapitulatif du code source :

Afficher un triangle colorié à l'écran
Sélectionnez
public partial class Form1 : Form
{
        // Notre device
        private Device device = null;
        // Notre VertexBuffer
        private VertexBuffer vBuffer;
        // Nos Vertices
        private CustomVertex.TransformedColored[] Vertices;
        
        // Initialisation de la form
        public Form1()
        {
            InitializeComponent();
            // Appel de la procédure d'initialisation du device Direct3D
            InitializeGraphics();
            CreateTriangle();
            // Evènement pour la boucle de rendu
            this.Paint += new PaintEventHandler(this.Render);
        }
        
        // Procédure d'initialisation du device Direct3D
        private void InitializeGraphics()
        {
            // Définition des PresentParameters
            PresentParameters presentParams = new PresentParameters();
            presentParams.Windowed = true;
            presentParams.SwapEffect = SwapEffect.Discard;
        
            // Creation de notre device
            device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);
        }
        
        // Procédure de création de notre triangle
        private void CreateTriangle()
        {
            // On définit vertices de notre triangle
            Vertices = new CustomVertex.TransformedColored[3];
            Vertices[0] = new CustomVertex.TransformedColored(200.0f, 125.0f, 0.5f, 1f, Color.Beige.ToArgb());
            Vertices[1] = new CustomVertex.TransformedColored(275.0f, 275.0f, 0.5f, 1f, Color.Orange.ToArgb());
            Vertices[2] = new CustomVertex.TransformedColored(125.0f, 275.0f, 0.5f, 1f, Color.Red.ToArgb());
        
            // On crée le vertex buffer
            vBuffer = new VertexBuffer(typeof(CustomVertex.TransformedColored), Vertices.Length,
               device, 0, CustomVertex.TransformedColored.Format, Pool.Default);
        
            // On stocke les vertices dans le vertex buffer
            vBuffer.SetData(Vertices, 0, LockFlags.None);
        }
        
        // La boucle de rendu
        private void Render(object sender, PaintEventArgs e)
        {
            // Effacement de la scène
            device.Clear(ClearFlags.Target, 0, 1.0f, 0);
            // Début de la scène
            device.BeginScene();
            // On définit vertex buffer à lire
            device.SetStreamSource(0, vBuffer, 0);
            // On définit le type des vertices stockés dans notre vertex buffer
            device.VertexFormat = CustomVertex.TransformedColored.Format;
            // On dessine le vertex buffer
            device.DrawPrimitives(PrimitiveType.TriangleList, 0, Vertices.Length / 3);
        
            // Fin de la scène
            device.EndScene();
            // Présentation de la scène à l'écran
            device.Present();
        }
}

Et un petit screenshot de ce que vous devriez obtenir...

Image non disponible
Screenshot de l'application réalisée à l'aide de ce tutoriel

Index - Tutoriel Précédent - Ce Tutoriel en PDF

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 funky.data . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.