Strumenti Utente



tutorial_qt:opengl_01

Questa è una vecchia versione del documento!


Qt e Opengl: Introduzione

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

In questo articolo accenneremo brevemente le principali novità introdotte e vedremo un esempio in cui reimplementeremo la classe QWindow. Nel tutorial 1bis (?!) invece vedremo 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.

~~READMORE~~

QGLWidget vs QWindow

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 della classe QWindow.

Quali le differenze tra le due classi?

Una prima differenza è che QGLWidget deriva da QWidget, QWindow no, non avremo quindi a disposizione alcuni dei comodi strumenti presenti in QWidget.

Un'altra differenza riguarda le modalità con cui andremo a scrivere le sottoclassi. Derivando QGLwidget era necessario reimplementare i metodi virtuali initializeGL(), paintGL () e resizeGL( int width, int height ). Con la classe QWindow abbiamo invece una maggiore flessibilità: possiamo decidere noi come e con quali metodi fornire i vari comandi.

Partiamo da QWindow...

Iniziamo con un esempio concreto, creando una classe derivata di QWindow nella quale vogliamo disegnare un triangolo.

Creiamo la classe GLWindow che estende QWindow e QOpenGLFunctions. Estendiamo anche ques'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 attribute e metodi che analizzeremo strada facendo.

Il costruttore di GLWindow si limita ad inizializzare un attributo e a dire alle Qt che intendiamo disegnare sulla superficie con le opengl.

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

La gestione degli eventi nel nostro primo esempio è molto semplice, 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; se è visibile, dovremo ridisegnare il triangolo. Ecco il codice

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

Il metodo paint(), che vedremo tra poco, è quello che 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();
}

... e aggiungiamo un po' di Opengl

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() inizializza m_context e chiama il metodo initialize() che si occupa di impostare l'ambiente 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. Più in particolare, nel metodo initShaders():

  • 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 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 enableAttributeArray() di QOpenglShaderProgram, con cui attiviamo l'attributo che ci interessa, e poi il metodo setAttributeArray() con cui passiamo i 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 mail.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 contenuati nell'archivio opengltut01.zip.

e poi...

Nel prossimo articolo approfondiremo i comandi opengl sui quali in questo primo articolo non ci siamo molto soffermati. Vedremo quindi con maggior dettaglio cosa fanno i metodi setUniformValue(), enableAttributeArray() e setAttributeArray() della classe QOpenGLShaderProgram e ci concentreremo su metodi più efficienti di scambio delle informazioni con la GPU.

Per approfondire

opengl

Qt e opengl


tutorial_qt/opengl_01.1421942643.txt.gz · Ultima modifica: 2015/01/22 17:04 da mickele

Facebook Twitter Google+ Digg Reddit LinkedIn StumbleUpon Email