05 Rendu avec un VertexBuffer

La dernière fois nous avons détaillé la création d'un VertexBuffer, ce qui contient des informations sur les sommets. Nous avons besoin du VertexBuffer car c'est la structure que demande le Device pour faire les dessins. Dans cet article, nous allons voir comment utiliser notre VertexBuffer pour afficher quelquechose à l'écran.

Pour afficher une forme, nous devons modifier notre méthode Render. Il n'y a pas de grandes modifs cependant

protected void Render() { device.Clear(ClearFlags.Target, Color.Bisque, 1.0F, 0); device.BeginScene(); // Deux nouvelles lignes ici device.SetStreamSource(0, vertices, 0); device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); device.EndScene(); device.Present(); }

Vous aurez constaté les deux nouvelles lignes. La première ne nécessite pas de grandes explications :

device.SetStreamSource(0, vertices, 0);

SetStreamSource dit simplement au Device : "Quand je te demande d'afficher des trucs, prends les sommets contenus dans ce VertexBuffer." Nous pourrions avoir plusieurs VertexBuffers, par exemple un pour chaque objet à dessiner, et il est important de donner la bonne source de sommets pour ne pas afficher n'importe quoi.

La deuxième ligne est celle où toute l'action se produit :

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

On demande à Direct3D de dessiner quelquechose, enfin!

Le premier argument de DrawPrimitives est très important : il indique à Direct3D quel genre de chose on va dessiner. Il y a six choix possibles : PointList, LineList, LineStrip, TringleList, TriangleStrip et TriangleFan.

PointList est le plus simple. Cela indique à Direct3D que les sommets dans le VertexBuffer doivent être affichés comme une liste de points. Vous n'aurez probablement recours à ceci très souvent, à part pour afficher un ciel étoilé en arrière-plan.

LineList est similaire : les sommets dans le buffer vont par deux, chaque paire contenant les points de début et de fin de la ligne. Etant donné qu'on doit donner les points de départ et d'arrivée de chaque ligne, afficher une suite de lignes connectées est inefficace : nous devrions donner le début de la première ligne, la fin de la première ligne, puis redonner ce point en tant que début de la deuxième ligne etc. Vu que la performance est le maître mot, Direct3D fournit les LineStrip.

LineStrip nous permet de dessiner des séries de lignes interconnectées, où la fin d'un segment est le commencement du prochain. Si nous passons PritiveType.LineStrip à DrawPrimitives, le Device considèrera que les sommets dans le VertexBuffer sont ordonnés comme ceci : début, fin du premier segment, fin du deuxième segment, fin du troisième, etc. Il n'y a pas d'obligation d'avoir un nombre pair de sommets comme avec LineList mais on ne peut dessiner que des segments connectés, c'est l'inconvénient.

En utilisant des points et des lignes, on peut réaliser de kitchissimes graphismes façon "BattleZone". Bien sûr, nous préfererions faire mieux, nous préfererions dessiner des solides. Et bien les solides dans Direct3D sont fait à base de triangles - utilisez en suffisaments et vous pourrez pratiquement tout dessiner. Un cube par exemple est fait de six carrés, chacun constitué de deux triangles.

Comme mentionné plus haut, nous avons trois choix pour le rendu des triangles : TriangleList, TriangleStrip et TriangleFan. TriangleList est le plus simple. Spécifier TriangleList indique à Direct3D que nous avons organisé notre VertexBuffer comme un ensemble de trio de sommets, et pour chaque trio il devrait dessiner le triangle correspondant.

Etant donné qu'on va dessiner qu'un triangle, cette solution marche bien pour nous :

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

Cependant si nous avions à faire avec des tonnes de triangles, ce choix ne serait pas le meilleur. Imaginez le cube évoqué plus haut. Il est fait de douze triangles mais chacun d'eux partage tous ses sommets avec d'autres triangles. Devoir rentrer ces sommets plusieurs fois ne serait pas efficace. Nous pouvons utiliser les TriangleStrips et TriangleFans pour pallier à cela. (et en fait il y a un truc appelé "IndexBuffer" qui est encore mieux pour cela, mais ça fera l'objet d'un autre article).

De la même façon qu'un LineStrip commence par deux points définissant une ligne, un TriangleStrip commence par trois points définissant un triangle. Egalement comme les LineStrip, les points suivants rajoutent un triangle fait du nouveau point et des deux points précédents. Un TriangleFan est similaire mais tous les triangles ont le premier point en commun. Ces schémas devraient vous éclairer :

TriangleList

Ca va mieux?

OK! Retournons à Render, nous avons décrit le premier paramètre de DrawPrimtives :

device.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);

Le second paramètre est l'indice du sommet dans le VertexBuffer par lequel commencer. Dans notre cas c'est zero puisqu'on veut démarrer au début. Mais si nous avions des centaines de sommets décrivant des douzaines de triangles, nous voudrions peut être dessiner les triangles n° 10 à 77, nous n'utiliserions pas zero dans ce cas-là.

Le dernier paramètre est un peu délicat. Il définit le nombre de primtives à dessiner. Quand j'ai demarré avec Direct3D j'ai d'abord cru qu'il s'agissait du nombre de sommets à dessiner. Ce n'est pas le cas : donner la valeur trois dans notre code provoquerait une erreur vu qu'il essaierait de dessiner trois triangles, ce qui demanderait pour un TriangleList neuf points. Notre VertexBuffer n'en a que trois.

Ainsi quel est le resultat de tout cela? Ceci :

Remarquez que la couleur de chaque sommet est onctueusement mélangée sur la surface du triangle. Cela ne se produit pas seulement pour les couleurs mais également pour les textures et les vecteurs normaux, deux choses dont nous parlerons dans des articles prochains.

Il y a encore un detail que je voudrais mentionner, car si vous commencez à bidouiller ce code, vous risquez de vous heurter à un problème. Etant donné que les scènes Direct3D habituelles manipulent des centaines si ce n'est pas des milliers voir des millions de triangles, c'est important de ne traiter que ceux qui seront visibles au final. Pour cette raison Direct3D utilise un procédé appelé "culling" (en français : sélection) pour éliminer immédiatement plein de triangles dans la scène.

Le culling se base sur l'estimation que les objets sont solides - qu'ils n'ont pas de trous dedans. Pour de tels objets, nous savons qu'aucune partie d'un triangle tournant le dos au spectateur ne sera visible. Direct3D ne va même pas tenir compte de ces triangles de dos. Ce procédé est parfois connu en tant que "backface culling".

Notez que ce n'est pas valable pour les objets à trous, comme les tubes creux. Dans ce cas, vous devrez dire à Direct3D de ne pas supprimer les faces de dos en ajoutant cette ligne dans la procédure Render :

device.RenderState.CullMode = Cull.None;

Pour revenir au comportement par défaut, spécifier

device.RenderState.CullMode = Cull.CounterClockwise;

La question qui est peut être en train de vous tarauder est "Comment il sait qu'un triangle est de face ou de dos?". La dernière ligne de code vous donne un indice : il tient compte de l'ordre des sommets qui font le triangle. Par défaut, si les sommets furent rentrés dans l'ordre correspondant au sens des aiguilles d'une montre, le triangle est de face, sinon le triangle est de dos. Cull.Clockwise produirait le résultat inverse et Cull.None signifie "n'ignore aucun triangles".

Allez donc tenter des expériences avec ces divers paramètres en ajoutant d'autres triangles, en changeant l'ordre des sommets et en manipulant le CullMode.

La prochaine fois, nous apprendrons comment nous passer des coordonnées transformées, c'est à dire, nous allons enfin voir pourquoi il y a "3D" dans le mot Direct3D.