Traduzione a cura di Fabrizio Angius [qtsolutions -A-T- gmx.net]
English TOC.

Capitolo 14 - OpenGL

Quando si ha a che fare con Qt e OpenGL, non si sa bene dove andare a disegnare una linea. Dato che questo dovrebbe essere un tutorial su Qt, il capitolo si basa sull'eccellente tutorial OpenGL di NeHe. Gli interessati potranno dare un'occhiata al tutorial di NeHe. Quelli che vogliono far funzionare OpenGL con Qt dovrebbero leggere questo capitolo.

Questo tutorial si basa sui primi dodici capitoli del tutorial OpenGL di NeHe. Prima bisogna pero' affrontare un po' di teoria legata a Qt. Qt fornisce un intero modulo OpenGL in quasi tutte le versioni. Alcune delle versioni commerciali non lo prevedono. Il modulo contiene una serie di classi che incapsulano le parti piu' interessanti di OpenGL. Nella maggior parte delle applicazioni e' sufficiente la classe QGLWidget. In questo capitolo, tutto il codice riguardera' una classe derivata da QGLWidget e chiamata NeHeWidget. La dichiarazione di NeHeWidget puo' essere vista nell'esempio 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;
};

Esempio 14-1

Il widget non puo' essere istanziato, dato che contiene solo metodi puramente virtuali, ma ne parleremo piu' avanti. In alcuni dei capitoli di NeHe le scene vengono aggiornate utilizzando un timer. Per facilitare la cosa utilizzeremo un QTimer. La classe QTimer puo' essere utilizzata per invocare uno slot dopo un certo periodo di tempo o per invocarlo periodicamente. Passando al costruttore un intervallo non nullo, impostiamo il timer in modo da far invocare lo slot timeOutSlot con quel dato intervallo. Lo slot timeOutSlot chiama quindi il metodo timeOut. Il motivo per cui non si chiama direttamente timeOut e' per non avere ulteriori slot (e per non avere un timeOutSlot con overloading) nelle varie sottoclassi.

Il metodo keyPressEvent gestisce il tasto Esc chiudendo semplicemente la finestra. Piu' avanti, le sottoclassi sovrascriveranno keyPressEvent per fornire funzionalita' aggiuntive chiamando comunque sempre anche l'implementazione della superclasse.

L'esempio 14-2 mostra il metodo main utilizzato per avviare tutti gli esempi. Ogni capitolo trattera' poi una specifica implementazione di NeHeWidget chiamata NeHeChapterXX, con XX che indica il numero del corrispondente capitolo nel tutorial di NeHe.

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

Esempio 14-2

Il primo capitolo di NeHe riguarda il setup di OpenGL che nel nostro caso viene gestito da Qt e da QGLWidget. Il codice base, spesso indicato nei tutorial di NeHe, e' piu' o meno contenuto nella classe NeHeWidget.

Se si utilizza Microsoft Visual C++, e' necessario cambiare la riga LIBS += -lGLU con LIBS += -lglu32 nei file progetto.

Capitolo 2

Prima di leggere il capitolo 2 del tutorial di NeHe, e' bene aver visto la descrizione dettagliata di QGLWidget nella documentazione ufficiale di Qt. Il capitolo introduce le basi per disegnare con OpenGL: vertici, triangoli e quadrati.

Un'immagine dal capitolo 2

Figura 14-1

Il metodo DrawGLScene dei tutorial di NeHe corrisponde al metodo paintGL di Qt. Allo stesso modo, resizeGL e initializeGL hanno dei metodi corrispondenti nelle classi base di NeHe. Il metodo paintGL della classe NeHeChapter2 e' mostrato nell'esempio 14-3, e potra' essere riconosciuto facilmente leggendo il tutorial di 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();
}

Esempio 14-3

Capitolo 3

Il capitolo 3 introduce il metodo glColor3f per impostare il colore corrente. Mostra anche come impostando un colore e creando piu' vertici questi avranno tutti lo stesso colore. La classe NeHeChapter3 e' identica alla classe del capitolo due, eccetto per l'aggiunta delle chiamate a glColor3f nel metodo paintGL.

Un'immagine dal capitolo 3

Figura 14-2

Capitolo 4

Nel capitolo 4, si introduce il movimento. L'esempio 14-4 mostra le parti interessanti della classe NeHeChapter4.

Un'immagine dal capitolo 4

Figura 14-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();
  }
};

Esempio 14-4

Nel codice di esempio di NeHe, i valori rtri ed rquad vengono aggiornati dopo ogni operazione di disegno. Nella versione Qt questo viene fatto mediante un QTimer che chiama poi il metodo updateGL; quest'ultimo si occupa di schedulare un aggiornamento della scena. Con questo approccio l'animazione e' sempre costante, indipendentemente dal framerate.

Capitolo 5

Il capitolo 5 introduce veri oggetti 3D. Nei capitoli precedenti sono stati utilizzati oggetti piatti in uno spazio 3D, ora vengono mostrati un cubo e una piramide. Il codice e' praticamente identico a quello del capitolo 4.

Un'immagine dal capitolo 5

Figura 14-4

Capitolo 6

Nel capitolo 6, ci sono alcune cose interessanti: vengono introdotte le texture. Qui ci sono alcune particolarita' di Qt da prendere in considerazione.

Un'immagine dal capitolo 6

Figura 14-5

Nell'esempio 14-6 viene mostrati il codice per caricare le immagini e utilizzarle come texture. Notate che la classe QImage supporta un formato che va d'accordo con OpenGL. Il metodo loadGLTextures viene chiamato dal metodo 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 );
}

Esempio 14-6

Capitolo 7

Il capitolo 7 introduce illuminazione, filtri per texture e movimenti controllati da tastiera.

Un'immagine dal capitolo 7

Figura 14-6

Il codice assomiglia notevolmente a quello del sesto capitolo, ma ci sono alcuni cambiamenti in loadGLTextures e una nuovissima versione di keyPressEvent. Parte della nuova versione e' mostrata nell'esempio 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 );
  }
}

Esempio 14-7

Con questo approccio utilizziamo i meccanismi di Qt per gli eventi da tastiera, invece di affidarci all'array utilizzato nel tutorial di NeHe.

Capitolo 8

Nel capitolo 8 viene introdotto l'effetto blending [letteralmente "mescolamento", ndT]. Il codice e' analogo a quello dei capitoli precedenti, con l'aggiunta di un flag per attivare o disattivare il blending.

Un'immagine dal capitolo 8

Figura 14-7

Capitolo 9

Il capitolo 9 inizia ad utilizzare luci e blending per creare effetti piu' complessi. In questo caso, stelle scintillanti che ruotano formando una spirale.

Un'immagine dal capitolo 9

Figura 14-8

L'aggiornamento della posizione di ogni stella viene gestito dal metodo timeOut. A parte cio', il codice e' un misto della versione Qt del capitolo otto e del capitolo nove di NeHe.

Capitolo 10

Nel capitolo 10 viene caricato il modello di una scena e l'utente vi si puo' muovere all'interno con una visuale in prima persona.

Un'immagine dal capitolo 10

Figura 14-0

Il codice di questo capitolo e' diverso da quello dei capitoli precedenti. Non c'e' nessun timer dato che ogni movimento fa scattare un'invocazione del metodo updateGL che ridisegna la scena.

La differenza piu' grossa rispetto alla versione di NeHe riguarda la gestione dei file. Il modello della scena viene caricato nel metodo loadTriangles mostrato nell'esempio 14-8. I metodi C della versione di NeHe sono stati sostituiti dai corrispondenti metodi Qt. In questo modo la gestione e' piu' robusta.

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

Esempio 14-8

Capitolo 11

Il capitolo 11 implementa un effetto bandiera. Un'immagine viene mappata su una superficie che "ondeggia nel vento". Il codice e' abbastanza immediato e i punti che definiscono la superficie vengono aggiornati nel metodo timeOut.

Un'immagine dal capitolo 11

Figura 14-10

Capitolo 12

Nel capitolo 12 si introducono le call-list. E' un metodo per inserire una serie di chiamate OpenGL in una lista. Queste liste possono essere poi invocate per riprodurre una sequenza di chiamate.

Un'immagine dal capitolo 12

Figura 14-11

Questo e' l'ultimo capitolo di questo tutorial. Se volete vedere altri capitoli portati su Qt, fatemelo sapere. Personalmente spero che siate in grado di farlo da soli guardando il codice di questi capitoli. L'intero codie sorgente puo' essere scaricato qui.

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