在 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-&gt;makeCurrent(m_pOffscreenSurface) 更改了当前的GL 上下文,但您永远不会重新绑定原始上下文。尝试在调用m_pOpenGLContext-&gt;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 显示为纹理的主要内容,如果未能解决你的问题,请参考以下文章

如何在 OpenGL 3.2 中绘制全屏四边形?

更快的全屏渲染(OpenGL/glsl)?

全屏模糊 GLSL 显示对角线(OpenGL 核心)

如何在 C++ 中将位图绘制为 OpenGL 纹理?

如何在OpenGL中将像素作为纹理绘制到多边形?

OpenGL4.0教程 计算着色器简介