在 OpenGL 的全屏四边形中将 QGraphicsScene 显示为纹理
Posted
技术标签:
【中文标题】在 OpenGL 的全屏四边形中将 QGraphicsScene 显示为纹理【英文标题】:Showing a QGraphicsScene as a texture in a full Screen Quad of OpenGL 【发布时间】:2019-12-10 12:42:49 【问题描述】:我需要绘制一个占据整个视图的 Quad(我不确定视口在技术上是否准确),并将 QGraphicsScene 场景作为纹理加载到 Quad 上。这是我的 OpenGLCanvas 代码,它简单地继承并重新实现了 QOpenGLWidget。
我现在包含所有文件以编译和重现示例
openglcanvas.h:
#ifndef OPENGLCANVAS_H
#define OPENGLCANVAS_H
#include <QOpenGLWidget>
#include "targettest.h"
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QOpenGLShader>
class OpenGLCanvas : public QOpenGLWidget, protected QOpenGLFunctions
Q_OBJECT
public:
explicit OpenGLCanvas(QWidget *parent = nullptr);
void initializeGL() override;
void paintGL() override;
void resizeGL(int w, int h) override;
private:
float xrot,yrot,zrot;
TargetTest targetTest;
// QOpenGLBuffer buffer;
// QOpenGLShaderProgram shaderProg;
;
#endif // OPENGLCANVAS_H
openglcanvas.cpp
#include "openglcanvas.h"
OpenGLCanvas::OpenGLCanvas(QWidget *parent):QOpenGLWidget(parent)
xrot = yrot = zrot = 0.0;
targetTest.initialize(100,100);
initializeOpenGLFunctions();
void OpenGLCanvas::initializeGL()
glClearColor(0.0,0.0,1.0,0.0);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
void OpenGLCanvas::paintGL()
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-2, +2, -2, +2, 1.0, 15.0);
//glOrtho(-1, +1, -1, +1, 1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0, 0.0, -7.0);
glRotatef(xrot, 1.0, 0.0, 0.0);
glRotatef(yrot, 0.0, 1.0, 0.0);
glRotatef(zrot, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
targetTest.renderCurrentPosition(0,0,0,0);
glBindTexture(GL_TEXTURE_2D, targetTest.getFBO()->texture());
glEnable(GL_TEXTURE_2D);
glBegin(GL_QUADS);
glNormal3d(0,0,+1);
glTexCoord2f(0.0, 0.0); glVertex3d(-1,-1,0);
glTexCoord2f(0.0, 2.0); glVertex3d(-1,1,0);
glTexCoord2f(2.0, 2.0); glVertex3d(1,1,0);
glTexCoord2f(2.0, 0.0); glVertex3d(1,-1,0);
glEnd();
glFlush();
void OpenGLCanvas::resizeGL(int width, int height)
Q_UNUSED(width) Q_UNUSED(height)
// int side = qMin(width, height);
// glViewport((width - side) / 2, (height - side) / 2, side, side);
// glMatrixMode(GL_PROJECTION);
// glLoadIdentity();
// glOrtho(-2, +2, -2, +2, 1.0, 15.0);
// //glOrtho(-1, +1, -1, +1, 1.0, 1.0);
// glMatrixMode(GL_MODELVIEW);
这里是目标测试的代码,它绘制了一些圆圈作为 QGraphicsScene 并将它们渲染到 QOpenGLFramebufferObject:
targettest.h
#ifndef TARGETTEST_H
#define TARGETTEST_H
#include <QPainter>
#include <QGraphicsScene>
#include <QGraphicsEllipseItem>
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <QOffscreenSurface>
#include <QDebug>
#include <QOpenGLPaintDevice>
class TargetTest
public:
TargetTest();
~TargetTest();
void initialize(qint32 screenw, qint32 screenh);
void finalize();
void renderCurrentPosition(qint32 rx, qint32 ry, qint32 lx, qint32 ly);
QOpenGLFramebufferObject * getFBO() return m_pFbo;
private:
const qreal K_LARGE_D = 0.1;
const qreal K_SMALL_D = 0.02;
QOpenGLContext *m_pOpenGLContext;
QOpenGLFramebufferObject *m_pFbo;
QOffscreenSurface *m_pOffscreenSurface;
QGraphicsScene *canvas;
QGraphicsEllipseItem *leftEye;
QGraphicsEllipseItem *rightEye;
qreal r;
;
#endif // TARGETTEST_H
targettest.cpp
#include "targettest.h"
TargetTest::TargetTest()
canvas = nullptr;
leftEye = nullptr;
rightEye = nullptr;
void TargetTest::initialize(qint32 screenw, qint32 screenh)
canvas = new QGraphicsScene(0,0,screenw,screenh);
canvas->setBackgroundBrush(QBrush(Qt::gray));
// Qt OpenGL Initialization.
QSurfaceFormat format;
format.setMajorVersion( 4 );
format.setMinorVersion( 1 );
format.setProfile( QSurfaceFormat::CompatibilityProfile );
m_pOpenGLContext = new QOpenGLContext();
m_pOpenGLContext->setFormat( format );
if( !m_pOpenGLContext->create() )
qDebug() << "Open GL Context initialization failed";
// create an offscreen surface to attach the context and FBO to
m_pOffscreenSurface = new QOffscreenSurface();
m_pOffscreenSurface->create();
m_pOpenGLContext->makeCurrent( m_pOffscreenSurface );
m_pFbo = new QOpenGLFramebufferObject(screenw, screenh, GL_TEXTURE_2D );
qreal R = K_LARGE_D*screenw/2;
r = K_SMALL_D*screenw/2;
qreal horizontal_target_space = (screenw -4*2*R)/2;
qreal vertical_target_space = (screenh -4*2*R)/2;
qreal horizontal_margin = R;
qreal vertical_margin = R;
qreal offset = (R-r);
QList<QPointF> largeTargetUpperRight;
largeTargetUpperRight << QPointF(horizontal_margin ,vertical_margin)
<< QPointF(horizontal_margin + 2*R + horizontal_target_space ,vertical_margin)
<< QPointF(horizontal_margin + 4*R + 2*horizontal_target_space,vertical_margin)
<< QPointF(horizontal_margin ,vertical_margin + 2*R + vertical_target_space)
<< QPointF(horizontal_margin + 2*R + horizontal_target_space ,vertical_margin + 2*R + vertical_target_space)
<< QPointF(horizontal_margin + 4*R + 2*horizontal_target_space,vertical_margin + 2*R + vertical_target_space)
<< QPointF(horizontal_margin ,vertical_margin + 4*R + 2*vertical_target_space)
<< QPointF(horizontal_margin + 2*R + horizontal_target_space ,vertical_margin + 4*R + 2*vertical_target_space)
<< QPointF(horizontal_margin + 4*R + 2*horizontal_target_space,vertical_margin + 4*R + 2*vertical_target_space);
for (qint32 i = 0; i < largeTargetUpperRight.size(); i++)
QGraphicsEllipseItem *circle = canvas->addEllipse(0,0,2*R,2*R,QPen(Qt::black),QBrush(Qt::darkBlue));
QGraphicsEllipseItem *innerCircle = canvas->addEllipse(0,0,2*r,2*r,QPen(Qt::black),QBrush(Qt::yellow));
qreal x = largeTargetUpperRight.at(i).x();
qreal y = largeTargetUpperRight.at(i).y();
circle->setPos(x,y);
innerCircle->setPos(x+offset,y+offset);
// Initializing the
leftEye = canvas->addEllipse(0,0,2*r,2*r,QPen(),QBrush(QColor(0,0,255,100)));
rightEye = canvas->addEllipse(0,0,2*r,2*r,QPen(),QBrush(QColor(0,255,0,100)));
void TargetTest::renderCurrentPosition(qint32 rx, qint32 ry, qint32 lx, qint32 ly)
if (!canvas) return;
leftEye->setPos(lx-r,ly-r);
rightEye->setPos(rx-r,ry-r);
// m_pOpenGLContext->makeCurrent( m_pOffscreenSurface );
// m_pFbo->bind();
QOpenGLPaintDevice device( m_pFbo->size() );
QPainter painter( &device );
canvas->render( &painter );
// m_pFbo->release();
TargetTest::~TargetTest()
finalize();
void TargetTest::finalize()
if (canvas)
delete canvas;
canvas = nullptr;
leftEye = nullptr;
rightEye = nullptr;
为了完成这个我的 mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui class MainWindow;
QT_END_NAMESPACE
class MainWindow : public QMainWindow
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
;
#endif // MAINWINDOW_H
还有我的 mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
ui->setupUi(this);
MainWindow::~MainWindow()
delete ui;
我的 main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
最后是我的 mainwindow.ui 文件
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="OpenGLCanvas" name="openGLWidget"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>20</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>OpenGLCanvas</class>
<extends>QOpenGLWidget</extends>
<header location="global">openglcanvas.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
所以发生的情况是,当我运行程序时,我只是看到屏幕被 glClearColor 中加载的颜色填充(我已经测试过将颜色从黑色更改,我可以确认这是我看到的),就是这样.
那我做错了什么?
更新:我已经用@Rabbid76修改了paintGL()中的代码,但是问题没有改变
UPDATE2:按照评论的建议删除一些代码后现在我看到了:
看起来应该是带有圆圈的灰色小方块应该占据整个屏幕,因为它应该覆盖整个四边形。
【问题讨论】:
需要注意的是,从OpenGLCanvas::paintGL
调用的TargetTest::renderCurrentPosition
使用m_pOpenGLContext->makeCurrent(m_pOffscreenSurface)
更改了当前的GL 上下文,但您永远不会重新绑定原始上下文。尝试在调用m_pOpenGLContext->makeCurrent(m_pOffscreenSurface)
后立即在OpenGLCanvas::paintGL
中添加makeCurrent()
,看看是否有任何改变。
我们正在做一些事情。我正在更新问题!
现在的问题是你不再使用你的 FBO m_pFbo
-- 你的 QOpenGLPaintDevice
与默认的 FBO/上下文相关联,这就是为什么 QGraphicsScene
被直接渲染到窗口而不是纹理。如果您可以编辑您的问题以提供minimal reproducible example,我也许可以看一下,但如果没有,恐怕我只是在猜测。
唯一真正缺少的是主文件和一个 .h 文件。稍后我将与他们一起更新问题。
@G.M.那里有完整的代码。我认为 .pro 不是必需的,对吗?不然我就放上来了。谢谢!
【参考方案1】:
显示的代码存在一些问题,但主要问题在于您创建和使用 FBO 的方式。在TargetTest::initialize
你有...
QSurfaceFormat format;
format.setMajorVersion( 4 );
format.setMinorVersion( 1 );
format.setProfile( QSurfaceFormat::CompatibilityProfile );
m_pOpenGLContext = new QOpenGLContext();
m_pOpenGLContext->setFormat( format );
if( !m_pOpenGLContext->create() )
qDebug() << "Open GL Context initialization failed";
// create an offscreen surface to attach the context and FBO to
m_pOffscreenSurface = new QOffscreenSurface();
m_pOffscreenSurface->create();
m_pOpenGLContext->makeCurrent( m_pOffscreenSurface );
m_pFbo = new QOpenGLFramebufferObject(screenw, screenh, GL_TEXTURE_2D );
但是,在这种情况下,无需创建新的 GL 上下文 - 只需在当前上下文中创建 FBO。于是上面的代码就变得简单了……
m_pFbo = new QOpenGLFramebufferObject(screenw, screenh, GL_TEXTURE_2D);
现在考虑TargetTest::renderCurrentPosition
实现...
if (!canvas)
return;
leftEye->setPos(lx-r,ly-r);
rightEye->setPos(rx-r,ry-r);
QOpenGLPaintDevice device( m_pFbo->size() );
QPainter painter( &device );
canvas->render( &painter );
这里您没有绑定 FBO,因此 QOpenGLPaintDevice
与默认帧缓冲区相关联,QGraphicsScene
被渲染到该缓冲区,而不是附加到 FBO 的纹理。以上应该变成...
if (!canvas)
return;
leftEye->setPos(lx-r,ly-r);
rightEye->setPos(rx-r,ry-r);
m_pFbo->bind();
QOpenGLPaintDevice device( m_pFbo->size() );
QPainter painter( &device );
canvas->render( &painter );
m_pFbo->release();
现在QGraphicsScene
将被渲染为正确的纹理并按OpenGLCanvas::paintGL
的预期使用。
上面显示的两个更改是修复的主要部分,但还有其他问题。首先,来自QOpenGLPaintDevice
documentation...
当使用 QPainter 绘制到 QOpenGLPaintDevice 时, 当前的 OpenGL 上下文将被绘制引擎改变以反映 它的需要。应用程序不应依赖于 OpenGL 状态 重置为其原始条件,尤其是当前着色器 程序、OpenGL 视口、纹理单元和绘图模式。
这有点模糊,但在目前的情况下,一旦完成对 FBO 的渲染,就必须修复视口。所以更改OpenGLCanvas::paintGL
,使其在调用`TargetTest::renderCurrentPosition) 后立即调用glViewport
...
targetTest.renderCurrentPosition(0,0,0,0);
glViewport(0, 0, width(), height());
另一个问题是,您可能会在任何上下文被创建并成为当前上下文之前使用 GL 调用。您的 OpenGLCanvas
ctor 实现是...
OpenGLCanvas::OpenGLCanvas(QWidget *parent):QOpenGLWidget(parent)
xrot = yrot = zrot = 0.0;
targetTest.initialize(100,100);
initializeOpenGLFunctions();
targetTest.initialize(100,100)
和 initializeOpenGLFunctions()
都假定一个有效的 GL 上下文。这些应该从ctor中删除并添加到OpenGLCanvas::initializeGL
的顶部...
void OpenGLCanvas::initializeGL ()
initializeOpenGLFunctions();
targetTest.initialize(100,100);
glClearColor(0.0,0.0,1.0,0.0);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
执行上述所有操作会导致(至少对我而言)以下框架...
顺便说一句,您目前正在使用旧的固定管道功能,例如 glVertex3d
等。该功能已被弃用一段时间,甚至可能在某些平台上不可用。相反,您应该转向使用缓冲区对象、着色器等的“现代”OpenGL。为此,您可能需要查看诸如learnopengl.com 之类的网站。
【讨论】:
我使用的是旧代码,因为至少我认为我理解了它。但事实证明,代码永远不会按照我想要的方式运行。谢谢你的回答。我将从这个示例继续,并将您的答案标记为正确以上是关于在 OpenGL 的全屏四边形中将 QGraphicsScene 显示为纹理的主要内容,如果未能解决你的问题,请参考以下文章