Dessiner des lignes
La dernière fois nous avions parlé des rudiments de la construction de polygones avec OpenGL. OpenGL ne supporte qu'un petit nombre de primitives géométriques : points, lignes, polygones et surfaces décrites par un tableau de petit quadrilatères ou triangles.
L'idée derrière cette simplicité est de laisser toute liberté au programmeur pour développer des objets complexes à partir de ces objets simples. OpenGL contient un certain nombre de commandes pour contrôler le détail des points, lignes et polygones.
Par exemple la taille d'un point peut-être spécifié en pixels en utilisant glPointSize:
void glPointSize(GLfloat size)
Par défaut la taille d'un point est de 1.0 et elle est toujours supérieure à zéro. Vous remarquerez que la taille d'un point est donnée par un nombre réel; les portions de point et de ligne sont autorisées. OpenGL interprète les portions de point en fonction du contexte de rendu. Si l'anti-crénelage est actif alors OpenGL modifie les pixels voisins de la ligne pour lui donner l'apparence de la largeur spécifiée. L'anti-crénelage est une technique visant à éliminer les gros carrés que l'on voit lorsque l'on dessine des lignes à basse résolution. Si l'anti-crénelage n'est pas actif, glPointSize arrondira la taille à l'entier le plus proche.
La taille physique d'un pixel est en fait dépendante du matériel. Par exemple à basse résolution un pixel apparaît très large. Respectivement à haute résolution la ligne de largeur 1.0 (valeur par défaut) peut être quasiment invisible (phénomène que l'on rencontre aussi sur certaines imprimantes) . Pour estimer la taille réelle de vos pixels il vous faut connaître les dimensions d'un pixel sur votre matériel.
La largeur des lignes est spécifiée par la fonction glLineWidth qui doit être appelée avant le bloc glBegin() - glEnd()qui dessine la (les) ligne(s). Voici la syntaxe complète de la fonction :
void glLineWidth(GLfloat width)
Il se peut que l'implémentation d'OpenGL limite la largeur des lignes sans anti-crénelage à la valeur maximale de la largeur avec anti-crénelage (arrondi à l'entier le plus proche). Gardez également en mémoire que la largeur d'une ligne n'est pas mesurée perpendiculairement à celle-ci, mais dans la direction des Y si la valeur absolue de la pente est inférieure à 1, dans la direction des X dans le cas contraire.
Ce mois-ci nous avons préparé une autre animation 2D simple, mais je l'espère utile, qui montre comment utiliser différentes largeurs de lignes dans votre application OpenGL (../../common/March1998/example2.c, ../../common/March1998/Makefile). J'ai choisi un exemple tiré de la physique quantique : une particule piégée dans un double puits de potentiel. Pourquoi ? à dire vrai,..., j'ai oublié. Quoiqu'il en soit je pense qu'il peut-être utile pour un étudiant en sciences physiques ou en école d'ingénieurs de voir l'intégration de l'équation temporelle de Shroedinger, les autres pourront tout de même apprécier et contempler les phénomènes inhabituel de la physique quantique. Une particule en mécanique quantique n'est pas représentée par une position et une vitesse, mais par une onde, une onde quantique (la ligne en trait plein de couleur violette sur notre animation) dont la valeur absolue au carré représente la probabilité de présence à une position donnée (ligne blanche en pointillés) :
Figure 1. Simulation Quantique
Pour ce qui ont eu des cours sur les équations différentielles j'ajouterai que l'équation est intégrée par une Transformation de Fourier Rapide avec séparation d'opérateur (FFT en anglais), cette méthode est bien plus précise et rapide que n'importe quelle méthode bornée. Elle est applicable à des équations de propagation non linéaire. L'opérateur temporel est différencié en deux opérateurs au deuxième ordre (et plus) en deux opérateurs plus simple dépendant respectivement de la fréquence et de la position. On évalue l'évolution temporelle de l'équation en appliquant successivement les deux opérateurs à +delta et -delta autour des valeurs des espaces de fréquence et de position.
Le corps du programme peut-être utilisé pour d'autres applications. Vous pouvez remplacer ma simulation quantique par n'importe qu'elle autre fonction temporelle et voir l'évolution de votre système. Vous pouvez également écrire une version OpenGL simplifiée de gnuplot pour tracer fonctions et graphiques.
Si le lecteur a suivi les articles précédents sur GLUT et OpenGL, le code de ce programme apparaîtra très simple et facile à
comprendre (mis à part la physique quantique peut-être).
Il n'y a rien d'extraordinaire ici.
Dans la fonction main() nous ouvrons une seule fenêtre un mode double-buffer
(double tampon mémoire peut en être une traduction française),
ensuite nous définissons deux fonctions callback display et
idle qui prennent en charge respectivement l'affichage et
l'intégration de l'équation. Je vous rassure, il n'est pas nécessaire
de comprendre tout ce qui se passe dans la fonction idle(), bien que ce soit une jolie application,
ce n'est pas indispensable pour comprendre le sujet de l'article.
Les choses nouvelles pour nous aujourd'hui sur OpenGL se trouve dans la fonction callback d'affichage (display()) :
void
display (void)
{
static char label[100];
float xtmp;
/* Clean drawing board */
glClear (GL_COLOR_BUFFER_BIT);
/* Write Footnote */
glColor3f (0.0F, 1.0F, 1.0F);
sprintf (label, "(c)Miguel Angel Sepulveda 1998");
glRasterPos2f (-1.1, -1.1);
drawString (label);
/* Draw fine grid */
glLineWidth (0.5);
glColor3f (0.5F, 0.5F, 0.5F);
glBegin (GL_LINES);
for (xtmp = -1.0F; xtmp < 1.0F; xtmp += 0.05)
{
glVertex2f (xtmp, -1.0);
glVertex2f (xtmp, 1.0);
glVertex2f (-1.0, xtmp);
glVertex2f (1.0, xtmp);
};
glEnd ();
/* Draw Outsite box */
glColor3f (0.1F, 0.80F, 0.1F);
glLineWidth (3);
glBegin (GL_LINE_LOOP);
glVertex2f (-1.0F, -1.0F);
glVertex2f (1.0F, -1.0F);
glVertex2f (1.0F, 1.0F);
glVertex2f (-1.0F, 1.0F);
glEnd ();
/* Draw Grid */
glLineWidth (1);
glColor3f (1.0F, 1.0F, 1.0F);
glBegin (GL_LINES);
for (xtmp = -0.5; xtmp < 1.0; xtmp += 0.50)
{
glVertex2f (xtmp, -1.0);
glVertex2f (xtmp, 1.0);
glVertex2f (-1.0, xtmp);
glVertex2f (1.0, xtmp);
};
glEnd ();
/* Draw Coordinate Axis */
glLineWidth (2);
glBegin (GL_LINES);
glVertex2f (-1.0, 0.0);
glVertex2f (1.0, 0.0);
glVertex2f (0.0, -1.0);
glVertex2f (0.0, 1.0);
glEnd ();
/* Axis Labels */
glColor3f (1.0F, 1.0F, 1.0F);
sprintf (label, "Position");
glRasterPos2f (0.80F, 0.025F);
drawString (label);
glColor3f (1.0F, 0.0F, 1.0F);
sprintf (label, " Quantum Probability ");
glRasterPos2f (0.025F, 0.90F);
drawString (label);
glColor3f (1.0F, 1.0F, 1.0F);
sprintf (label, " Real(Psi) ");
glRasterPos2f (0.025F, 0.85F);
drawString (label);
/* Draw Wavefunction */
psiDraw (NR_POINTS, psi, x);
/* Draw potential Function */
potentialDraw (NR_POINTS, potential, x);
glutSwapBuffers ();
};
La première chose à faire est de mettre à zéro le bit codant la couleur de la zone mémoire (buffer color bit), ce qui a pour effet d'effacer la zone d'affichage et nous donne un fond de fenêtre noir. Ensuite nous ajoutons une note en bas de la fenêtre en utilisant glRastePos et glutBitmapCharacter (drawstring n'est rien d'autre qu'une fonction englobant l'appel de plusieurs fonctions OpenGL pour dessiner une chaîne de caractères). Dans de futures leçons glRastePos apparaîtra de nouveau comme une fonction auxiliaire pour placer des textures. Ni OpenGl ni GLUT n'offre la possibilité de dessiner facilement du texte dans une fenêtre. La fonction glutBitmapCharacter affiche uniquement une image 2D représentant un caractère dans la zone mémoire.
Après la note il y a une série de lignes de code : le contour, la grille de fond,
les axes de coordonnées et bien sûr la courbe courante dessinée par
psiDraw et potentialDraw.
Avant chaque dessin de ligne, il y a une commande glLineWidth qui spécifie sa largeur en pixels.
La figure 1 montre la sortie sur un système Xwindow (réalisé sous Linux pour Alpha).
Pour des raisons qui me sont inconnues, le même programme sous Windows95 donne un affichage affreux,
il semble que l'anti-crénelage n'est pas bien supporté par le driver OpenGL de SGI ;
il est alors difficile de différencier les différentes lignes qui en principe devraient
avoir des largeurs différentes, les lignes de la grille à l'arrière plan sont elles aussi mal rendues.
Ces défauts apparaissent en haute résolution donc ce n'est pas un effet parasite dû à une trop basse résolution.
Je suis heureux de proclamer qu'une fois de plus le système Xwindows de Linux surpasse largement win95/NT.
Il y a deux types de lignes dessinés par la fonction display(), le type GL_LINES qui crée une ligne en joignant les différents points mais ne clos pas la courbe, et GL_LINE_LOOP qui ferme le contour.
Des Lignes avec anti-crénelage
J'ai activé l'anti-crénelage pour les lignes dans la fonction callback reshape(),
void
reshape (int w, int h)
{
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glViewport (0, 0, w, h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluOrtho2D (-1.2, 1.2, -1.2, 1.2);
glEnable (GL_LINE_SMOOTH); /* Enable Antialiased lines */
glEnable (GL_LINE_STIPPLE);
};
Qu'est ce que ce GL_LINE_STIPPLE ? OpenGL ne permet pas uniquement de contrôler la largeur d'une ligne, il permet également de fixer un motif. En activant GL_LINE_STIPPLE nous pouvons dessiner des lignes en pointillé ou tout autre motif. La seule ligne ayant un motif se trouve dans psiDraw() :
glLineWidth (1);
glPushAttrib (GL_LINE_BIT);
glLineStipple (3, 0xAAAA);
glBegin (GL_LINE_STRIP);
for (i = 0; i < nx; i++)
{
xs = ratio1 * (x[i] - XMIN) - 1.0;
ys = ratio2 * (psi[2 * i] - YMIN) - 1.0;
glVertex2d (xs, ys);
};
glEnd ();
glPopAttrib ();
Des Motifs pour les lignes
La fonction glLineStipple permet de spécifier le type de motif utilisé. Dans notre exemple, nous utilisons le motif 0xAAAAA. En binaire ce nombre se lit 0000100010001000 et OpenGL interprète cela en dessinant 3 points éteints 1 point allumé, 3 points éteints 1 point allumé, 3 points éteints 1 point allumé, et finalement 4 points éteints. Le motif est lu de droite à gauche car les bits de poids faible sont lus en premier. Vous remarquerez que glLineStipple prend deux paramètres, le motif qui est un nombre hexadécimal et un entier qui sert de facteur d'échelle à notre motif. Aussi avec un facteur de 3 notre ligne sera composé de 9 points éteints 3 points allumés, 9 points éteints 3 points allumés, 9 points éteints 3 points allumés, et finalement 12 points éteints. En jouant sur le motif et le facteur d'échelle, on peut dessiner toute sorte de lignes compliquées.
Encore un détail : j'ai encadré les directives de dessin de ligne par des ordres glPushAttrib et glPopAttrib . Vous rappelez vous que dans le tout premier article sur OpenGL, je vous disais qu'OpenGL était une machine à état ? Dans nos futurs articles nous verrons plus en détail ces ordres push et pop, mais brièvement, ce que nous faisons avec le premier ordre glPushAttrib(GL_LINE_BIT) c'est de mettre dans une pile la valeur courante de la variable d'état GL_LINE_BIT (C'est cette variable qui fixe le motif), ensuite nous modifions cette variable aprés nos appels à glLineStipple et quand nous avons fini nous restaurons l'ancienne valeur en la dépilant par un appel à glPopAttrib. Ce mécanisme constitue un moyen efficace de modifier les valeur des variables d'état d'OpenGL. Si nous n'avions pas fait cela, toutes les lignes dessinées après l'appel à glLineStippleauraient eu le même motif et nous aurions été forcé de déclarer un motif pour chaque ligne dessinée par notre programme. Push et Pop nous évitent ce travail fastidieux
La prochaine fois ....
OpenGL est connu pour sa fantastique API de rendu 3D. Jusqu'à maintenant nous avons exploré quelques possibilités de rendu 2D, La prochaine fois nous nous intéresserons aux scènes 3D, comment définir une perspective, le système de coordonnées, les horizons, les matrices de projections.
D'ici là amusez vous avec OpenGL...
|