Introduction
Ceci est le premier article de la série consacrée à
OpenGL, un standard de l'industrie en matière d'infographie 2 et
3D (voir Qu'est
ce qu' OpenGL). Nous considererons que le lecteur connait son
propre environnement de developpement en C et possède quelques
connaissance de la bibliothèque GLUT (sinon, suivez les articles
consacrés à "la programmation GLUT" dans cette revue. Avec
Linux, nous recommandons l'utilisation de la bibliothèque Mesa qui
est une superbe implantation gratuite d'Open-GL. Il existe meme maintenant
un support materiel pour Mesa (voir Les
cartes Graphiques 3Dfx ).
Chaque présentation d'une nouvelle commande OpenGL sera accompagnée
d'exemples qui essaieront (au moins !) d'exploiter ses fonctionnalités.
A la fin de cette série d'articles, nous présenteront le
code source d'une simulation de jeux completement écrite avec OpenGL.
Avant de commencer, je voudrais attirer l'attention sur le fait que
comme je suis un scientifique, mon expérience avec OpenGL se résume
principalement à écrire des simulations de quantas réels
et de systèmes classiques. Aussi, mes exemples sont orientés
;-). J'espère que les lecteurs trouveront ces exemples accessibles
ou du moins amusants. Si vous etes intéressés par d'autres
exemples, n'hésitez pas à me le faire savoir.
OpenGL est souvent associé avec le graphisme 3D, des beaux effets
spéciaux, des modèles complexes avec des modèles d'éclairages
réalistes etc... Toutefois, c'est aussi une machine de tracé
garphique 2D. Ce point est important car on peut apprendre beaucoup de
choses avec deux dimensions avant d'entrer dans la complexité des
perspectives 3D, le modelage, l'éclairage, le positionnement des
caméras etc ... Un grand nombre d'applications industrielles et
scientifiques, utilise les tracés 2D. Commençons donc par
voir comment vréer de simple animation dans un plan.
Dessiner des Points
OpenGL n'a que quelques primitives géométriques : points,
lignes, polygones. Tous sont décrits par des vecteurs. Un vecteur
est caractérisé par 2 ou 3 points réels, les coordonnées
Cartésiennes du vecteur, (x,y) en 2D et (x,y,z) en 3D. Bien
que les coordonnées Cartésiennes soient les plus courantes
en infographie , il existe aussi le système homogène de coordonnées
dans lequel chaque point est décrit par 4 nombres réels (x,y,z,w).
Nous y reviendrons après avoir introduit des notions de tracés
en 3D.
Puisqu'avec OpenGL, tous les objets géométriques sont
décrit en termes de vecteurs ordonnés, il y a une fammille
de fonctions pour déclarer un vecteur. Sa syntaxe est :
void glVertex{234}{sifd}[v](TYPE coords);
La notation devra vous devenir familière. Les accolades indiques
une partie du nom de la fonction. Cette dernière peut prendre 2,3
ou 4 parametres soit de type entiers, entiers longs, réels ou doubles.
Ces parametres peuvent etre fournit optionnellement sous forme de vecteurs,
auquel cas, le nom de la fonction comportera un v. Voici quelques exemples
:
void glVertex2s(1, 3);
void glVertex2i(23L, 43L);
void glVertex3f(1.0F, 1.0F, 5.87220972F);
float vector[3];
void glVertex3fv(vector);
A titre de simplification, toutes ces fonctions sont appellées
glVertex*.
OpenGL interprètes une séquence quelconque de vecteurs
selon son contexte. Le contexte est déclaré par la paire
de fonctions glBegin(GLenum mode) et glEnd().
Toute instruction glVertex* executée entre
les deux est interprétée en fonction de la valeur de mode,
par exemple:
glBegin(GL_POINTS);
glVertex2f(0.0, 0.0);
glVertex2f(1.0, 0.0);
glVertex2f(0.0, 1.0);
glVertex2f(1.0, 1.0);
glVertex2f(0.5, 0.5);
glEnd();
dessine 5 points en 2D avec les coordonnées spécifiées.
GL_POINTS est une des constantes définies dans le fichier d'entete
<GL/gl.h>. Il existe de nombreux autres modes disponibles mais
nous les décrirons au fur et à mesure des besoins.
Chaque point est tracé avec la couleur mémorisée
par la variable d'état OpenGL associée au tampon des couleurs.
Pour changer de couleur, on utilise la famille de fonctions glColor*;
il y a beaucoup à dire sur la sékection et la manipulation
des couleurs (ceci fera l'objet d'un article séparé). Pour
l'instant, nous utiliserons 3 nombres réels variants de 0.0 à
1.0. C'est le codage RVB (Rouge-Vert-Bleu):
glColor3f(1.0, 1.0, 1.0); /* White */
glColor3f(1.0, 0.0, 0.0); /* Red */
glColor3f(1.0, 1.0, 0.0); /* Magenta */
etc...
Download:
../../common/January1998/Makefile, ../../common/January1998/../../common/January1998/example1.c, ../../common/January1998/../../common/January1998/example2.c
Nous en savons déjà assezpour écrire deux premiers
exemples de code. Le premier est un programme OPenGL simple qui trace un
nombre donné d'orbites dans une projection chaotique (La projection
standard). Cela n'a pas d'importance si vous n'etes pas habitués
aux projections et à la projection standard en particulier.
En termes simples, une projection, prend un point et en génère
un autre à partir d'une formule précise:
yn+1 = yn + K sin(xn)
xn+1 = xn + yn+1
Dans le cas de la projection standard, il représente la trace
que laisse une particule chargée qui décrit des cercles autour
d'un tore d'un accélérateur de particules et en traverse
une section plane. L'étude de ces propriétés et d'autres
projections est importante en physique car elle nous aide à comprendre
la stabilité de la particule chargée confinée dans
le cyclotron.
La projection standard est plaisante parce que pour certaines valeur
du paramètre K, elle montre clairement un mélange de mouvement
chaotiques et captifs. Finalement, meme ceux qui ne sont pas vraiment intéressés
par la physique mais plutot par de beaux codes graphiques devraient se
pencher sur les projections et leurs propriétés. Beaucoup
d'algorithmes de génération de textures, feux d'artifices,
arbres, terrains, etc... sont basés sur les projections fractales.
Voici le code ../../common/January1998/../../common/January1998/example1.c:
#include <GL/glut.h>
#include <math.h>
const double pi2 = 6.28318530718;
void NonlinearMap(double *x, double *y){
static double K = 1.04295;
*y += K * sin(*x);
*x += *y;
*x = fmod(*x, pi2);
if (*x < 0.0) *x += pi2;
};
void winInit(){
/* Set system of coordinates */
gluOrtho2D(0.0, pi2, 0.0, pi2);
};
void display(void){
const int NumberSteps = 1000;
const int NumberOrbits = 100;
const double Delta_x = pi2/(NumberOrbits-1);
int step, orbit;
glColor3f(0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
for (orbit = 0; orbit < NumberOrbits; orbit++){
double x, y;
y = 3.1415;
x = Delta_x * orbit;
glBegin(GL_POINTS);
for (step = 0; step < NumberSteps; step++){
NonlinearMap(&x, &y);
glVertex2f(x, y);
};
glEnd();
};
for (orbit = 0; orbit < NumberOrbits; orbit++){
double x, y;
x = 3.1415;
y = Delta_x * orbit;
glBegin(GL_POINTS);
for (step = 0; step < NumberSteps; step++){
NonlinearMap(&x, &y);
glVertex2f(x, y);
};
glEnd();
};
};
int main(int argc, char **argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
glutInitWindowPosition(5,5);
glutInitWindowSize(300,300);
glutCreateWindow("Standard Map");
winInit();
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
Lisez s'il vous plait, l'article Programmation
de GLUT pour comprendre les fonction glut*. La majorité de ceprogramme
viens de là. La fenetre graphique est ouverte en mode tampon simple
et RGB. Une fonction de rappel nommée display()
trace la projection: d'abord nous sélectionnons la couleur noire
pour le fond; glClear(GL_COLOR_BUFFER_BIT) initialise le tampon de couleurs
à la couleur courante (noir), puis après avoir choisi la
couleur blanche avec glColor, nous lançons la fonction NonlinearMap()
plusieurs fois et traçons les points avec glVertex* en mode
GL_POINTS. Vraiment simple.
Remarquer que dans la fonction d'initialisation winInit()
il n'y a qu'une seule instruction du la bibliothèque d'utilitaires
OpenGL, gluOrtho2D().
Cette fonction établit un système de coordonnées
2D orthogonal. Les paramètres sont "x minimum, x maximum,
y minimum, y maximum".
j'ai choisi une fenetre mono-mode et un grand nombre de points
afin que vous ayez une chance de voir l'image se dessiner. Il est usuel
qu' avec le mono-mode et de grandes images forte consommatrice de temps
machine, les choses apparaissent à l'écran au fur et à
mesure qu'elles sont traitées par les fonctions OpenGL.
L'exécution de l'exemple 1 donne l'image suivante :
Passons maintenant au second programme, ../../common/January1998/../../common/January1998/example2.c:
#include <GL/glut.h>
#include <math.h>
const double pi2 = 6.28318530718;
const double K_max = 3.5;
const double K_min = 0.1;
static double Delta_K = 0.01;
static double K = 0.1;
void NonlinearMap(double *x, double *y){
/* Standard Map */
*y += K * sin(*x);
*x += *y;
/* Angle x is module 2Pi */
*x = fmod(*x, pi2);
if (*x < 0.0) *x += pi2;
};
/* Callback function:
What to do in absence of use input */
void idle(void){
/* Increase the stochastic parameter */
K += Delta_K;
if(K > K_max) K = K_min;
/* Redraw the display */
glutPostRedisplay();
};
/* Initialization for the graphics window */
void winInit(void){
gluOrtho2D(0.0, pi2, 0.0, pi2);
};
/* Callback function:
What to do when the display needs redrawing */
void display(void){
const int NumberSteps = 1000;
const int NumberOrbits = 50;
const double Delta_x = pi2/(NumberOrbits-1);
int step, orbit;
glColor3f(0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
for (orbit = 0; orbit < NumberOrbits; orbit++){
double x, y;
y = 3.1415;
x = Delta_x * orbit;
glBegin(GL_POINTS);
for (step = 0; step < NumberSteps; step++){
NonlinearMap(&x, &y);
glVertex2f(x, y);
};
glEnd();
};
for (orbit = 0; orbit < NumberOrbits; orbit++){
double x, y;
x = 3.1415;
y = Delta_x * orbit;
glBegin(GL_POINTS);
for (step = 0; step < NumberSteps; step++){
NonlinearMap(&x, &y);
glVertex2f(x, y);
};
glEnd();
};
glutSwapBuffers();
};
int main(int argc, char **argv) {
/* GLUT Initializations */
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(5,5);
glutInitWindowSize(300,300);
/* Open Window */
glutCreateWindow("Order to Chaos");
/* Window initializations */
winInit();
/* Register callback functions */
glutDisplayFunc(display);
glutIdleFunc(idle);
/* Launch event processing */
glutMainLoop();
return 0;
}
Ce programme est basé sur ../../common/January1998/../../common/January1998/example1.c,
la différence principale réside dans l'ouverture de la fenetre
en mode double tampon. De plus, le paramètre K évolue pendant
la vie du programme. Il ya une nouvelle fonction de rappel idle()
qui est déclarée au gestionnaire d'évenements GLUT
par glutIdleFunc(). Cette fonction a une signification spéciale;
elle est appelée régulièrement par le gestionnaire
d'évenement tant qu'il n'y a pas d'action utilisateur. La fonction
de rappel idle() est idéale pour programmer des animations.
Dans l'exemple 2, elle sert à modifier légèrement
les valeur du paramètre de projection. A la fin d' idle()
il y a une autre instruction GLUT utile, glutPostResDisplay()
qui redessine la fenetre en préservant les initialisations précédentes.
En général, c'est plus efficace que d'appeler une nouvelle
fois display().
Une autre différence remarquable est l'utilisation de glutSwapBuffers()
à la fin de display(). La fenetre a été
initialisée en mode double-tampon, en conséquence, toutes
les instruction de tracés sont appliquées au tampon caché;
l'utilisateur ne peut pas voir l'image se dessinner dans ce cas. Une fois
que l'image complète est terminée, elle est rendue visible
en inversant les tampons avec glutSwapBuffers(). Sans cette technique,
l'animation n'est pas fluide.
Voici quelques images dessinnées pendant l'animation :
IMPORTANT: La fonction de rappel display() est
toujours appelée au moins une fois, avant idle(). N'oubliez pas
ceci en écrivant vos animamtions et au moment de répartir
ce qui va dans display() et idle().
Drawing Lines and Polygons
Download:
../../common/January1998/../../common/January1998/example3.c
Nous avons déjà mentionné que glBegin(GLenum
mode) accepte divers modes et que les séquences de vecteurs
v0, v1,v2, v3,v4,...
vn-1 déclarés à postérioi sont interprétés
en conséquence. Les valeurs possibles du mode et les actions associées
sont :
-
GL_POINTS Trace un point pour chaqun des n vecteurs.
-
GL_LINES Trace une série de
lignes non connectées. Les segments sont tracés entre
v0 et v1, v2 et v3,...etc.
Si n est impair vn-1 est ignoré.
-
GL_POLYGON Trace un polygone avec v0, v1,..,vn-1
commes vecteurs. n doit etre au moins égal à 3 ou rien
n'est dessiné. De plus, le polygone ne peut se croiser lui et doit
etre convex à cause de limitations (dues aux algorithmes).
-
GL_TRIANGLES Trace une serie de triangles avec les vecteurs
v0, v1 et v2, puis v3, v4
et v5 etc. Si n n'est pas un multiple de 3, les points restants
sont ignorés.
-
GL_LINE_STRIP Trace une ligne de v0 à
v1, puis de v1 à v2 et ainsi de
suite. Finalement de vn-2 à vn-1 pour un total
de n-1 segments. Il n'y a pas de restrictions sur les vecteurs décrivants
les tronçons de lignes, les lignes peuvent arbitrairement se croiser.
-
GL_LINE_LOOP Identique à GL_LINE_STRIP excepté
qu'un segment final est dessiné de vn-1 à v0,
pour fermer la boucle.
-
GL_QUADS Dessine une serie de quadrilatères avec
les vecteurs v0, v1, v2, v3
et v4, v5, v6, v7 et ainsi
de suite.
-
GL_QUAD_STRIP Dessine une serie de quadrilatères
avec les vecteurs v0, v1, v3, v2
puis v2, v3, v5, v4 et ainsi
de suite.
-
GL_TRIANGLE_STRIP Dessine une serie de triangles avec
les vecteurs v0, v1, v2, puis v2,
v1, v3, puis v2, v3, v4,
etc. L'ordre permet de s'assurer que les triangles ont l'orientation correcte
et l'a bande peut etre utilisée pour former une partie de la surface
-
GL_TRIANGLE_FAN Similaire à GL_TRIANGLE_STRIP
excepté que les triangles sont v0, v1, v2,
puis v0, v2, v3, puis v0, v3,
v4, et ainsi de suite. . Tous les triangles ont v0
en commun.
Dans notre troisième exemple, une autre animation, nous utilisons
GL_LINES et GL_POLYGON. Compilez le program, puis regardez le code source
et analyser sont fonctionnement. Il est globalement très similaire
à ../../common/January1998/../../common/January1998/example2.c. L'image tracées est un pendule très
simple. L'animation simule le mouvement d'un pendule idéal. Voici
une capture de l'animation :
Comme précedemment, il ya une fonction de rappel idle()
dont le but est de maintenir l'orloge animée (en mettant à
jour la variable time). display() trace deux objets;
la corde et le poids du pendule (en white et red respectivement). Le mouvement
des coordonnées du pendule est implicite dans les formules de xcenter
et ycenter:
void display(void){
static double radius = 0.05;
const double delta_theta = pi2/20;
double xcenter , ycenter;
double x, y;
double theta = 0.0;
double current_angle = cos(omega * time);
glColor3f(0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
/* Draw pendulum cord */
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_LINES);
glVertex2f(0.0, 0.0);
xcenter = -cord_length * sin(current_angle);
ycenter = -cord_length * cos(current_angle);
glVertex2f(xcenter, ycenter);
glEnd();
/* Draw pendulum dish */
glColor3f(1.0, 0.0, 0.0);
glBegin(GL_POLYGON);
while (theta <= pi2) {
x = xcenter + radius * sin(theta);
y = ycenter + radius * cos(theta);
glVertex2f(x, y);
theta += delta_theta;
};
glEnd();
glutSwapBuffers();
};
Exercises
Voici des suggestions pour pratiquer ce que vous avez déjà
appris :
-
Dans ../../common/January1998/../../common/January1998/example1.c essayez d'autres projections.
Allez à la bibliothèque et prenez n'importe quel livre sur
le chaos et les fractales, vous y trouverez certainement de nombreux examples.
Changez les paramètres, les systèmes de coordonnées
et appliquez plusieurs projections avant de dessiner les points. Amusez
vous bien.
-
Dans ../../common/January1998/../../common/January1998/example2.c vous
pouvez ajouter des couleurs à chacun des points. Par exemple, un
codage de couleurs très interressant pourrait etre basé sur
la stabilité locale de l'orbite
(Physics
Review Letters Vol 63, (1989) 1226) , quand la trajectoire traverse
une région chaotique, elle devient rouge tandis que dans des régions
plus stables, elle pourait devenir bleue. Si vous codez cet effet, la nature
fractale de cet exemple deviendra évident. Pour ceux d'entre vous
qui n'ont pas de connaissance des équations différentielles,
cela peut paraitre ardu, mais cela vaut la peine d'en savoir plus si vous
voulez tirer profit des projections et des fractales dans vos graphiques
numériques.
-
Dans ../../common/January1998/../../common/January1998/example3.c ,
essayez de changer les types de lignes utilisés pour le dessins
du disque. Utilisez GL_LINES, GL_TRIANGLES, etc.. Regardez ce qui se passe.
Essayez d'optimiser la génération du disque. Il n'est pas
nécessaire d'évaluer les sinus et cosinus si souvent que
cela pour dessinner le meme disque dans chaque image, vous pourriez le
stocker dans un tableau. En utilisant des polygones, essayez d'attacher
des boites, des diamants ou n'importe quoi d'autre au bout du pendule.
Concevez deux pendules par image, qui bougent indépendamment ou
mem qui se rencontrent.
La prochaine fois...
C'est tout pour aujourd'hui. Il reste encore beaucoup de choses à
dire sur les polygones. Dans le prochain numéro, (March 1998)
nous continuerons à explorer les polygones et nous traiteront en
détail certaines des commandes que vous connaissez déjà.
|