我必须在基于Qt的应用中渲染一个巨大的图像(例如30.000 x 30.000像素)。
我可以通过OpenGL执行此操作,但我将其作为单个纹理加载。因此,我受限于我的显卡最大纹理尺寸(在这种情况下,16.368像素)。我需要实现像平铺或类似的东西,同时保持良好的渲染性能。
有没有任何例子可以实现这一点,最好是Qt集成良好? (不一定是OpenGL)。 或者还有其他起点吗?
谢谢
答案 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);
}
你得到这个结果: