Opengl-glut: création d'une fenetre et rotation d'une pyramide 3d

Ce tutoriel part du début: si vous n'avez jamais fait d'OpenGL et que vous êtes intéressé, il peut être intéressant pour vous!!
Pour avoir le code du projet, regardez la source suivante: http://codes-sources.commentcamarche.net/source/36790-rotation-pyramide-3d-coloree-opengl-glut

Bonne continuation!!!

Principes de GLUT

Si vous n'avez jamais fait encore d'OpenGL sur Visual Studio.NET, lancez ce Wizard fait par NeHe pour qu'il installe tout le nécessaire : http://www.sourcecodeprojects.com/1122044/

Puis, mettez le fichier de GLUT(glut.h) que vous pouvez trouver ici : http://www.xmission.com/~nate/glut/glut-3.7.6-bin.zip dans le dossier Program Files\MicrosoftVisual Studio 8\VC\PlatformSDK\Include\gl\ et vous serez capables de programmer en glut sur Visual Studio !!!

Lancez Visual studio et créez un nouveau projet (empty project) ou ouvrez le projet que je vous envoie.

Si votre projet est nouveau,allez dans les options du projet et dans configuration properties/linker/input, tapez : glu32.lib opengl32.lib glut32.lib dans additional dependencies.
Toujours dans linker, allez dans debugging et dans `generate debug info',choisissez Yes(\DEBUG) ettout en bas, choisissez : Runtime tracking anddisable optimizations (/ASSEMBLYDEBUG). Si vous ne voulez pas que la console s'affiche lorsque vous lancez votre programme, ajoutez la ligne : /SUBSYSTEM:WINDOWS/ENTRY:mainCRTStartup dans `commandline' de `linker'.

La première étape consiste en la déclaration et implémentation de main(), qui se placera à la fin du code, ou après la déclaration des prototypes. Avec GLUT, il faut utiliser un main()normal et non pas WinMain(), contrairement à l'OpenGL normal sous Windows.

Analysons cette fonction :

 
int main(int arg, char **argc)
{
      //ici, on vainitialiser les propriétés de la fenetre que l'on va créer
      //commen on vaafficher (le '|' permet d'utiliser plusieurs éléments dans
      //un seulargument grace au code binaire: chaque élément a le '1' dans une rangéé,
      //et avec '|'(or), plusieurs rangées auront des 1 pour avoir plusieurs fonctionnalités
      glutInitDisplayMode( GLUT_DOUBLE |GLUT_DEPTH | GLUT_RGBA | GLUT_MULTISAMPLE );
      //ici, ondéfinit les coordonnées de la position de la fenetre dans l'écran
    glutInitWindowPosition( 0, 0 );
      //ici, la taillede la fenetre
    glutInitWindowSize( 800, 600 );

      //ici oninitialize le tout par rapport aux ressources (args) et on crée la fenetre
      glutInit(&arg,argc);
      glutCreateWindow("première fenetre glut");
     
      //ici, on appelle la fonction InitGL(), que l'on va implémenter plus tard
      // et qui vacontenir toutes les initialisations de l'opengl: emplacement des textures,
      //des 'lights',du 'blending' etc...
      InitGL();
     
      //ici, on dit àglut quelles fonction il devra utiliser dans la boucle principale (glutMainLoop())

      //en mettantcomme argument le pointeur de la fonction écrite. Elles seront appelées dans laboucle
      //principalequand il y en aura besoin
      glutDisplayFunc(DrawGLScene); //fonctiond'affichage: c'est le code principal de votre programme
      glutReshapeFunc(glutResize); //appelée en cas de'resize' de la fenetre
      glutKeyboardFunc(keydown);         //appeléequand l'utilisateur appuie sur une touche normale
      glutKeyboardUpFunc(keyup);         //relache latouche normale
      glutSpecialFunc(specdown);         //appuie surune touche spéciale (F1, flèches...)
      glutSpecialUpFunc(specup);         //relache latouche spéciale
 
      glutMainLoop();                    //boucle du jeu

      return 1;
}

Maintenant, on va regarder le code de InitGL(), qui est déjà assez commentée ;). Cette fonction lancée au début du programme sert à initialiser et régler tous les paramètres OpenGL que l'on va utiliser pendant le programme. Par exemple, si on veut mettre en place une lumière, ou le alphablending, effacer l'écran, mettre en place une display list etc.

Les fonctions de couleurs comme par exemple glClearColor() et glColor3f() utilisent comme arguments la quantité de chaque couleur :le rouge, le vert et le bleu (RGB) et parfois RGBA où le A correspond au niveau alpha (la transparence).

glColor3f(float red, float green, float blue) ;

les arguments de cette fonction sont des `float' qui vont de 0.0f à 1.0f où 0.0f est le plus foncé et 1.0f le plus clair. Pour ne pas gaspiller du temps à faire des conversions implicites, on met directement le`.0' qui différencie des `int' et le `f' qui différencie des `double'. En changeant les arguments de glClearColor(), vous pouvez vous amuser à changer la couleur de le fenêtre !

 
int InitGL(GLvoid)                                                          // All Setup For OpenGL Goes Here
{
      //glShadeModel(GL_SMOOTH);                                       //Enable Smooth Shading
      //glClearColor(0.0f, 0.0f, 0.0f, 0.5f);                    // Black Background
      //glClearDepth(1.0f);                                                  //Depth Buffer Setup
      //glEnable(GL_DEPTH_TEST);                                       //Enables Depth Testing
      //glDepthFunc(GL_LEQUAL);                                              //The Type Of Depth Testing To Do
      //glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
      glMatrixMode(GL_PROJECTION);
   // Set the persepctive. View angel 45, aspect ration 1, near plance 1
   // and the far clipping plane at 200.  The last two mean to start drawing
   // from 1 unit in front of you up to 200 units far.
  gluPerspective(45, 1.3, 0.1, 200.0);
 
   // Change to the modelview matrix.
   glMatrixMode(GL_MODELVIEW);
   glClearColor(0.0f, 0.0f, 0.0f, 0.5f); //Cette fonction permetd'effacer
                                         //l'écran avec la couleur spécifiée
      returnTRUE;                                                           // Initialization Went OK
}

L'utilisation d'un type de retour `int' peut être utile pour vérifier le fonctionnement de cette fonction : dans main, on pourrait mettre :

if ( !InitGL()){
      MessageBox(...) ;  //pour dire où est le problème
      return 0 ;        //sortir de main() et l'exécution du programme
}

glutResize() est une sorte de InitGL, qui remet en place les effets, la perspective etc. lorsqu'on modifie les dimensions de la fenêtre.

void glutResize(int width, int height)
{
      glViewport(0,0, width, height);
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();

      /* modify this line to change perspective values */
      gluPerspective(45.0,(float)width/(float)height,1.0, 300.0);
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
}

Maintenant, nous allons passer aux autres fonctions CALLBACK, c'est-à-dire les fonctions qui sont prises en compte par glutMainLoop() après les avoir spécifiées dans les fonctions du type glut...Func(void (*)(/*arguments nécessaires à respecter*/) func) ;

Les fonctions que j'ai appelées keydown() et keyup() sont celles qui s'occupent de l'appui de touches avec un code ASCII, c'est-à-dire les lettre, chiffres, entrée, ESC etc... sauf les flèches, les fonctions spéciales(F1, F2...) et d'autres. J'ai déclaré en variable globale un tableau de 256 éléments de type `bool' pour mettre cette touche en `true' lorsque l'utilisateur appuie sur la touche et en `false' quand il la relâche, comme ça il pourra appuyer sur plusieurs touches en même temps. Si vous avez besoin d'un appui simple sur une touche (donc qu'il suffit d'appuyer une seule fois, par exemple pour changer le statut de la lumière quand on appuie) il vaut mieux de mettre directement un switch(key) dans la fonction et y spécifier ce qu'il faut faire. Ceci attendra un peu après l'appui pour éviter que le changement lumière/pas lumière ne soit pas trop rapide et difficile à bien gérer...

De même, pour specdown() et specup(), sauf que les touches sont des touches spéciales (F1, F2, flèches, pgDown etc...). Ici, le tableau que j'ai déclaré est skeys[].

Pour ensuite accéder aux touches, vous ferez par exemple : if (keys[`l']) {...} ou if ( !keys[`l']) {...}. Dans windows.h,il y a des macros (#define) pour les touches normales : par exemple keys[VK_ESC]. Pour les spéciales, il y a des macros : GLUT_KEY_F1 ouGLUT_KEY_LEFT etc...

Dans le tableau skeys, je ne sais pas vraiment le nombre de touches qu'il y a donc j'ai mis 256 éléments :o|.

Les types des arguments sont à respecter !! dans la keyboardFunc, key est un unsigned char alors que dans specialFunc ununsigned int !! Les autres arguments x et y correspondent aux coordonnées de la positions de la souris au moment de l'appui.

void keydown(unsigned char key, int x, int y)
{
      keys[key] = true;
      switch(key)
      {
        case VK_ESCAPE: exit(0); break;
      }
}

void keyup(unsigned char key, int x, int y)
{
      keys[key] = false;
}

void specdown(int key, int x, int y)
{
      skeys[key] =true;
      switch(key)
      {
      case GLUT_KEY_F1:
            glutFullScreen();
      }
}

void specup(int key, int x, int y)
{
      skeys[key] =false;
      switch(key)
      {
      }
}

Nous passons maintenant à la displayFunc, que j'ai appelée DrawGLScene() . C'est la fonction appelée à chaque boucle de jeu et où l'on dessine le tout. glLoadIdentity() sert à remettre à zéro la matrice et annuler toute translation ou rotation exécutée préalablement. glutSwapBuffers()sert à changer de buffer : il y a 2 buffers (back buffers), quand l'un travaille, l'autre affiche : ce sont des tampons mémoire qui s'alternent.

void DrawGLScene(GLvoid)                // Here's Where We Do All The Drawing
{
      glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);  // Clear Screen And Depth Buffer
      glLoadIdentity();       //on 'reset' lamatrice de l'écran: origine au centre de la fenetre

      glutSwapBuffers();           //changement des buffers

      glutPostRedisplay();    //reaffiche leplan
}

Et voilà !!! Notre fenêtre est créée et il nous reste plus qu'à dessiner !!!

Premiers affichages

Dans ce chapitre, on va commencer à dessiner dans la fenêtre.

Pour dessiner des triangles et d'autres polygones, on utilise le système glBegin() ; glEnd() ; . Entre ces parties de code,nous allons dessiner en utilisant des Vertex. Voici un exemple :

glBegin(GL_TRIANGLES);
      glVertex3f( 0.0f, 1.0f, 0.0f);
      glVertex3f( 1.0f, 1.0f, 0.0f);
      glVertex3f(-1.0f,-1.0f, 0.0f);
glEnd();

l'argument de glBegin() est un GLenum qui indique ce quel'on veut dessiner, par exemple GL_TRIANGLES pour des triangles, GL_QUADS pour des quads (c'est-à-dire des quadrilatères), GL_POLYGON pour _un_ polygone etc...GL_POLYGON ne dessinera qu'un polygone, ayant le nombre de points situés entre glBegin() et glEnd(). Par contre, GL_TRIANGLES dessinera un triangle tous les 3 points présents dans le bloc et GL_QUADS un quadrilatère tous les 4 points.

Note : le `3f' dans glVertex indique le type de paramètres utilisés : 3 indique qu'il a 3 coordonnées (il existe aussi avec 2 et 4 coordonnées, mais comme je capte pas trop l'utilisation de la 4è coordonnée, j'en parle pas) et f qu'il s'agit d'un `float' (avec `v', on met un seul argument qui est un tableau (v pour vector) du nombre d'éléments requis).

Pour ajouter des couleurs, on utilise glColor3f() ou 4f sil'on veut le alpha. Quand on appelle cette fonction, elle sera gardée jusqu'à ce que l'on ne change la couleur à nouveau. Ici, par exemple, on va dessine rune pyramide en couleur :

glBegin(GL_TRIANGLES);
            glColor3f(1.0f, 0.0f, 0.0f);       glVertex3f( 0.0f, 1.0f, 0.0f);
            glColor3f(0.0f, 1.0f, 0.0f);       glVertex3f(-1.0f,-1.0f, 1.0f);
            glColor3f(0.0f, 0.0f, 1.0f);       glVertex3f( 1.0f,-1.0f, 1.0f);

            glColor3f(1.0f, 0.0f, 0.0f);       glVertex3f( 0.0f, 1.0f, 0.0f);
            glColor3f(0.0f, 0.0f, 1.0f);       glVertex3f( 1.0f,-1.0f, 1.0f);
            glColor3f(0.1f, 0.1f, 0.1f);       glVertex3f( 1.0f,-1.0f,-1.0f);
 
            glColor3f(1.0f, 0.0f, 0.0f);       glVertex3f( 0.0f, 1.0f, 0.0f);
            glColor3f(0.1f, 0.1f, 0.1f);       glVertex3f( 1.0f,-1.0f,-1.0f);
            glColor3f(0.0f, 0.0f, 1.0f);       glVertex3f(-1.0f,-1.0f,-1.0f);
 
            glColor3f(1.0f, 0.0f, 0.0f);       glVertex3f( 0.0f, 1.0f, 0.0f);
            glColor3f(0.0f, 0.0f, 1.0f);       glVertex3f(-1.0f,-1.0f,-1.0f);
            glColor3f(0.0f, 1.0f, 0.0f);       glVertex3f(-1.0f,-1.0f, 1.0f);
glEnd();

Maintenant, on va ajouter des translations et des rotations pour faire des animations.

Pour voir un peux plus loin et pas trop grand, on effectue au début une translation de 6.0f sur l'axe z :

glTranslatef(0.0f,0.0f,-6.0f);

Cette fonction admet comme argument les coordonnées du vecteur de translation.

Si l'on voulait par exemple zoomer et dézoomer la pyramide,on peut utiliser une variable qui change lorsque l'on appuie sur les touches page up et page down. Pour cela, on déclare un variable globale au début du programme :

float deep = -6.0f ;

Et ensuite on la change avec l'appuie des touches (ce code peut être mis dans DrawGLScene()) :

if (skeys[GLUT_KEY_PAGE_UP])
{
      deep -= 0.02f;
}

if (skeys[GLUT_KEY_PAGE_DOWN])
{
      deep += 0.02f;
}

et on appellerait glTranslatef avec cette variable :

glTranslatef(0.0f,0.0f, deep) ;

Voilà la fonction en entier. La forme de la pyramide est un peu étrange à cause de la perspective, et parce qu'il lui manque le coté en dessous que vous pouvez dessiner avec GL_QUADS (n'oubliez pas la variable globale deep !!) :

void DrawGLScene(GLvoid)                      // Here's Where We Do All The Drawing
{
      if (skeys[GLUT_KEY_PAGE_UP])
      {
            deep-= 0.02f;
      }
      if (skeys[GLUT_KEY_PAGE_DOWN])
      {
            deep+= 0.02f;
      }
 
      glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);  // Clear Screen And Depth Buffer
      glLoadIdentity();       //on 'reset' lamatrice de l'écran: origine au centre de la fenetre
     
      glTranslatef(0.0f, 0.0f, deep);
 
      glBegin(GL_TRIANGLES);
            glColor3f(1.0f,0.0f, 0.0f); glVertex3f( 0.0f, 1.0f,0.0f);
            glColor3f(0.0f,1.0f, 0.0f); glVertex3f(-1.0f,-1.0f,1.0f);
            glColor3f(0.0f,0.0f, 1.0f); glVertex3f( 1.0f,-1.0f,1.0f);
 
            glColor3f(1.0f,0.0f, 0.0f); glVertex3f( 0.0f, 1.0f,0.0f);
            glColor3f(0.0f,0.0f, 1.0f); glVertex3f( 1.0f,-1.0f,1.0f);
            glColor3f(0.1f,0.1f, 0.1f); glVertex3f(1.0f,-1.0f,-1.0f);
 
            glColor3f(1.0f,0.0f, 0.0f); glVertex3f( 0.0f, 1.0f,0.0f);
            glColor3f(0.1f,0.1f, 0.1f); glVertex3f( 1.0f,-1.0f,-1.0f);
            glColor3f(0.0f,0.0f, 1.0f); glVertex3f(-1.0f,-1.0f,-1.0f);
 
            glColor3f(1.0f,0.0f, 0.0f); glVertex3f( 0.0f, 1.0f,0.0f);
            glColor3f(0.0f,0.0f, 1.0f); glVertex3f(-1.0f,-1.0f,-1.0f);
            glColor3f(0.0f,1.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 1.0f);
      glEnd();
 
       glutSwapBuffers();           //changement des buffers
      glutPostRedisplay();    //reaffiche leplan
}

Maintenant, de même, on va ajouter des rotations avec la fonction glTranslatef(). Cette fonction a comme arguments l'angle de rotation et les coordonnées du vecteur autour duquel on fait la rotation. Par exemple :

glRotatef(xrot, 1.0f, 0.0f, 0.0f);  //faire une rotation de xrot degrés sur levecteur de
     //coordonnées (1, 0, 0) donc l'axe (Ox)

On va ajouter de la rotation à la pyramide sur l'appui des flèches. Pour cela, on va utiliser deux variables globales : xrot et yrot pour les rotations respectives sur x et sur y et on va ajouter le code suivant après les autres if :

if (skeys[GLUT_KEY_LEFT])
      yrot -= 0.04f;
if (skeys[GLUT_KEY_RIGHT])
      yrot += 0.04f;
if (skeys[GLUT_KEY_UP])
      xrot -= 0.04f;
if (skeys[GLUT_KEY_DOWN])
      xrot +=0.04f;

Et celui-là après le glTranslatef() :

glRotatef(yrot, 0.0f, 1.0f, 0.0f);
glRotatef(xrot, 1.0f, 0.0f, 0.0f);

Une autre possibilité est celle d'ajouter de la vitesse de rotation lorsqu'on appuie sur ces touches:

Variables globales à ajouter : float xspeed =0.0f ; float yspeed = 0.0f ;

On modifie les if précédents pour qu'il change la vitesse de rotation (si c'est trop rapide sur votre ordinateur, changez l'augmentation):

if (skeys[GLUT_KEY_LEFT])
      yspeed -=0.005f;
if (skeys[GLUT_KEY_RIGHT])
      yspeed +=0.005f;
if (skeys[GLUT_KEY_UP])
      xspeed -=0.005f;
if (skeys[GLUT_KEY_DOWN])
      xspeed +=0.005f;

Et enfin, on ajoute ce code à la fin de DrawGLScene() pour qu'a chaque boucle la pyramide fasse une rotation relavive à la vitesse :

xrot +=xspeed;

yrot += yspeed;

Voilà le code final :

 
void DrawGLScene(GLvoid)              // Here's Where We Do All The Drawing
{
      if (skeys[GLUT_KEY_PAGE_UP])
      {
            deep-= 0.02f;
      }
      if (skeys[GLUT_KEY_PAGE_DOWN])
      {
            deep+= 0.02f;
      }
      if (skeys[GLUT_KEY_LEFT])
            yspeed-= 0.005f;
      if (skeys[GLUT_KEY_RIGHT])
            yspeed+= 0.005f;
      if (skeys[GLUT_KEY_UP])
            xspeed-= 0.005f;
      if (skeys[GLUT_KEY_DOWN])
            xspeed+= 0.005f;
 
 
      glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);  // Clear Screen And Depth Buffer
      glLoadIdentity();       //on 'reset' lamatrice de l'écran: origine au centre de la fenetre

      glTranslatef(0.0f, 0.0f, deep);         //translation (zoom)
      glRotatef(yrot,0.0f, 1.0f, 0.0f);
      glRotatef(xrot,1.0f, 0.0f, 0.0f);
 
      glBegin(GL_TRIANGLES);
            glColor3f(1.0f,0.0f, 0.0f); glVertex3f( 0.0f, 1.0f,0.0f);
            glColor3f(0.0f,1.0f, 0.0f); glVertex3f(-1.0f,-1.0f,1.0f);
            glColor3f(0.0f,0.0f, 1.0f); glVertex3f( 1.0f,-1.0f,1.0f);
 
            glColor3f(1.0f,0.0f, 0.0f); glVertex3f( 0.0f, 1.0f,0.0f);
            glColor3f(0.0f,0.0f, 1.0f); glVertex3f( 1.0f,-1.0f,1.0f);
            glColor3f(0.1f,0.1f, 0.1f); glVertex3f(1.0f,-1.0f,-1.0f);
 
            glColor3f(1.0f,0.0f, 0.0f); glVertex3f( 0.0f, 1.0f,0.0f);
            glColor3f(0.1f,0.1f, 0.1f); glVertex3f(1.0f,-1.0f,-1.0f);
            glColor3f(0.0f,0.0f, 1.0f); glVertex3f(-1.0f,-1.0f,-1.0f);
 
            glColor3f(1.0f,0.0f, 0.0f); glVertex3f( 0.0f, 1.0f,0.0f);
            glColor3f(0.0f,0.0f, 1.0f); glVertex3f(-1.0f,-1.0f,-1.0f);
            glColor3f(0.0f,1.0f, 0.0f); glVertex3f(-1.0f,-1.0f,1.0f);
      glEnd();
 
      glBegin(GL_QUADS);
            glColor3f(0.0f,0.0f, 1.0f); glVertex3f( 1.0f,-1.0f,1.0f);
            glColor3f(0.1f,0.1f, 0.1f); glVertex3f(1.0f,-1.0f,-1.0f);
            glColor3f(0.0f,0.0f, 1.0f); glVertex3f(-1.0f,-1.0f,-1.0f);
            glColor3f(0.0f,1.0f, 0.0f); glVertex3f(-1.0f,-1.0f,1.0f);
      glEnd();
 
      xrot +=xspeed;
      yrot +=yspeed;
 
      glutSwapBuffers();           //changementdes buffers
      glutPostRedisplay();    //reaffiche leplan
}

Pour vous entrainer, vous pouvez faire en sorte que le cube se remette en position initiale quand on appuie sur une touche.

A voir également
Ce document intitulé « Opengl-glut: création d'une fenetre et rotation d'une pyramide 3d » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous