C++ 在Qt中渲染巨大的图像

C++ 在Qt中渲染巨大的图像,c++,qt,opengl,C++,Qt,Opengl,我必须在基于Qt的应用程序中渲染一个巨大的图像(例如,30.000 x 30.000像素) 我可以通过OpenGL实现这一点,但我将其作为单个纹理加载。因此,我受到图形卡最大纹理大小(在本例中为16.368像素)的限制。我需要实现类似平铺或类似的功能,同时保持良好的渲染性能 是否有任何例子可以实现这一点,最好是通过良好的Qt集成?(不一定是OpenGL)。 或者还有其他的起点吗 谢谢您可以使用QOpenGLxxx类来完成。这里是一个完整的函数示例,具有很好的性能,并使用了现代OpenGL技术。每

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

我可以通过OpenGL实现这一点,但我将其作为单个纹理加载。因此,我受到图形卡最大纹理大小(在本例中为16.368像素)的限制。我需要实现类似平铺或类似的功能,同时保持良好的渲染性能

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


谢谢

您可以使用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);
}
和着色器:

// 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);
}
您将得到以下结果:


你可以用手缝它;)@vitaly-t我也希望开箱即用的平铺渲染,如果有这样的库,请注意,询问库的问题是您的图像的来源是什么?你怎么知道你的渲染是痛苦的?你有一个例子吗?如果您需要缩小它,可以通过QtConcurrent在工作线程中进行。除此之外,巨大图像的一小部分的光点速度非常快,例如,在小部件中以1:1的比例显示该大图像的片段将非常快。你是否真的尝试过并确定自己有问题,或者你是在不知道问题会出现的情况下假设问题?@KubaOber我用OpenGL尝试过这个方法,但它只会使应用程序崩溃。如果你仍然需要将巨大的图像分割成瓷砖,那么使用OpenGL有什么意义?@pepe:为了在显示时获得良好的性能。您是否有一个使用QPainter和QImage显示此类图像并具有可提高性能的运行示例?我对它很感兴趣。我认为将一幅巨大的图像分割成较小的图像并不难。您在初始化时拆分一次,并在显示时获得使用加速技术的性能优势。我是不是错了?我遇到了完全相同的问题,我求助于OpenGL获得了一个很好的结果。整个问题是在内存中获取瓷砖(并有效地管理它们)。你如何解决这个问题?OP没有澄清这一点。一旦你这样做了,Qpaint就不会那么慢了。@peppe如果你想缩小视图,那么要绘制的瓷砖的数量就会增加,并且QImage/Qpaint会快速显示它们的极限。您必须处理mipmap(缩小)和管理可见的瓷砖(放大)。在这两种情况下,您必须处理线性插值以获得正确的视觉结果。OpenGL比QImage更有效:这些功能是本机的。使用QImage制作mipmap系统就像是重新发明轮子。对于图形卡的内存带宽,您必须使用磁盘上的磁贴缓存,并根据需要在纹理中升级/卸载缓存。不容易做到!我知道,但是OP到目前为止没有谈到缩放,没有谈到图像的来源,没有提到问题出在哪里(QImage无法加载,因为它太大?内存不足?格式奇怪?)。。。
#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);
}