Traduction par Jean-luc Biord, du Site de la communauté Qt francophone.
English TOC.

Chapitre 14 - OpenGL

Lorsqu'on traite Qt et OpenGL, le problème est de savoir comment tracer les dessins. Alors que ce tutoriel est un tutoriel Qt, ce chapitre est basé sur le grand tutoriel OpenGL de NeHe. Pour ceux d'entre vous qui sont intéressés par OpenGL, regardez le tutoriel de NeHe. Pour ceux d'entre vous qui veulent connaître OpenGL fonctionnant avec Qt, lisez ceci.

Ce tutoriel est basé sur les douze premiers chapitres du tutoriel OpenGL de NeHe. Mais d'abord, il y a une certaine théorie de Qt qui doit être vue. Qt fournit un module entier OpenGL presque pour toutes les versions. Quelques versions commerciales ne l'ont pas. Le module contient un ensemble de classes qui encapsule les parties intéressantes d'OpenGL. Dans la plupart des applications, la classe QGLWidget est la seule classe nécessaire. Dans ce chapitre, tout le code sera basé autour d'une classe dérivée de QGLWidget appelé NeHeWidget. La déclaration de NeHeWidget peut être vue dans l'exemple 14-1.

class NeHeWidget : public QGLWidget
{
  Q_OBJECT
  
public:
  NeHeWidget( int timerInterval=0, QWidget *parent=0, char *name=0 );

protected:
  virtual void initializeGL() = 0;
  virtual void resizeGL( int width, int height ) = 0;
  virtual void paintGL() = 0;
  
  virtual void keyPressEvent( QKeyEvent *e );
  
  virtual void timeOut();
  
protected slots:
  virtual void timeOutSlot();
  
private:
  QTimer *m_timer;
};

Exemple 14-1

Le widget ne peut pas être instancié, car il contient des méthodes virtuelles pures, mais plus sera vu à ce sujet plus loin. Dans plusieurs chapitres de NeHe, les graphiques sont mis à jour par un timer. Pour faciliter cela, un QTimer est employé. Un QTimer est une classe qui peut être configurée pour déclencher un slot une seule fois après un temps donné, ou déclencher un slot de façon répétitive indiquée par un intervalle. En fournissant un intervalle de timer différent de zéro au constructeur, le chronomètre est installé pour appeler timeOutSlot avec l'intervalle donné. timeOutSlot appelle alors la méthode timeOut. La raison de cet appel indirect à timeOut, est d'éviter d'avoir tous les slots additionnels (y compris la surcharge de timeOutSlot) dans chacune des sous-classes.

La méthode keyPressEvent traite la touche échappement et ferme simplement la fenêtre si elle la rencontre. Plus tard, les sous-classes surchargeront keyPressEvent pour fournir plus de fonctionnalité, mais toutes appellent l'implémentation de la classe de base.

L'exemple 14-2 montre la fonction principale qui est employée pour utiliser tous les exemples. Chaque chapitre se compose alors d'une implémentation spécifique de NeHeWidget appelé NeHeChapterXX où XX indique le numéro de chapitre du tutoriel de NeHe.

int main( int argc, char **argv )
{
  QApplication a( argc, argv );
  
  NeHeWidget *w = new NeHeChapterXX();
  a.setMainWidget( w );
  w->show();
  
  return a.exec();
}

Exemple 14-2

Le premier chapitre du tutoriel de NeHe traite de la configuration d'OpenGL, et cela peut être pris en compte pour Qt et QGLWidget. Le code de base, celui qui est souvent mentionné dans le tutoriel de NeHe est plus ou moins encapsulé dans la classe NeHeWidget.

Dans les fichiers-projets, la ligne LIBS += -lGLU devra être changé pour LIBS += -lglu32 pour travailler avec Microsoft Visual C++.

Chapitre 2

En lisant le chapitre 2 du tutoriel de NeHe, il est bon d'avoir vu la description détaillée de QGLWidget dans la documentation officielle de Qt. Le chapitre présente les bases du dessin OpenGL : vertexes (sommets), triangles and quads (quadruples).

A screenshot from chapter 2

Schéma 14-1

La méthode DrawGLScene du tutoriel de NeHe correspond à la méthode paintGL de Qt. De la même manière, resizeGL et initializeGL ont des méthodes correspondantes dans les classes de base de NeHe. La méthode paintGL de la classe NeHeChapter2 est montrée dans l'exemple 14-3, et peut facilement être reconnue en lisant le tutoriel de NeHe.

void paintGL()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();

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

  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();

  glTranslatef(3.0f,0.0f,0.0f);

  glBegin(GL_QUADS);
    glVertex3f(-1.0f, 1.0f, 0.0f);
    glVertex3f( 1.0f, 1.0f, 0.0f);
    glVertex3f( 1.0f,-1.0f, 0.0f);
    glVertex3f(-1.0f,-1.0f, 0.0f);
  glEnd();
}

Exemple 14-3

Chapitre 3

Le chapitre 3  présente comment employer la méthode glColor3f pour paramétrer la couleur courante. Il montre également comment régler la couleur et créer plusieurs sommets et donner à ces sommets la même couleur. La classe NeHeChapter3 est identique à la classe du chapitre deux excepté l'addition des appels de glColor3f dans la méthode de paintGL.

A screenshot from chapter 3

Schéma 14-2

Chapitre 4

Dans le chapitre 4, le mouvement est présenté. L'exemple 14-4 montre les parties intéressantes de la classe NeHeChapter4.

A screenshot from chapter 4

Schéma14-3

class NeHeChapter4 : public NeHeWidget
{
private:
  GLfloat rtri, rquad;
public:
  NeHeChapter4( QWidget *parent=0, char *name=0 ) : NeHeWidget( 50, parent, name )
  {
    rtri = rquad = 0.0f;
  }
  
protected:
  void paintGL()
  {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();

    glTranslatef(-1.5f,0.0f,-6.0f);
    glRotatef(rtri,0.0f,1.0f,0.0f);

    ...

    glLoadIdentity();
    glTranslatef(1.5f,0.0f,-6.0f);
    glRotatef(rquad,1.0f,0.0f,0.0f);

    ...
  }
  
  void timeOut()
  {
    rtri += 0.5f;
    rquad -= 0.25f;
    
    updateGL();
  }
};

Exemple 14-4

Dans le code d'exemple de NeHe, les valeurs de rtri et de rquad sont mises à jour après chaque opération de dessin. Dans la version de Qt, le timer le fait et puis appelle la méthode updateGL qui programme le dessin. Cette approche fait que l'animation fonctionne au même rythme, indépendamment du framerate.

Chapitre 5

Le chapitre 5 présente les vrais objets 3D. Dans les premiers chapitres, les objets plats dans l'espace 3D ont été présentés, maintenant une boîte et une pyramide sont montrées. Le code est presque identique à celui du chapitre 4.

A screenshot from chapter 5

Schéma 14-4

Chapitre 6

Dans le chapitre 6, les choses intéressantes commencent : la texturisation est présentée. Ici, il y a quelques particularités de Qt qui doivent être prises en compte.

A screenshot from chapter 6

Schéma 14-5

Dans l'exemple 14-6, le code pour charger des images et les lier aux textures est montré. Notez que la classe QImage a le support amical du format OpenGL. La méthode loadGLTextures est appelée depuis la méthode initializeGL.

void NeHeChapter6::loadGLTextures()
{
  QImage t;
  QImage b;
    
  if ( !b.load( "../images/nehe.bmp" ) )
  {
    b = QImage( 16, 16, 32 );
    b.fill( Qt::green.rgb() );
  }
    
  t = QGLWidget::convertToGLFormat( b );
  glGenTextures( 1, &texture[0] );
  glBindTexture( GL_TEXTURE_2D, texture[0] );
  glTexImage2D( GL_TEXTURE_2D, 0, 3, t.width(), t.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, t.bits() );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
}

Exemple 14-6

Chapitre 7

Le chapitre 7 présente l'éclairage, les filtres de texture et les mouvements commandés au clavier.

A screenshot from chapter 7

Schéma 14-6

Le code ressemble beaucoup au code du chapitre six, mais avec quelques changements dans loadGLTextures, et une version nouvelle et surchargée de keyPressEvent. Des parties de la nouvelle version sont montrées dans l'exemple 14-7.

void keyPressEvent( QKeyEvent *e )
{
  switch( e->key() )
  {
  case Qt::Key_L:
    light = !light;
      
    if( light )
      glEnable( GL_LIGHTING );
    else    
      glDisable( GL_LIGHTING );
    
    break;
      
  case Qt::Key_F:
    filter++;
    if( filter > 2 )
      filter = 0;
    
    break;
      
  case Qt::Key_Left:
    droty -= 0.01f;

    break;
      
  ...
      
  default:
    NeHeWidget::keyPressEvent( e );
  }
}

Exemple 14-7

En utilisant cette approche, nous employons le mécanisme de Qt pour les événements du clavier, au lieu de compter sur un tableau de touches clavier utilisé dans le tutoriel de NeHe.

Chapitre 8

Dans le chapitre 8, l'effet de mélange est présenté. Le code est comme celui du chapitre précédent, mais avec un drapeau additionnel pour basculer le mélange à marche et arrêt.

A screenshot from chapter 8

Schéma 14-7

Chapitre 9

Le Chapitre 9 commence à employer l'éclairage et le mélange pour établir des effets plus complexes. Dans ce cas-ci, le scintillement tient le premier rôle dans une rotation en spirale

A screenshot from chapter 9

Schéma 14-8

La mise à jour de la position de chaque étoile est traitée dans la méthode timeOut. Indépendamment de cela, le code est un mélange du chapitre huit de la version de Qt et du code du chapitre neuf de NeHe.

Chapitre 10

Dans le chapitre 10, un modèle de monde est chargé et l'utilisateur peut se déplacer à l'intérieur dans une perspective à la première personne.

A screenshot from chapter 10

Schéma 14-0

Le code de ce chapitre est différent des premiers chapitres. Il n'y a aucun timer impliqué, au lieu de cela chaque mouvement déclenche un appel à updateGL qui redessine la scène.

Le plus grand changement par rapport à la version de NeHe est la manipulation de fichier. Le modèle du monde est chargé dans la méthode loadTriangles montrée dans l'exemple 14-8. Les méthodes C de la version de NeHe ont été remplacées par les méthodes correspondantes de Qt. Ceci rend l'utilisation plus robuste.

void loadTriangles()
{
  QFile f( "world.txt" );
    
  if( f.open( IO_ReadOnly ) )
  {
    QTextStream ts( &f );
      
    Vertex v[3];
    int vcount = 0;
    bool allok, ok;
      
    while( !ts.atEnd() )
    {
      QStringList line = QStringList::split( " ", ts.readLine().simplifyWhiteSpace() );
        
      if( line.count() == 5 )
      {
        allok = true;
        v[vcount].x = line[0].toFloat( &ok );
        allok &= ok;
        v[vcount].y = line[1].toFloat( &ok );
        allok &= ok;
        v[vcount].z = line[2].toFloat( &ok );
        allok &= ok;
        v[vcount].u = line[3].toFloat( &ok );
        allok &= ok;
        v[vcount].v = line[4].toFloat( &ok );
        allok &= ok;
          
        if( allok )
          vcount++;
          
        if( vcount == 3 )
        {
          vcount = 0;
          Triangle t;
          t.vertex[0] = v[0];
          t.vertex[1] = v[1];
          t.vertex[2] = v[2];
            
          triangles.append( t );
        }
      }
    }
      
    f.close();
  }
}

Exemple 14-8

Chapitre 11

Le Chapitre 11 implémente un effet de drapeau. Une image est tracée sur une surface qui "ondule dans le vent". Le code est clair et les points qui établissent la surface sont mis à jour dans la méthode timeOut.

A screenshot from chapter 11

Schéma 14-10

Chapitre 12

Dans le chapitre 12 des listes d'appel sont présentées. C'est une manière d'établir des listes d'appels OpenGL dans une liste. Cette liste peut alors être appelée pour rejouer la liste d'appels.

A screenshot from chapter 12

Schéma 14-11

C'est le chapitre final de ce tutoriel. Si vous voulez voir plus de chapitres sur Qt, vous êtes libre de me contacter. Mon espoir est que vous pouvez le faire vous même en regardant le code de ces chapitres. Le code source entier peut être téléchargé ici.

This is a part of digitalfanatics.org and is valid XHTML.