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 maggiori chiarimenti vedi la mail di uno degli sviluppatori 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. Poiché, che come dice il nome stesso, QGLWidget deriva dalla classe QWidget, eredita da questo i segnali e i metodi.

Per disegnare il nostro progetto dobbiamo creare una sottoclasse di QGLWIdget che reimplementa i metodi virtuali initializeGL(), paintGL () e resizeGL( int width, int height ).

Posto che è comunque sempre possibile ricorrere alla classe QGLWidget anche con le Qt5, il nuovo approccio proposto prevede l'impiego della classe QWindow. Si tratta di una classe di livello più basso rispetto a QWidget, ma non per questo con meno potenzialità.

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 un oggetto QWindow nel quale vogliamo disegnare un triangolo sfruttando le librerie opengl.

Creiamo la classe GLWindow che estende la classe QWindow (ovviamente) e la classe QOpenGLFunctions. Estendiamo anche ques'ultima classe per poter accedere facilmente a tutti i comandi opengl contenuti in quest'ultima classe, indipendentemente dalla piattaforma su cui lavoriamo.

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

Definiamo inoltre alcuni attributi.

Nel file glwindow.cpp avremo il costruttore

GLWindow::GLWindow(QWindow *parent) :
    QWindow(parent):
    context(0),
    device(0) {
    setSurfaceType(QWindow::OpenGLSurface);
}

nel quale ci limitiamo ad inizializzare due attributi e a dire alle Qt che intendiamo usare le opengl.

La gestione degli eventi in una QWindow è molto semplice, ci basta reimplementare due metodi virtuali: void exposeEvent(QExposeEvent *event) e void resizeEvent(QResizeEvent *event).

Il primo metodo viene chiamato ogni volta che cambia la visibilità della nostra superficie. Se è visibile, dovremo ridisegnare il triangolo

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

Il metodo paint(), che vedremo tra poco, è quello che disegna il nostro 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.1379173291.txt.gz · Ultima modifica: 2013/09/14 17:41 da mickele

Facebook Twitter Google+ Digg Reddit LinkedIn StumbleUpon Email