在Qt渲染巨大的图像

时间:2016-04-01 14:28:17

标签: c++ qt opengl

我必须在基于Qt的应用中渲染一个巨大的图像(例如30.000 x 30.000像素)。

我可以通过OpenGL执行此操作,但我将其作为单个纹理加载。因此,我受限于我的显卡最大纹理尺寸(在这种情况下,16.368像素)。我需要实现像平铺或类似的东西,同时保持良好的渲染性能。

有没有任何例子可以实现这一点,最好是Qt集成良好? (不一定是OpenGL)。 或者还有其他起点吗?

谢谢

1 个答案:

答案 0 :(得分:4)

您可以使用QOpenGLxxx类完成此操作。这是一个完整的功能示例,具有非常好的性能和使用现代OpenGL技术。每个图块都是独立的纹理。在此示例中,我对所有纹理使用相同的图像,但在您的情况下,您可以创建原始图像的切片并将其用作切片纹理。

QOpenGLWidget用作显示切片的基类。 QOpenGLBuffer来管理openGL顶点缓冲区。 QOpenGLShaderProgram来管理着色器。

tilewidget.cpp:

#include "tiledwidget.h"

#include <QOpenGLFunctions>
#include <QPainter>
#include <QOpenGLTexture>
#include <QTransform>
#include <QOpenGLBuffer>
#include <QVector2D>

// a small class to manage vertices in the vertex buffer
class Vertex2D
{
public:
    Vertex2D(){}
    Vertex2D(const QPointF &p, const QPointF &c) :
        position(p)
      , coords(c)
    {
    }
    QVector2D position; // position of the vertex
    QVector2D coords; // texture coordinates of the vertex
};


TiledWidget::TiledWidget(QWidget *parent) :
    QOpenGLWidget(parent)
  , m_rows(5)
  , m_cols(5)
  , m_vertexBuffer(new QOpenGLBuffer)
  , m_program(new QOpenGLShaderProgram(this))
{
}
TiledWidget::~TiledWidget()
{
    qDeleteAll(m_tiles);
    delete m_vertexBuffer;
    delete m_program;
}
void TiledWidget::initializeGL()
{
    // tiles creation based on a 256x256 image
    QImage image(":/lenna.png");
    if (image.format() != QImage::Format_ARGB32_Premultiplied)
        image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);

    for (int row = 0; row < m_rows; row++)
    {
        for (int col = 0; col < m_cols; col++)
        {
            QOpenGLTexture* tile = new QOpenGLTexture(QOpenGLTexture::Target2D);
            if (!tile)
            {
                qDebug() << "Ooops!";
                break;
            }
            if (!tile->create())
            {
                qDebug() << "Oooops again!";
                break;
            }

            tile->setSize(256, 256);
            tile->setFormat(QOpenGLTexture::RGBA8_UNorm);
            // you can manage the number of mimap you desire...
            // by default 256x256 => 9 mipmap levels will be allocated:
            // 256, 128, 64, 32, 16, 8, 4, 2 and 1px
            // to modify this use tile->setMipLevels(n);
            tile->setMinificationFilter(QOpenGLTexture::Nearest);
            tile->setMagnificationFilter(QOpenGLTexture::Nearest);
            tile->setData(image, QOpenGLTexture::GenerateMipMaps);
            m_tiles << tile;
        }
    }
    // vertex buffer initialisation
    if (!m_vertexBuffer->create())
    {
        qDebug() << "Ooops!";
        return;
    }
    m_vertexBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
    m_vertexBuffer->bind();
    // room for 2 triangles of 3 vertices
    m_vertexBuffer->allocate(2 * 3 * sizeof(Vertex2D));
    m_vertexBuffer->release();

    // shader program initialisation
    if (!m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/basic_vert.glsl"))
    {
        qDebug() << "Ooops!";
        return;
    }
    if (!m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/basic_frag.glsl"))
    {
        qDebug() << "Ooops!";
        return;
    }
    if (!m_program->link())
    {
        qDebug() << "Ooops!";
        return;
    }

    // ok, we are still alive at this point...
}
// this slot is called at windows close before the widget is destroyed
// use this to cleanup opengl
void TiledWidget::shutDown()
{
    // don't forget makeCurrent, OpenGL is a state machine!
    makeCurrent();
    foreach(QOpenGLTexture* tile, m_tiles)
    {
        if (tile->isCreated())
            tile->destroy();
    }
    if (m_vertexBuffer)
        m_vertexBuffer->destroy();
}
void TiledWidget::resizeGL(int width, int height)
{
    Q_UNUSED(width);
    Q_UNUSED(height);
    // ...
}
// you can alternatively override QOpenGLWidget::paintGL if you don't need
// to draw things with classic QPainter
void TiledWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)

    QPainter painter(this);
    // native draw
    painter.beginNativePainting();
    drawGL();
    painter.endNativePainting();

    // draw overlays if needed
    // ...draw something with painter...
}
void TiledWidget::drawGL()
{
    // always a good thing to make current
    makeCurrent();
    // enable texturing
    context()->functions()->glEnable(GL_TEXTURE_2D);
    // enable blending
    context()->functions()->glEnable(GL_BLEND);
    // blending equation (remember OpenGL textures are premultiplied)
    context()->functions()->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    // clear
    context()->functions()->glClearColor(0.8, 0.8, 0.8, 1);
    context()->functions()->glClear(GL_COLOR_BUFFER_BIT);
    context()->functions()->glClear(GL_DEPTH_BUFFER_BIT);

    // viewport and matrices setup for a 2D tile system
    context()->functions()->glViewport(0, 0, width(), height());
    QMatrix4x4 projectionMatrix;
    projectionMatrix.setToIdentity();
    projectionMatrix.ortho(0, width(), height(), 0, -1, 1);
    QMatrix4x4 viewProjectionMatrix;
    // use a QTransform to scale, translate, rotate your view
    viewProjectionMatrix = projectionMatrix * QMatrix4x4(m_transform);

    // program setup
    m_program->bind();
    // a good practice if you have to manage multiple shared context
    // with shared resources: the link is context dependant.
    if (!m_program->isLinked())
        m_program->link();

    // binding the buffer
    m_vertexBuffer->bind();

    // setup of the program attributes
    int pos = 0, count;
    // positions : 2 floats
    count = 2;
    m_program->enableAttributeArray("vertexPosition");
    m_program->setAttributeBuffer("vertexPosition", GL_FLOAT, pos, count, sizeof(Vertex2D));
    pos += count * sizeof(float);

    // texture coordinates : 2 floats
    count = 2;
    m_program->enableAttributeArray("textureCoordinates");
    m_program->setAttributeBuffer("textureCoordinates", GL_FLOAT, pos, count, sizeof(Vertex2D));
    pos += count * sizeof(float);

    m_program->setUniformValue("viewProjectionMatrix", viewProjectionMatrix);
    m_program->setUniformValue("f_opacity", (float) 0.5);


    // draw each tile
    for (int row = 0; row < m_rows; row++)
    {
        for (int col = 0; col < m_cols; col++)
        {
            QRect rect = tileRect(row, col);
            // write vertices in the buffer
            // note : better perf if you precreate this buffer

            Vertex2D v0;
            v0.position = QVector2D(rect.bottomLeft());
            v0.coords = QVector2D(0, 1);

            Vertex2D v1;
            v1.position = QVector2D(rect.topLeft());
            v1.coords = QVector2D(0, 0);

            Vertex2D v2;
            v2.position = QVector2D(rect.bottomRight());
            v2.coords = QVector2D(1, 1);

            Vertex2D v3;
            v3.position = QVector2D(rect.topRight());
            v3.coords = QVector2D(1, 0);

            int vCount = 0;
            // first triangle v0, v1, v2
            m_vertexBuffer->write(vCount * sizeof(Vertex2D), &v0, sizeof(Vertex2D)); vCount++;
            m_vertexBuffer->write(vCount * sizeof(Vertex2D), &v1, sizeof(Vertex2D)); vCount++;
            m_vertexBuffer->write(vCount * sizeof(Vertex2D), &v2, sizeof(Vertex2D)); vCount++;

            // second triangle v1, v3, v2
            m_vertexBuffer->write(vCount * sizeof(Vertex2D), &v1, sizeof(Vertex2D)); vCount++;
            m_vertexBuffer->write(vCount * sizeof(Vertex2D), &v3, sizeof(Vertex2D)); vCount++;
            m_vertexBuffer->write(vCount * sizeof(Vertex2D), &v2, sizeof(Vertex2D)); vCount++;

            // bind the tile texture on texture unit 0
            // you can add other textures binding them in texture units 1, 2...
            QOpenGLTexture* tile = m_tiles.at(tileIndex(row, col));
            // activate texture unit 0
            context()->functions()->glActiveTexture(GL_TEXTURE0);
            // setup texture options here if needed...
            // set sampler2D on texture unit 0
            m_program->setUniformValue("f_tileTexture", 0);
            // bind texture
            tile->bind();
            // draw 2 triangles = 6 vertices starting at offset 0 in the buffer
            context()->functions()->glDrawArrays(GL_TRIANGLES, 0, 6);
            // release texture
            tile->release();
        }
    }
    m_vertexBuffer->release();
    m_program->release();
}
// compute the tile index
int TiledWidget::tileIndex(int row, int col)
{
    return row * m_cols + col;
}

// compute the tile rectangle given a row and a col.
// Note : You will have to manage the opengl texture border effect
// to get correct results. To do this you must overlap textures when you draw them.
QRect TiledWidget::tileRect(int row, int col)
{
    int x = row * 256;
    int y = col * 256;
    return QRect(x, y, 256, 256);
}

tilewidget.h:

#ifndef TILEDWIDGET_H
#define TILEDWIDGET_H

#include <QOpenGLWidget>

#include <QOpenGLFramebufferObjectFormat>
#include <QOpenGLShaderProgram>

#include <QTransform>

class QOpenGLTexture;
class QOpenGLBuffer;
class QOpenGLShaderProgram;

class TiledWidget : public QOpenGLWidget
{
public:
    TiledWidget(QWidget *parent = 0);
    ~TiledWidget();
public slots:
    void shutDown();
private:
    QTransform m_transform;
    int m_rows;
    int m_cols;
    QVector<QOpenGLTexture*> m_tiles;
    QOpenGLBuffer *m_vertexBuffer;
    QOpenGLShaderProgram* m_program;

    void resizeGL(int width, int height);
    void initializeGL();
    void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
    void drawGL();
    int tileIndex(int row, int col);
    QRect tileRect(int row, int col);
};

#endif // TILEDWIDGET_H

mainwindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "tiledwidget.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    m_tiledWidget = new TiledWidget(this);
    setCentralWidget(m_tiledWidget);
}
MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::closeEvent(QCloseEvent *event)
{
    Q_UNUSED(event);
    // destroy textures before widget desctruction
    m_tiledWidget->shutDown();
}

和着色器:

// vertex shader    
#version 330 core

in vec2 vertexPosition;
in vec2 textureCoordinates;

uniform mat4 viewProjectionMatrix;

out vec2 v_textureCoordinates;

void main()
{
    v_textureCoordinates = vec2(textureCoordinates);
    gl_Position = viewProjectionMatrix * vec4(vertexPosition, 0.0, 1.0);
}

// fragment shader
#version 330 core

// vertices datas
in vec2 v_textureCoordinates;

// uniforms
uniform sampler2D f_tileTexture; // tile texture
uniform float f_opacity = 1; // tile opacity

out vec4 f_fragColor; // shader output color

void main()
{
    // get the fragment color from the tile texture
    vec4 color = texture(f_tileTexture, v_textureCoordinates.st);
    // premultiplied output color
    f_fragColor = vec4(color * f_opacity);
}

你得到这个结果:

enter image description here