Dessiner avec OpenGL : Les bases
publié le dimanche 26 novembre 2006 (vu 18648 fois)

Maintenant que nous avons une base (voir L’affichage : Projection et Transformations) pour créer une fenêtre d’affichage OpenGL correctement initialisée, nous allons pouvoir nous amuser à y dessiner ;-)

OpenGL étant une bibliothèque graphique 3D, le dessin d’effectue essentiellement à base de polygones. Pour résumer, un polygone est une forme géométrique fermée composée d’au minimum 3 segments. Si vous voulez tous les détails sur ce qu’est un polygone, cliquez sur le lien ;-)

On peut dénombrer 3 types de polygones reconnus par OpenGL, la version générique ainsi que 2 optimisations :
- un GL_POLYGON est un polygône générique dont le nombre de segments n’est pas prédéterminé mais évidemment composé de 3 segments minimum
- un GL_QUAD est composé de 4 segments, tel un quadrilatère
- un GL_TRIANGLE est quand à lui composé de 3 segments, un triangle donc ;-) Pourquoi le quadrilatère et le triangle sont considérés comme des optimisations ? Je vous l’expliquerai dès que nous verrons comment on les fait afficher par OpenGL ;-)

Pour être correctement affiché par OpenGL, un polygone plein doit être convexe. Si ce terme ne vous est pas familier, lisez l’article Wikipedia ;-) La raison de cette limitation se situe au sein même des drivers, tout polygone étant découpé en triangle, les points du polygone sont parcourus linéairement dans l’ordre où ils sont envoyés. Par conséquent un polygone concave ou un polygone croisé provoqueront des effets inattendus. Si vous envisagez de ne dessiner que les contours du polygone, le problème ne se posera pas.

Une image étant plus parlante qu’un long texte, voici quelques exemples de formes géométriques rendues avec OpenGL : Formes générées à partir des primitives OpenGL

L’image contient 6 formes, en bleu la forme remplie, en blanc le contour :

  1. Un polygone à 6 côtés de forme concave rendu avec le paramètre GL_POLYGON. Contrairement au contour, on voit bien que le remplissage n’est pas bon
  2. Un autre polygone, toujours avec le paramètre GL_POLYGON, mais cette fois la forme est convexe. Aucun problème dans le remplissage qui épouse bien les contours du polygone.
  3. Un quadrilatère concave rendu avec le paramètre GL_QUADS. Tout comme le polygone, on remarque tout de suite le débordement du remplissage.
  4. Un second quadrilatère, convexe cette fois, le remplissage parfait ne déborde pas de la forme géométrique.
  5. Une forme concave, la même que celle rendue avec le premier quadrilatère mais cette fois en utilisant des triangles. Le paramètre GL_TRIANGLES a donc été utilisé pour demander à OpenGL d’afficher non plus un quadrilatère mais des triangles. On peut voir au niveau des contours que la forme est composée de 2 triangles.
  6. Une autre forme concave, identique au premier polygone dessiné mais cette fois avec des triangles. D’après les contours, on constate que 4 triangles ont étés nécessaires et, contrairement à la même forme rendue avec GL_POLYGON, le remplissage ne déborde pas.

Vous allez me dire que, puisque les GL_POLYGON et les GL_QUADS peuvent poser des problèmes de rendu, autant se limiter à l’utilisation des GL_TRIANGLES.
Dans le cas d’un affichage 2D, tout dépend de ce que vous envisagez de faire. Un logiciel de dessin vectoriel limité à 2 dimensions se servira certainement beaucoup des GL_POLYGON tandis qu’un moteur de jeu 2D à base de sprites préférera les GL_QUADS.
Pour un moteur de particules, qu’il soit en 2D ou en 3D, l’utilisation des GL_QUADS sera également bien appropriée.
En 3D, on ne dessine pas de simples formes planes mais des objets/volumes de plus en plus complexes, aucun doute dans ce cas que les triangles sont bien plus pratiques. De plus, nous verrons plus tard qu’OpenGL propose des traitements optimisés pour les triangles. Que ce soit en 2D ou en 3D, ces possibilités d’optimisation vous feront certainement renoncer à l’utilisation des GL_QUADS dans tout autre projet qu’un logiciel vectoriel 3D.
Vous l’aurez compris, votre choix sera affecté par le type de programme que vous développez. L’utilisation d’une primitive plutot qu’une autre sera fonction des avantages que vous pourrez en tirer.


Voilà pour les bases, mais maintenant que l’on sait ce que connait OpenGL question primitives, comment on fait pour les afficher ?
Evidemment, si vous êtes arrivé jusqu’ici c’est que ça vous intéresse toujours et il est temps de passer à la pratique ;-)
Maintenant que notre application est paramétrée, nous pouvons ajouter qeulques commandes dans la fonction de dessin (DrawFrame). Pour afficher un polygone avec OpenGL, il faut procéder de la manière suivante :

La commande glBegin prend en paramètre le nom d’une primitive, GL_POLYGONE par exemple, afin de déterminer l’utilisation qui sera faite des données transmises.
Les données en questions étant les commandes glVertex3f, commande dérivée de glVertex avec la définition de 3 paramètres (x, y et z) de type float.
Comme dans notre exemple nous nous limitons à 2 dimensions, nous pouvons faire appel à la commande glVertex2f.
Pour finir les dessin, la commande glEnd doit être appelée, afin que OpenGL sache que les envois de données sont terminés concernant la primitive initiée avec glBegin.
Nous pouvons également ajouter comme information la couleur du sommet. La commande glColor sert à définir la couleur du prochain sommet que l’on enverra. Comme pour la commande glVertex, la commande glColor se décline en de nombreuses versions dont celle que nous allons utiliser ici : 3 paramètres de type unsigned byte décrivant la couleur au format RGB habituel.
Dessinons par exemple le polygone convexe de l’image :

Simple n’est-ce pas ?
Lorsque je vous ai énuméré les 3 types de polygones, je vous disai que le quadrilatère et le triangle étaient des optimisations. En quoi ces polygones sont-ils optimisés comparé à un polygone générique ?
Simplement parcequ’un quadrilatère étant composé de 4 sommets précisément et un triangle de 3 sommets, il est possible de définir plusieurs quadrilatères ou plusieurs triangles à la suite entre un glBegin et un glEnd.
Un exemple valant mieux qu’un long discours, voici comment le polygone concave est dessiné avec les triangles :

Vous ne pouvez pas faire cela avec des polygones génériques puisque, n’ayant pas un nombre de sommets déterminés, le polygone se sera pas fermé tant que glEnd ne sera pas appelé.


Voilà pour les bases du dessin avec OpenGL, ce n’est pas très compliqué ;-)
Si vous consultez la documentation de glBegin, ce que je vous conseille vivement, vous trouverez d’autres définitions de primitives pour dessiner des points ou des lignes mais également des quadrilatères et des triangles encore optimisés.
Aller, je ne vais pas vous laisser en plan comme ça et nous allons de ce pas regarder en détail 2 primitives pouvant nous faire gagner en performances.

La primitive GL_TRIANGLE_FAN, comme son nom l’indique... ou pas, désigne des "triangles en ventilateur" :-/ Mais qu’est-ce que je raconte... Ces termes anglais sont vraiment pas facile à traduire ^^
Prenons comme exemple le polygone convexe et demandons nous comment le dessiner en utilisant des triangles. Le polygone comporte 6 sommets et va nécessiter 4 triangles pour être dessiné soit 12 appels à glVertex (4 triangles de 3 sommets) alors qu’un GL_POLYGON ne fait appel qu’à 6 glVertex pour les 6 sommets.
En utilisant la primitive GL_TRIANGLE_FAN, on ne fera que 6 appels à glVertex. Le premier triangle nécessitant 3 appels à glVertex (logique :D), les 3 autres triangles seront dessinés avec 1 appel à glVertex chacun.
Quel est ce miracle me demandez-vous ? Simplement en utilisant la primitive GL_TRIANGLE_FAN, le premier sommet et le dernier sommet transmis sont utilisés comme premier et second sommets du nouveau triangle, la primitive se terminant comme un GL_POLYGON sur un appel à glBegin, ce qui donne :

Décomposition en image du GL_TRIANGLE_FAN : GL_TRIANGLE_FAN décomposé

L’autre primitive intéressante est GL_TRIANGLE_STRIP, traduit litéralement par "bande de triangles" ^^
La primitive GL_TRIANGLE_STRIP fonctionne sur le même principe à la différence près que ce sont les 2 derniers sommets déjà transmis qui sont réutilisés.
Prenons comme exemple le polygone concave. Vous avez vu plus haut que le rendu de ce polygone avec des triangles nécessitait la répétition de plusieurs sommets, portant à 12 leur nombre total alors qu’il n’en demandait que 6 à l’origine.
En utilisant GL_TRIANGLE_STRIP, on reste à 6 sommets, tout comme le GL_POLYGON :

En image, voici les étapes de la création du GL_TRIANGLE_STRIP : GL_TRIANGLE_STRIP décomposé


Ainsi s’achève notre article qui ne fait que survoler les possibilités offertes par OpenGL dans la création de formes géométriques. Nous nous sommes cantonnés à la 2D alors que ce qui nous intéresse, je le vois aussi dans vos yeux, c’est la 3D ;-)
Les différences entres les primitives de base et les optimisations (GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP) prendront plus de sens lorsque la troisième dimension viendra perturber tout ça :))
Rendez-vous au prochain article pour explorer cette nouvelle dimension et découvrir encore et toujours plus de possibilités.

Le code source que je vous laisse contient d’autres appels à OpenGL que je ne détaille pas dans l’article, une occasion pour vous de vous documenter sur ces fonctionnalités bien que les commentaires devraient suffire à leur compréhension ;-)

Code source (fichier Zip de 1.9 ko)
Executable windows (fichier Zip de 62.9 ko)