在 OpenGL 的全屏四边形中将 QGraphicsScene 显示为纹理

Showing a QGraphicsScene as a texture in a full Screen Quad of OpenGL

我需要绘制一个占据整个视图的四边形(我不确定视口在技术上是否准确)并将 QGraphicsScene 场景作为纹理加载到四边形上。这是我的 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() 中的代码,但是问题没有改变

更新 2:按照评论的建议删除了一些代码后现在我看到了:

应该看起来像带圆圈的灰色小方块应该占据整个屏幕,因为它应该覆盖整个四边形。

显示的代码存在一些问题,但主要问题在于您创建和使用 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...

When painting to a QOpenGLPaintDevice using QPainter, the state of the current OpenGL context will be altered by the paint engine to reflect its needs. Applications should not rely upon the OpenGL state being reset to its original conditions, particularly the current shader program, OpenGL viewport, texture units, and drawing modes.

这有点含糊,但在手头的例子中,一旦完成对 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 等。该功能已被弃用很长一段时间,甚至可能在某些平台上不可用。相反,您应该转向使用缓冲区对象、着色器等的 'modern' OpenGL。为此,您可能需要查看 learnopengl.com.

等网站