Base du dessin en 2D sous OpenGL pour Windows

 

Eléments primitifs

Pour dessiner en 2D, vous vous attendez à pouvoir dessiner un point, une ligne, un rectangle tout comme vous pourriez le faire avec n'importe quel système disposant de fonctions graphiques de bases.

Sous OpenGL, toutes ces figures sont appelées des éléments primitifs. Car grâce à ces éléments primitifs, vous allez pouvoir composer un objet 2D (et plus tard, 3D).

 

Dessiner un élément primitif

Pour dessiner ces éléments primitifs, vous devez d'abord indiquer le type de l'élément primitif. D'où la fonction glBegin() qui attend ce type. Voici la liste des éléments primitifs disponibles:

 

GL_POINTS

Pour dessiner un point

1 vertex suffit
GL_LINES

Pour dessiner des lignes

les vertice vont par pair, pour dessiner une ligne, il faut deux vertice !
GL_LIGNE_STRIP Dessiner des lignes connectées entre elles

Soit les points A, B, C et D, vous obtiendrez:

une ligne entre A et B, une entre B et C et enfin entre C et D

GL_LINE_LOOP Idem que GL_LIGNE_STRIP, avec en plus une ligne dessinée entre le premier vertex et le dernier vertex définis

Soit les points A, B, C et D, vous obtiendrez:

une ligne entre A et B, une entre B et C, entre C et D et enfin entre D et A

GL_TRIANGLES Dessiner un triangle Il faut trois vertice par triangle
GL_TRIANGLE_STRIP Dessine des triangles connectés entre eux

il faut au minimum 3 vertice pour dessiner le premier triangle. Ensuite, chaque ajout de nouveau vertex créera un triangle avec les deux vertice dernièrement définis.

les vertice ABC vont donc former un premier triangle. Si ajout d'un vertex D, vous obtiendrez un nouveau triangle BCD, de nouveau un vertex E, vous obtiendrez un nouveau triangle CDE, ...

GL_TRIANGLE_FAN Similaire à GL_TRIANGLE_STRIP, sauf que le premier vertex de chaque triangle est toujours le premier vertex qui a été saisi.

Les vertice ABC vont donc former un premier triangle. Si ajout d'un vertex D, vous obtiendrez un nouveau triangle CDA, de nouveau un vertex E, vous obtiendrez un nouveau triangle DEA, ...

Où soit A, B, C, D, E, 5 vertice, B, C, D et E forme un quadrilatère et A et au centre de celui-ci. 4 triangles sont ainsi formés pour dessiner ce quadrilatère avec un vertex commun à ces triangles: A

GL_QUADS Dessine un quadrilatère Il faut 4 vertice pour chacun des quadrilatères
GL_QUAD_STRIP Même chose que GL_TRIANGLE_STRIP, mais pour un quadrilatère il faut au minimum 4 vertice pour dessiner le premier triangle. Ensuite, chaque ajout d'un couple de nouveau vertice créera un quadrilatère avec les deux vertice dernièrement définis.
GL_POLYGON Dessine un polygone convexe

 

Image non trouvée !GL_LINE_STRIP_ADJACENCY, GL_LINES_ADJACENCY, GL_TRIANGLE_STRIP_ADJACENCY ou GL_TRIANGLES_ADJACENCY sont disponibles à partir de la version 3.2, d'autres seront dépréciés

Image non trouvée !En réalité, OpenGL décomposera automatiquement toutes les figures avec plus de 3 vertice en triangle. En fait, pour dessiner en 3D, la base est toujours le triangle.

Image non trouvée !Pour Android, il faudra précéder le nom de la primitive par GLES20. Exemple GLES20.GL_LINE_LOOP;

 

Evidemment, comme il y a un glBegin(), il faudra mettre un glEnd() pour indiquer que vous avez terminé de définir le ou les éléments primitifs. Il est en effet possible de dessiner plusieurs éléments primitifs identiques en une seule définition. Bien entendu, seules les éléments primitifs ayant toutes les coordonnées pour le définir seront dessinés. l'élément primitif (toujours le dernier) dont la définition est incomplète sera tout simplement non traité ni pendant ce traitement du glBegin()->glEnd(), ni après !

Je dis le dernier, car si vous oubliez un vertex d'un élément primitif, l'élément primitif sera dessiner avec le vertex de l'élément primitif suivant (s'il existe). Ce sera donc toujours le dernier élément primitif qui ne sera pas dessiné. Mais dans le même temps, vous devriez obtenir à l'affichage quelque chose ne correspondant absolument pas à ce que vous vouliez.

Pour définir les éléments primitifs, il faudra en fait définir tous les vertice des éléments primitifs.

La fonction permettant de faire cela se nomme glVertex(). En fait, cette fonction est polyforme, je m'explique:

 

Fonctions polyformes

Vous travaillez en 2D, vous ne passerez donc que x et y soit 2 paramètres. la fonction sera donc plus précisement glVertex2...

Enfin, x et y peuvent être de différents types (entier, flottant, ...). vous le préciserez donc lors de l'appelle de la fonction de la manière suivante:

glVertex2type

où type pourra être i, d, f, ... (voir tableau des types OpenGL en annexe).

Bien entendu, différents types sont disponibles, mais x et y seront ensemble du même type ! pas de x en entier et de y en flottant !!!

 

Cette manière de nommer les fonctions est souvent utilisée dans OpenGL. Il suffit donc de connaître la fonction générale pour retrouver celle qui vous intéresse vraiment.

  • la fonction commence par gl
  • suivi de l'opération à faire
  • du nombre de paramètres
  • du type de paramètres.

 

De la couleur

Pour gérer la couleur, vous indiquez à OpenGL qu'il faut activer la gestion de la couleur sur les objets.

glEnable(GL_COLOR_MATERIAL); (Par défaut, il est actif).

Pour la désactiver : glDisable (GL_COLOR_MATERIAL);

Cette fonction est aussi polyforme:vous allez donc utiliser le même principe sur cette fonction...

C'est bien beau de dessiner, mais il serait intéressant de choisir la couleur !

Couleur en anglais se dit color.

Traditionnellement, pour faire de la 2D, vous avez besoin de 3 paramètres pour la couleur : le rouge, le vert et le bleu soit 3 paramètres que vous pourrez passer sur 1 octet (unsigned byte soit 256 niveaux d'intensités).

ce qui donne:

gl Color 3 ub

La fonction utilisée ici sera glColor3ub(Rouge,Vert,Bleu) !!!

Pour mettre votre élément primitif en rouge vif, il suffira de faire précéder glVertex() de la fonction glColor()

Ici glColor3ub(255, 0, 0);

Vous auriez tout aussi bien pu passer les couleurs en nombre flottant. dans ce cas, la couleur variera entre [0,1]

glColor3f(1.0f,0.0f,0.0f);

 

Mais que se passe-t-il si je mets un appel glColor avec une couleur différente, avant chaque vertex ?

Eh bien, vous définirez une couleur unique à chaque vertex ! OpenGL passera d'une couleur à une autre graduellement. Ce qui peut faire de jolis effets de dégrader !

 

Voici un exemple de code:

Image non trouvée !

Image non trouvée !Il existe deux autres fonctions, qui seront vues un peu plus loin, concernant la gestion des couleurs. Si vous êtes nouveau dans OpenGL, inutile de lire la suite. Cependant, si vous connaissez déjà OpenGL, vous désirez peut-être vous rappeler les commandes de gestion de couleurs (car vous ne connaissez pas par coeur la liste des fonctions existantes et vous ne savez pas forcement où chercher): il y a glColorMaterial() qu'utilise glColor() et glMaterial() une combinaison de glColorMaterial() et glColor().

 

Le ViewPort

Tout ce que vous dessinez et envoyé vers le viewport. Cela correspond à une zone de clipping qui lors de l'initialisation d'OpenGL (par exemple via la création de votre fenêtre sous GLUT) a été positionnée sur toute la surface de la zone cliente de la fenêtre. Il est possible de modifier cette zone de clipping en taille et en emplacement. Les précédentes images dessinées ne seront pas impactées par le changement du viewport.

Pour modifier la zone du viewport, vous utiliserez la fonction glViewport()

void glViewport(
GLint x,
GLint y,
GLsizei width,
GLsizei height
);

où x et y est par défaut à (0,0) càd en bas à gauche de la fenêtre.

En sortie:

GL_INVALID_VALUE si width ou height est négatif.

GL_INVALID_OPERATION si compris entre glBegin() et glEnd()

Pourquoi parler de cette fonction ?

Eh bien parce que si vous changeez la taille de votre fenêtre, il peut être intéressant d'en faire de même avec le viewport.

Sous GLUT, le changement de taille de la fenêtre est indiqué uniquement si vous avez codé une procédure de gestion de taille à GLUT.

Pour cela vous passerez par la fonction glutReshapeFunc()

void glutReshapeFunc(void (*func)(int width, int height));

Il faudra donc se créer une fonction du genre:

resize (int width, int height)
{

glViewport (0,0,width,height);

...

}

 

Matrice de projection et matrice de modèle

Un problème:

Mettez ce bout de code entre glBegin() et glEnd() à la place des autres objets:

glColor3ub (0,0,255);
glVertex2d(-1,2);
glVertex2d(1,1);
glVertex2d(1,-1);
glVertex2d(-1,-1);

Un point est de coordonnées (-1,2). Vous ne devriez donc pas obtenir un carré. Pourtant, c'est bien ce que vous voyez, et il occupe la totalité de la fenêtre !

En fait, il ne faut pas oublier que OpenGL est prévu pour travailler en 3D. Pour ne pas être gêné suivant l'angle où vous regardez et la position de l'objet à dessiner, deux matrices ont été créées permettant de gérer des repères indépendants. Une pour la caméra, une pour la construction des objets:

Une matrice de projection: elle permet en fait de gérer ce que vous pouvez voir à travers une caméra. Cette caméra pouvant être positionnée n'importe où dans l'espace.

Une matrice de vue du modèle: elle permet de positionner les objets dans l'espace.

Nous verrons plus tard qu'il en existe une troisième...

 

Pour indiquer la matrice que vous voulez paramétrer, vous allez utiliser la fonction glMatrixMode()

Cette fonction attend un paramètre qui est la matrice à utiliser:

  • GL_PROJECTION pour la matrice de projection.
  • GL_MODELVIEW pour la matrice de vue du modèle.

 

Vous pourrez ensuite réinitialiser la matrice : glLoadIdentity(); En fait, ceci charge la matrice avec les valeurs de la matrice "identité". Il est possible de définir une toute autre matrice via la fonction glLoadMatrix().

Et enfin utilisez les fonctions associées à la matrice en cours. Vous ne pourrez pas mélanger les fonctions, OpenGL ne gérant qu'une matrice à la fois. Vous ne pourrez donc pas dessiner un objet alors que la matrice courante et celle de projection.

 

Où utiliser les matrices ?

La matrice de projection sera initialisée lors du retaillage de notre fenêtre. Ceci afin de faire en sorte que les proportions de votre objet soit toujours les mêmes, même si l'objet diminue ou augmente en taille. Mais il est impératif au moment de l'affichage de réutiliser la matrice MODELVIEW, sinon, vous aurez de gros soucis pour dessiner vos objets !

On initialise d'abord la matrice via glLoadIdentity() et on va redéfinir la perspective.

En 2D, le plus approprié serait

void gluOrtho2D(GLdouble left,
GLdouble right,
GLdouble bottom,
GLdouble top)

left, right, bottom et top, zone de clipping...

(Equivalent à appeler glOrtho() vu tout de suite après avec zNear à 0 et zFar à 1...

Mais comme vous n'allez pas vraiment traîner dans la 2D, je vous présente déjà celle de la 3D:

Si 3D,

Repère orthonormé:

void glOrtho(GLdouble left,
GLdouble right,
GLdouble bottom,
GLdouble top,
GLdouble zNear,
GLdouble zFar)

left, right, bottom et top, zone de clipping...

zNear et zFar indique le clipping sur l'axe z.

Repère avec l'angle précisé:

void gluPerspective(
GLdouble fovy,
GLdouble aspect,
GLdouble zNear,
GLdouble zFar
);

où fovy est une valeur en degré (45 est bien).

aspect est en fait le ratio entre la largeur et la hauteur de votre fenêtre cliente. ce qui permettra de ne pas avoir une déformation en hauteur ou largeur de votre objet lorsque vous retaillerez votre fenêtre (Les proportions de votre objet seront ainsi gardées).

zNear et zFar indique le clipping sur l'axe z.

Image non trouvée !zNear et zFar sont toujours > 0 !

Suite au glLoadIdentity(), la caméra est positionnée en plein sur le plan utilisé par le modèle de vue pour afficher vos objets 2D, il sera donc impossible de voir vos objets, la caméra visualisant tout ce qui se trouve au-delà de ce plan (zNear > 0 !). Il vous faut donc reculer la caméra pour voir quelque chose.

D'où la fonction gluLookAt()

void gluLookAt(
GLdouble eyex,
GLdouble eyey,
GLdouble eyez,
GLdouble centerx,
GLdouble centery,
GLdouble centerz,
GLdouble upx,
GLdouble upy,
GLdouble upz
);

Les trois premières valeurs sont les coordonnées du point de vue (où se trouve la caméra), les trois suivantes, l'endroit où on regarde avec la caméra. Enfin les trois dernières correspondent à la verticale de la caméra (où encore comment vous tenez votre caméra dans les mains: 0, 1, 0 correspondant à une caméra tenu normalement, mais rien ne vous empêche de tenir votre caméra couchée, la partie basse vers le haut, voire l'objectif vers vous, mais dans ce cas, vous n'allez pas voir grand chose !)

Image non trouvée !Sachant que l'on ne doit pas avoir une parallèle entre le vecteur des yeux et le vecteur up (ne me demandez pas pourquoi !)

Image non trouvée !Cette fonction ne correspond absolument pas à une homothétie sur notre objet, celui-ci n'est en rien impacté ! On se contente de changer de point de vue (tout est relatif ...).

Image non trouvée !En infographie, le repère est inversé en Z par rapport à ce que l'on utilise habituellement en mathématique.

Image non trouvée !

Exemple d'utilisation:

case WM_SIZE:

glViewport (0,0,LOWORD (lParam),HIWORD (lParam));

glMatrixMode(GL_PROJECTION); glLoadIdentity();

gluPerspective(45,float(LOWORD (lParam))/float(HIWORD (lParam)),0.1,100);

gluLookAt(0,0,-5.0f,0,0,0,0,1,0);

return 0;

 

Exemple:

Image non trouvée !

Voici un exemple qui récapitule ce que je viens de dire jusqu'à présent. On trouvera un code qui fait le yo-yo avec la caméra...

Pour pouvoir faire le yo-yo, il faut pouvoir lancer un traitement qui va gérer la position de la caméra.

Dans mon cas, je vais indiquer à notre fenêtre le traitement à lancer s'il n'y a pas de message à gérer.

Pour cela, j'utilise la fonction glutIdleFunc(void (*func)(void))

Image non trouvée !Dans ce code, simplicité oblige, il n'y a pas de gestion automatique de la vitesse. L'incrément se fera via une variable prépro. INCREMENT. Suivant la puissance de votre pc, il faudra donc augmenter (machine trop lente) ou diminuer (machine trop rapide) la valeur de cette variable pour avoir un affichage correct.

 

Tracer des points

Si vous essayez de tracer des points à l'écran en utilisant l'option GL_POINT (vu précédemment), vous aurez l'impression que cela ne marche pas...

En fait, bien qu'en mathématique, un point n'ait pas de taille, ici, pour le voir, il faudra en définir une !

Et par défaut, cette taille est plutôt petite...vous allez donc devoir la changer.

void glPointSize( GLfloat size)

En entrée:

size est le diamètre du point...quoique vous verrez plutôt un carré qu'un cercle...

En sortie:

GL_INVALID_VALUE si la valeur est inférieure ou égale à 0.

GL_INVALID_OPERATION si vous mettez la commande entre glBegins et glEnd

 

Vous pourrez améliorer la qualité du tracé en utilisant l'anti-aliasing qui est désactivé par défaut

glEnable (GL_POINT_SMOOTH)

ou pour le désactiver glDisable (GL_POINT_SMOOTH)

glIsEnable (GL_POINT_SMOOTH) pourra vous indiquer l'activation de l'anti-aliasing.

 

Récupération d'informations sur la gestion du point

Il est à noter que la taille d'un point est limitée. Ces limites peuvent être récupérées en utilisant la fonction glGet (ou l'une de ses dérivés).

Les valeurs minimales et maximales possibles pour la taille d'un point peuvent être récupérées en utilisant l'argument GL_POINT_SIZE_RANGE et en passant un tableau de deux éléments (entiers ou flottants) pour récupérer la valeur minimale dans le premier élément puis maximale dans le second élément.

La granularité est un nombre qui indique la valeur minimale à ajouter à la taille courante d'un point pour voir effectivement grandir le point. En effet, si vous ajoutez une valeur inférieure à celle de la granularité à la taille actuelle de votre point, vous ne le verrez pas grandir. Pour connaître cette granularité, il faudra passer l'argument GL_POINT_SIZE_GRANULARITY, et une variable de type entier ou flottant.

Enfin, pour connaître la taille actuellement utilisée pour dessiner un point, vous utiliserez GL_POINT_SIZE. La taille par défaut d'un point est à 1.0f

Image non trouvée !

 

Tracé de droites

Là aussi, vous pourrez définir l'épaisseur d'une ligne, en utilisant glLineWidth( GLfloat size). Là aussi la fonction glGet pourra retourner les valeurs minimales ou maximales des tailles disponibles GL_LINE_WIDTH_RANGE, la granularité GL_LINE_WIDTH_GRANULARITY ou encore la taille actuelle GL_LINE_WIDTH.

De même, il sera possible d'activer ou de désactiver l'anti-aliasing: glEnable (GL_LINE_SMOOTH) ou pour le désactiver glDisable (GL_LINE_SMOOTH). glIsEnable (GL_LINE_SMOOTH) pourra vous indiquer l'activation de l'anti-aliasing.

Vous pourrez aussi définir un motif pour le tracer de la ligne. Il faut tout d'abord activer l'option par glEnable (GL_LINE_STIPPLE) (glDisable pour le désactiver, mais je pense que vous aviez deviné !)

Puis, pour définir le motif, vous utiliserez la fonction

void glLineStipple( GLint factor, GLushort pattern )

Avec factor qui est l'échelle à utiliser sur le motif

pattern qui est le motif

En sortie:

GL_INVALID_OPERATION si la fonction est appelée entre glBegin et glEnd.

Image non trouvée !Il est possible de mémoriser dans une pile les motifs avant d'utiliser la fonction glLineStipple().

Vous utiliserez glPushAttrib (GL_LINE_BIT) pour le mémoriser dans la pile, glPopAttrib () pour le restaurer (oui, sans attribut).

Image non trouvée !

 

Il y encore des choses à dire pour faire de la 2D, comme par exemple l'application d'une texture, l'affichage de texte. Vous pourriez aussi rajouter de l'éclairage pour faire des effets sympathiques en 2D, mais tout ceci sera étudié (bien sûr) mais en 3D cette fois, sachant qu'il ne sera pas bien difficile de le reproduire en 2D. En attendant, vous voici avec de bonnes bases pour attaquer la 3D !

 

Annexes

OpenGL pour Linux, Windows, Android...