Sommaire Index Rechercher Liens A Propos
[LinuxFocus Image]
[Barre de Navigation]
  Nouvelles   Archives

Programmation OpenGL : Rendu de Polygones Simples

par Miguel Angel Sepúlveda


Introduction

Dessiner des Points

Dessiner des Lignes et des Polygones

Exercises

La prochaine fois...

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à. 


Traduit par John Perr

Pour en savoir plus:
© 1998 Miguel Angel Sepúlveda
Ce site Web est maintenu par Miguel A Sepulveda.