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