Strumenti Utente



tutorial_qt:opengl_01

Qt e OpenGL - Introduzione

Con la nuova release delle Qt sono state introdotte nuove classi per l'impiego delle librerie grafiche Opengl.

In questo articolo accenneremo brevemente alle principali novità introdotte e vedremo un esempio applicativo disegneremo un triangolo in una classe derivata da QWindow. Nell'articolo Qt e OpenGL: Intro alla classe QOpenGLWidget potete invece vedere come realizzare lo stesso risultato reimplementando la classe QOpenGLWidget.

Il perché di tanti cambiamenti

Chi di voi proviene dalla versione 4 delle Qt noterà molti cambiamenti nell'impiego delle librerie Opengl all'interno del framework. I motivi di quello che sembrerebbe un restyling in realtà sono molto più profondi. Con le Qt5 si è cercato di utilizzare maggiormente la GPU per renderizzare la grafica delle interfacce. Per fare questo molte funzionalità che prima erano all'interno del modulo QtOpengl sono state estese al modulo QtGui.

Con l'occasione però sono state scritte nuova classi: ad esempio la classe QGlContext è stata riscritta nella classe QOpenglContext, QGlShaderProgram è diventata QOpenglShaderProgam etc. Le nuova classi, rispetto alle precedenti, hanno prestazioni leggermente migliori, hanno delle API più semplici e permettono l'impiego di un QOpenglContext su più superfici. Per approfondire l'argomento vi consiglio di leggere cosa dice in merito uno degli sviluppatori delle Qt all'indirizzo http://permalink.gmane.org/gmane.comp.lib.qt.devel/9065.

QGLWidget vs QWindow/QOpenGLWidget

La prima grande novità riguarda la classe base che useremo per creare la nostra superficie. Nel passato si faceva riferimento a QGLWidget.

Per quanto sia sempre possibile usare QGLWidget anche con le Qt5, il nuovo approccio prevede l'impiego delle classi QWindow e QOpenGLWidget.

Partiamo da QWindow...

Vediaemo come creare una classe derivata da QWindow per disegnare usando le API OpenGL. Nel farlo dobbiamo tener conto di due aspetti:

  • QWindow non deriva da QWidget, non avremo quindi a disposizione alcuni dei comodi strumenti presenti in QWidget
  • mentre con QGLWidget (e con QOpenGLWidget) è necessario ridefinire i metodi virtuali initializeGL(), paintGL () e resizeGL( int width, int height ), con la classe QWindow abbiamo una maggiore flessibilità: possiamo decidere noi come e con quali metodi fornire i vari comandi.

Creiamo la classe GLWindow che estende QWindow e QOpenGLFunctions. Estendiamo anche quest'ultima classe per poter accedere facilmente a tutti i comandi Opengl in essa contenuti.

Avremo un file header glwindow.h che conterrà

class QOpenGLShaderProgram;
#include <QtGui/QWindow>
#include <QtGui/QOpenGLFunctions>

class GLWindow : public QWindow, protected QOpenGLFunctions
{
public:
    explicit GLWindow(QWindow *parent = 0);

private:
    QOpenGLShaderProgram * m_program;
    QOpenGLContext * m_context;

    GLuint m_posAttr;
    GLuint m_colAttr;
    GLuint m_matrixUniform;
    
    void exposeEvent(QExposeEvent *event);
    void resizeEvent(QResizeEvent *event);
    virtual void initialize();
    virtual void initShaders();
    virtual void paint();
    virtual void paintGL();      
};

Abbiamo introdotto alcuni attributi e metodi che analizzeremo strada facendo.

Il costruttore di GLWindow si limita ad inizializzare un attributo e a comunicare all'infrastruttura delle Qt che intendiamo disegnare sulla superficie impiegando le API OpenGL.

GLWindow::GLWindow(QWindow *parent) :
    QWindow(parent):
    m_context(0) {
    
    setSurfaceType(QWindow::OpenGLSurface);
    
}

Poichè le funzionalità che vogliamo implementare sono molto semplici, ci basta reimplementare due metodi virtuali: void exposeEvent(QExposeEvent *event) e void resizeEvent(QResizeEvent *event). Il primo viene chiamato ogni volta che cambia la visibilità della nostra superficie di modo che ogni qualvolta l'oggetto diventa visibile, il triangolo sia ridisegnato. Ecco il codice

void GLWindow::exposeEvent(QExposeEvent *event) {
    if (isExposed())
        paint();
}

Il metodo paint(), che vedremo tra poco, disegna il triangolo.

Il metodo resizeEvent() viene chiamato in concomitanza dei ridimensionamenti della superficie. In tal caso dovremo cambiare le dimensioni della finestra OpenGL.

void GLWindow::resizeEvent(QResizeEvent *event) {
    if( m_context !=  0 )
        glViewport(0, 0, width(), height());

    if (isExposed())
        paint();
}

Gli shader

Alla base della programmazione OpenGL moderna ci sono gli shader.

Le prime versioni dello standard OpenGL prevedevano pipeline grafiche fisse: i calcoli eseguiti dalla scheda grafica erano di una tipologia ben specifica. Le Opengl 2.0 hanno introdotte per un primo livello di personalizzazione delle operazioni compiute dalla GPU che sono così diventate programmabili. Gli shader sono i programmi che carichiamo sulle schede grafiche.

Nella versione 5 delle Qt creiamo gli shader usando la classe QOpenGLShaderProgram.

Opengl secondo la Qt-way

I metodi che vedremo ora, sia come denominazione che come struttura che come compiti, sono il risultato di una scelta personale e come tali possono essere modificati. Perciò, acquisita un po' di dimestichezza con le OpenGL, modificateli pure!

Analizziamo prima il metodo paint(), il cuore delle operazioni di disegno

void GLWindow::paint() {
    if (!isExposed())
        return;
    
    bool needsInitialize = false;
    
    if (!context) {
        m_context = new QOpenGLContext(this);
        m_context->setFormat(requestedFormat());
        m_context->create();

        needsInitialize = true;
    }
    
    m_context->makeCurrent(this);
    
    if(needsInitialize) {
        initialize();
    }
    
    paintGL();
    
    m_context->swapBuffers(this);
}

Una volta sola, prima che avvenga il primo disegno, paint() inizializziamo m_context chiamando il metodo initialize() che imposta il contesto OpenGL

void GLWindow::initialize() {
    initializeOpenGLFunctions();

    initShaders();
    glViewport(0, 0, width(), height());

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    m_posAttr = m_program->attributeLocation("posAttr");
    m_colAttr = m_program->attributeLocation("colAttr");
    m_matrixUniform = m_program->uniformLocation("matrix");
}

Il metodo initShaders() inizializza gli shaders eseguendo le seguenti operazioni:

  • creiamo un oggetto QOpenGLShaderProgram
  • associamo a questo oggetto il codice degli shaders, contenuto in due file esterni
  • linkiamo il codice
void GLWindow::initShaders() {
    m_context->makeCurrent(this);
    
    m_program = new QOpenGLShaderProgram(this);
    
    // Compila lo shader dei vertici
    if (!m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vertex.glsl"))
        close();
    
    // Compile lo shader fragment
    if (!m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fragment.glsl"))
        close();
    
    // Linka shaders
    if (!m_program->link())
        close();
}

Gli shaders sono molto semplici. Il vertex shader, contenuto nel file vertex.glsl, è

attribute highp vec4 posAttr;
attribute lowp vec4 colAttr;
varying lowp vec4 col;
uniform highp mat4 matrix;

void main() {
   col = colAttr;
   gl_Position = matrix * posAttr;
}

Il fragment shader, contenuto nel file fragments.glsl, è

varying lowp vec4 col;

void main() {
   gl_FragColor = col;
}

Ed infine vediamo i comandi per disegnare il nostro triangolo. In questo primo articolo ci limitiamo a passare ad ogni ridisegno i dati necessari. Per farlo usiamo prima il metodo QOpenglShaderProgram::enableAttributeArray(), con cui attiviamo l'attributo che ci interessa, e poi il metodo QOpenglShaderProgram::setAttributeArray() con cui passiamo i relativi valori

void GLWindow::paintGL() {
    m_context->makeCurrent( this );
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    m_program->bind();
    
    QMatrix4x4 matrix;
    matrix.perspective(60, 4.0/3.0, 0.1, 100.0);
    matrix.translate(0, 0, -2);
    
    m_program->setUniformValue(m_matrixUniform, matrix);
    
    QVector2D vertices [] = {
        QVector2D(0.0f, 0.707f),
        QVector2D(-0.5f, -0.5f),
        QVector2D(0.5f, -0.5f)
    };
    
    m_program->enableAttributeArray( m_posAttr );
    m_program->setAttributeArray( m_posAttr, vertices );
    
    QVector3D colors[] = {
        QVector3D(1.0f, 0.0f, 0.0f),
        QVector3D(0.0f, 1.0f, 0.0f),
        QVector3D(0.0f, 0.0f, 1.0f)
    };
    
    m_program->enableAttributeArray( m_colAttr );
    m_program->setAttributeArray( m_colAttr, colors );
    
    glDrawArrays(GL_TRIANGLES, 0, 3);
    
    m_program->release();
}

Per visualizzare quello che abbiamo creato ci serve un file main.cpp

#include <QtGui/QGuiApplication>

#include "glwindow.h"

int main(int argc, char **argv) {
    QGuiApplication app(argc, argv);
    
    GLWindow window;
    window.resize(640, 480);
    window.show();
    
    return app.exec();
}

Ed ecco a voi il risultato finale

Tutti i file necessari sono contenuti nell'archivio opengltut01.zip.

Articoli correlati

Per approfondire

opengl

Qt e opengl

Potrebbero interessarti anche...

Comments


tutorial_qt/opengl_01.txt · Ultima modifica: 2015/05/05 14:43 da mickele

Facebook Twitter Google+ Digg Reddit LinkedIn StumbleUpon Email