Strumenti Utente



tutorial_qt:opengl_01

Questa è una vecchia versione del documento!


Tutorial Qt5 e Opengl #1

Con la nuova release delle Qt sono state introdotte delle nuove classi per l'impiego delle librerie grafiche Opengl. Con questa serie di articoli vedremo brevemente quali sono le novità introdotte analizzando alcuni esempi pratici.

Il perché di tanti cambiamenti

I motivi di quello che sembrerebbe un restyling in realtà sono molto più profondi. Con le Qt5 si è cercato di utilizzare maggiormente le 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 sono state scritte nuova classi: ad esempio QGlContext è diventata QOpenglContext, QGlShaderProgram p diventato QOpenglShaderProgam. 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

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

Posto che è comunque sempre possibile ricorrere alla classe QGLWidget anche con le Qt5, il nuovo approccio proposto prevede l'impiego della classe QWindow.

Una prima differenza è nel fatto che QGLWidget deriva da QWidget, QWindow no. Non avremo uindi a disposizione alcuni dei comodi metodi presenti in QWidget.

Un altra differenza riguarda le modalità di impiego. Derivando QGLwidget era necessario reimplementare i metodi virtuali initializeGL(), paintGL () e resizeGL( int width, int height ). Con la classe QWindow abbiamo invece una flessibilità molto maggiore: 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 nel 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 allora un file header glwindow.h che conterrà

#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;
    QOpenGLPaintDevice * m_device;

    GLuint m_posAttr;
    GLuint m_colAttr;
    GLuint m_matrixUniform;
};

Inoltre, come potete vedere, introduciamo alcuni attributi che analizzeremo strada facendo.

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

GLWindow::GLWindow(QWindow *parent) :
    QWindow(parent):
    context(0),
    device(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 iene 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 ogni qualvolta la superficie viene ridimensionata. In tal caso doivremo cambiare le dimensioni della finestra opengl.

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

    if (isExposed())
        paint();
}

... e aggiungiamo un po' di Opengl

Passiamo ora al metodo paint(), che è quello che disegna il triangolo.

I metodi che vedremo ora, sia come denomiinazione che come struttura e compiti possono cambiare, perciò se ritenete di doverli modificare, fate pure!

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

    if (!m_device)
        m_device = new QOpenGLPaintDevice;
    m_device->setSize( size() );

    paintGL();
    m_context->swapBuffers(this);
}

Alcuni comandi devono essere eseguiti una sola volta prima del primo disegno; li mettiamo nel metodo initialize()

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_d->program->attributeLocation("posAttr");
    m_colAttr = m_d->program->attributeLocation("colAttr");
    m_matrixUniform = m_d->program->uniformLocation("matrix");
}

Effettuaimo l'inizializzazione degli shaders nel metodo initShaders(). Abbiamo agigunto tra gli attributi di GLWindow un QOpenGLShaderProgram; ora gli associamo il codice degli shaders, contenuto in due file esterni

void GLWindow::initShaders() {
    m_context->makeCurrent(this);

    // Sovrasvcrive la codifica fintantoche' non sono compilati gli shaders
    setlocale(LC_NUMERIC, "C");

    m_program = new QOpenGLShaderProgram(this);

    // Compila lo shader dei vertici
    if (!m_d->program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vertex.glsl"))
        close();

    // Compile lo shader fragment
    if (!m_d->program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fragment.glsl"))
        close();
    // Linka shaders
    if (!m_d->program->link())
        close();

    // Ripristina la codifica del sistema
    setlocale(LC_ALL, "");
}

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

void GLWindow::paintGL() {
    m_d->context->makeCurrent( this );

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    m_d->program->bind();

    QMatrix4x4 matrix;
    matrix.perspective(60, 4.0/3.0, 0.1, 100.0);
    matrix.translate(0, 0, -2);

    m_d->program->setUniformValue(m_d->matrixUniform, matrix);

    GLfloat vertices[] = {
        0.0f, 0.707f,
        -0.5f, -0.5f,
        0.5f, -0.5f
    };
    GLfloat colors[] = {
        1.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 1.0f
    };

    glVertexAttribPointer(m_posAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, 0, colors);

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glDrawArrays(GL_TRIANGLES, 0, 3);

    glDisableVertexAttribArray(1);
    glDisableVertexAttribArray(0);

    m_program->release();
}

Ed ecco a voi il risultato finale

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

Per approfondire la programmazione opengl


tutorial_qt/opengl_01.1379173827.txt.gz · Ultima modifica: 2013/09/14 17:50 da mickele

Facebook Twitter Google+ Digg Reddit LinkedIn StumbleUpon Email