04 Création d'un VertexBuffer

La dernière fois nous avons évoqué les bases du rendu ou comment préparer Direct3D à afficher des trucs à l'écran. Nous étions capables d'effacer l'écran avec une couleur particulière. Pas mal, mais cette fois ci nous allons faire mieux : nous allons créer un VertexBuffer, ce qui est la dernière chose à connaître pour pouvoir dessiner des formes.-

Pour afficher une forme à l'écran, nous devons comprendre ce qu'est un vertex, (vertices au pluriel). Un vertex géometriquement parlant c'est le point où deux arêtes d'un polygone se rencontrent. Litteralement, ce n'est qu'un sommet en fait. Cependant, dans Direct3D, un vertex est une structure contenant plus d'informations que la position du sommet dans l'espace - il peut avoir une couleur, une coordonnée de texture ou d'autres données.

Etant donné qu'il est important de présenter les choses de manière aussi efficace que possible (souvenez vous qu'on a à envoyer cette information dans la carte graphique plusieurs fois par seconde) les sommets dans Direct3D sont décrits suivant un Format de Vertex Flexible, ou FVF. Un FVF n'est qu'un moyen de dire "Hé, ces sommets contiennent une position 3D et des coordonnées de texture" ou bien "Hé ces sommets contiennent une position 2D et une couleur".

Dans Managed Direct3D, nous n'avons pas vraiment à utiliser directement les FVFs contrairement à C++ non managé. A la place nous avons un ensemble de sous-classes à l'interieur de la classe CustomVertex. Voici une liste non exhaustive de ces classes :

Il y en a d'autres mais celles ci sont suffisantes pour continuer. Vous pouvez constater que les différents formats nous permettent de mettre différentes choses dans un sommet - une couleur ou pas de couleur, des coordonnées transformées ou non transformées.

Utiliser des coordonnées transformées veut simplement dire que nous avons déjà fait le travail pour savoir où vont aller choses à l'écran. Normalement, on laisse Direct3D faire ce travail en se basant sur la position de nos objets et de la caméra virtuelle. Pour l'instant nous allons utiliser les coordonnées transformées car utiliser les non-transformées signifie une manipulation de plein de choses qu'on a pas encore vu.

Sans tenir compte du format de sommet qu'on choisit, la façon de gérer les coordonnées est la même : le VertexBuffer. Un VertexBuffer est une sorte de tableau contenant des sommets. C'est assez fréquent de creer un VertexBuffer juste après avoir créé le device. Voici une nouvelle version de InitializeGraphics :

private VertexBuffer vertices; protected bool InitializeGraphics() { PresentParameters pres = new PresentParameters(); pres.Windowed = true; pres.SwapEffect = SwapEffect.Discard; device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, pres); vertices = CreateVertexBuffer(device); return true; }

Il n'y a qu'une nouvelle ligne qui appelle CreateVertexBuffer que voici :

protected VertexBuffer CreateVertexBuffer(Device device) { device.VertexFormat = CustomVertex.TransformedColored.Format; CustomVertex.TransformedColored[] verts = new CustomVertex.TransformedColored[3]; verts[0] = new CustomVertex.TransformedColored( this.Width / 2, this.Height / 4, 0.5F, 1, Color.Blue.ToArgb()); verts[1] = new CustomVertex.TransformedColored( this.Width * 3 / 4, this.Height * 3 / 4, 0.5F, 1, Color.Green.ToArgb()); verts[2] = new CustomVertex.TransformedColored( this.Width / 4, this.Height * 3 / 4, 0.5F, 1, Color.Red.ToArgb()); VertexBuffer buf = new VertexBuffer( typeof(CustomVertex.TransformedColored), verts.Length, device, 0, CustomVertex.TransformedColored.Format, Pool.Default ); GraphicsStream stm = buf.Lock(0, 0, 0); stm.Write(verts); buf.Unlock(); return buf; }

La procédure commence par positionner le format des sommets comme ceci :

device.VertexFormat = CustomVertex.TransformedColored.Format;

Ceci est un autre exemple où Managed Direct3D n'a pas rempli mes attentes de programmeur orienté objet : le VertexFormat est un paramètre global. Cela signifie que chaque vertex qui s'apprête à être dessiné doit avoir des coordonnées transformées et une information de couleur. Si j'utilisais plusieurs types de sommets différents, je devrais changer le format à chaque fois que je veux dessiner des sommets d'un format différent, et ce changement ne pourrait s'effecuer que dans la procédure de rendu (pas dans CreateVertexBuffer). Heureusement, je ne vais dessiner qu'un simple triangle dans cette démo, donc on peut laisser ce problème de côté pour l'instant.

L'étape suivante crée un tableau de sommets qu'on prévoit de mettre dans le VertexBuffer :

CustomVertex.TransformedColored[] verts = new CustomVertex.TransformedColored[3];

Nous initialisons ensuite chaque vertex de ce tableau (note : le new ici ne fait qu'initialiser il ne fait pas d'allocation étant donné que les vertices sont des structs) :

verts[0] = new CustomVertex.TransformedColored( this.Width / 2, this.Height / 4, 0.5F, 1, Color.Blue.ToArgb()); verts[1] = new CustomVertex.TransformedColored( this.Width * 3 / 4, this.Height * 3 / 4, 0.5F, 1, Color.Green.ToArgb()); verts[2] = new CustomVertex.TransformedColored( this.Width / 4, this.Height * 3 / 4, 0.5F, 1, Color.Red.ToArgb());

Dans le cas des sommets TransformedColored qu'on utilise ici, le constructeur prend 5 arguments : une position x y z, une valeur w, un entier représentant la couleur. Ignorons z pour le moment - rappelez vous qu'on travaille avec des coordonnées transformées (pensez "coordonnées écran") ainsi z n'a pas vraiment d'influence. Laissons de côté la valeur w pour le moment.

Les trois autres paramètres sont assez explicites. x et y sont les coordonnées à l'écran (le coin superieur gauche étant en 0;0) et la couleur est la couleur du sommet, bien que Direct3D nous oblige à utiliser ToArgb() pour convertir en entier, au lieu de nous permettre de mettre directement une des couleurs prédéfinies dans Color.

Ce que nous avons fait ici est simplement la définition des 3 sommets d'un triangle isocèle. le point du haut est à un quart de la hauteur en partant du haut de la fenêtre, le point à gauche est à un quart de la largeur en partant de la gauche, le point à droite est à un quart de la largeur en partant de la droite.

Nous avons quand même besoin de créer le VertexBuffer lui même.

VertexBuffer buf = new VertexBuffer( typeof(CustomVertex.TransformedColored), verts.Length, device, 0, CustomVertex.TransformedColored.Format, Pool.Default );

Notez qu'un VertexBuffer ne peut contenir qu'un type de sommet. Nous indiquons ce type grace au premier argument, nous donnons le nombre max de sommets que le VertexBuffer peut contenir avec le deuxième argument, on passe le device au troisième argument, le quatrième permet de spécifier des options supplémentaires, indiquons le format des sommets avec le cinquième et nous spécifions le pool par défaut avec le dernier paramètre. Le Pooling est un sujet avancé et Pool.Default est le bon choix pour le moment.

C'est un peu étrange qu'on ait à spécifier le format au premier et au cinquième paramètre, c'est dû au fait qu'on peut definir nos propres formats de sommets pour des situations particulières. Renseigner le premier et pas le cinquième marcherait.

Fiou! Tout ce qu'il reste à faire est de remplir le VertexBuffer. C'est assez facile :

GraphicsStream stm = buf.Lock(0, 0, 0); stm.Write(verts); buf.Unlock();

Nous obtenons une réference sur un GraphicsStream en verrouillant le VertexBuffer ce qui nous permet d'enfourner dedans nos trois sommets TransformedColored. Il est important de déverrouiller le VertexBuffer quand nous avons fini. Lock et Unlock sont nécessaires pour que Direct3D sache qu'il ne doit pas tenter de lire son contenu quand est en train d'écrire dedans.

Lorsque le VertexBuffer est créé, nous pouvons enfin l'afficher. Pour cela, nous aurons besoin de modifier notre méthode Render. Mais nous aborderons cela la prochaine fois.

Notes du traducteur

Tant que j'y suis, je vais transmettre un peu de mon savoir même si je suis débutant en Direct3D.

Il y a une autre façon de remplir le VertexBuffer, c'est de le créer et de recupérer le tableau de sommets avec Lock, au lieu de créer le tableau de sommets en plus du VertexBuffer

CustomVertex.TransformedColored[] verts = buf.Lock (0, 0); verts[0] = new CustomVertex.TransformedColored (...); verts[1] = ... buf.Unlock();

On peut utiliser le même procédé pour changer des valeurs dans les sommets et ainsi réaliser une animation (bien qu'il existe les matrices). Attention cependant à ne pas effectuer d'accès en lecture sur les sommets contenus dans le tableau retourné par Lock, il semblerait que ce soit couteux en performances. Les opérateurs d'incréments , --, +=, *= ... effectueraient aussi un accès en lecture.