使用 Qt5 进行屏幕外渲染(openGL)

Posted

技术标签:

【中文标题】使用 Qt5 进行屏幕外渲染(openGL)【英文标题】:Do offscreen render(openGL) with Qt5 【发布时间】:2013-06-20 19:04:00 【问题描述】:

使用openGL做一些图像处理,第一个实验是 将彩色图像转换为灰色,一切都很好,除了我不想要 显示小部件。

如果我不调用“show()”,QGLWidget 将不会开始渲染纹理 我可以在不显示小部件的情况下渲染纹理吗? QGLWidget 是一个合适的工具吗?

部分代码

#include <QDebug>

#include "toGray.hpp"

toGray::toGray(std::string const &vertex_file, std::string const &fragment_file, QWidget *parent)
    :basicGLWidget(vertex_file, fragment_file, parent) //read shaders, compile them, allocate VBO



void toGray::initializeVertexBuffer()

    std::vector<GLfloat> const vertex
        -1.0f,  1.0f, 0.0f, 1.0f,
        1.0f,  1.0f, 0.0f, 1.0f,
        -1.0f, -1.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, -1.0f, 0.0f, 1.0f,
        -1.0f, -1.0f, 0.0f, 1.0f,
    ;

    initializeVertexBufferImpl(vertex); //copy the data into QOpenGLBuffer

    QImage img(":/simpleGPGPU/images/emili.jpg");
    texture_addr_ = bindTexture(img);
    resize(img.width(), img.height());

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);


void toGray::paintGL()

    qglClearColor(Qt::white);
    glClear(GL_COLOR_BUFFER_BIT);

    program_.bind();
    bind_buffer();
    program_.enableAttributeArray("qt_Vertex");
    program_.setAttributeBuffer( "qt_Vertex", GL_FLOAT, 0, 4);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture_addr_);

    glDrawArrays(GL_TRIANGLES, 0, get_buffer(0).size());

    program_.disableAttributeArray("qt_Vertex");
    program_.release();
    glActiveTexture(0);
    release_buffer();

顶点着色器

attribute highp vec4 qt_Vertex;
varying highp vec2 qt_TexCoord0;

void main(void)
    
    gl_Position =  qt_Vertex;
    qt_TexCoord0 = (qt_Vertex.xy + vec2(1.0)) * 0.5;

片段着色器

uniform sampler2D source;
varying highp vec2 qt_TexCoord0;

vec3 toGray(vec3 rgb)

    return vec3(rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114);


void main(void)
        
    vec3 gray = toGray(texture2D(source, qt_TexCoord0).rgb);
    gl_FragColor = vec4(gray, 0.0);

编辑:使用QWindow FBO进行离屏渲染,结果是空白图片

代码可以编译,但是QOpenGLFrameBufferObject返回的QImage是空的。

.hpp

#ifndef OFFSCREENEXP_HPP
#define OFFSCREENEXP_HPP

#include <QOpenGLFunctions>
#include <QWindow>

class QImage;
class QOpenGLContext;

class offScreenExp : public QWindow, protected QOpenGLFunctions

public:
    explicit offScreenExp(QWindow *parent = 0);

    QImage render();

private:
    QOpenGLContext *context_;
;

#endif // OFFSCREENEXP_HPP

.cpp

#include <QImage>
#include <QOpenGLBuffer>
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <QOpenGLShaderProgram>
#include <QString>
#include <QWidget>

#include <QDebug>

#include "offScreenExp.hpp"

offScreenExp::offScreenExp(QWindow *parent) :
    QWindow(parent),
    context_(nullptr)

    setSurfaceType(QWindow::OpenGLSurface);
    setFormat(QSurfaceFormat());
    create();


QImage offScreenExp::render()

    //create the context
    if (!context_) 
        context_ = new QOpenGLContext(this);
        QSurfaceFormat format;
        context_->setFormat(format);

        if (!context_->create())
            qFatal("Cannot create the requested OpenGL context!");
    

    context_->makeCurrent(this);
    initializeOpenGLFunctions();

    //load image and create fbo
    QString const prefix("/Users/Qt/program/experiment_apps_and_libs/openGLTest/simpleGPGPU/");
    QImage img(prefix + "images/emili.jpg");
    if(img.isNull())
      qFatal("image is null");
    
    QOpenGLFramebufferObject fbo(img.size());
    qDebug()<<"bind success? : "<<fbo.bind();

    //if(glCheckFramebufferStatus(fbo.handle()) != GL_FRAMEBUFFER_COMPLETE)
    //    qDebug()<<"frame buffer error";
    //
    qDebug()<<"has opengl fbo : "<<QOpenGLFramebufferObject::hasOpenGLFramebufferObjects();

    //use two triangles two cover whole screen
    std::vector<GLfloat> const vertex
        -1.0f,  1.0f, 0.0f, 1.0f,
        1.0f,  1.0f, 0.0f, 1.0f,
        -1.0f, -1.0f, 0.0f, 1.0f,
        1.0f, 1.0f, 0.0f, 1.0f,
        1.0f, -1.0f, 0.0f, 1.0f,
        -1.0f, -1.0f, 0.0f, 1.0f,
    ;

    //initialize vbo
    QOpenGLBuffer buffer(QOpenGLBuffer::VertexBuffer);
    buffer.create();
    buffer.setUsagePattern(QOpenGLBuffer::StaticDraw);
    buffer.bind();
    buffer.allocate(&vertex[0], vertex.size() * sizeof(GLfloat) );
    buffer.release();

    //create texture
    GLuint rendered_texture;
    glGenTextures(1, &rendered_texture);

    // "Bind" the newly created texture : all future texture functions will modify this texture
    glBindTexture(GL_TEXTURE_2D, rendered_texture);

    //naive solution, better encapsulate the format in a function
    if(img.format() == QImage::Format_Indexed8)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, img.width(), img.height(), 0, GL_RED, GL_UNSIGNED_BYTE, img.scanLine(0));
    else if(img.format() == QImage::Format_RGB888)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.width(), img.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, img.scanLine(0));
    else
        QImage temp = img.convertToFormat(QImage::Format_RGB888);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.width(), img.height(), 0, GL_RGB, GL_UNSIGNED_BYTE, temp.scanLine(0));
    

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glBindTexture(GL_TEXTURE_2D, 0);

    //compile and link program
    QOpenGLShaderProgram program;
    if(!program.addShaderFromSourceCode(QOpenGLShader::Vertex,
                                        "attribute highp vec4 qt_Vertex;"
                                        "varying highp vec2 qt_TexCoord0;"

                                        "void main(void)"
                                        ""
                                        "   gl_Position =  qt_Vertex;"
                                        "   qt_TexCoord0 = (qt_Vertex.xy + vec2(1.0)) * 0.5;"
                                        ""))
        qDebug()<<"QOpenGLShader::Vertex error : " + program.log();
    

    // Compile fragment shader
    if (!program.addShaderFromSourceCode(QOpenGLShader::Fragment,
                                         "uniform sampler2D source;"
                                         "varying highp vec2 qt_TexCoord0;"

                                         "vec3 toGray(vec3 rgb)"
                                         ""
                                         " return vec3(rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114);"
                                         ""

                                         "void main(void)"
                                         ""
                                         "vec3 gray = toGray(texture2D(source, qt_TexCoord0).rgb);"
                                         "gl_FragColor = vec4(gray, 0.0);"
                                         ""
                                         ))
        qDebug()<<"QOpenGLShader::Fragment error : " + program.log();
    

    // Link shader pipeline
    if (!program.link())
        qDebug()<<"link error : " + program.log();
    

    //render the texture as usual
    //render the texture as usual
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
glViewport(0, 0, img.width(), img.height());

program.bind();
buffer.bind();
program.enableAttributeArray("qt_Vertex");
program.setAttributeBuffer( "qt_Vertex", GL_FLOAT, 0, 4);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, rendered_texture);

//bind and create fbo
QOpenGLFramebufferObject fbo(img.size());
qDebug()<<"bind success? : "<<fbo.bind();

glDrawArrays(GL_TRIANGLES, 0, buffer.size());
QImage result = fbo.toImage();

program.disableAttributeArray("qt_Vertex");
program.release();    
buffer.release();

fbo.release();
glActiveTexture(0);
glBindTexture(GL_TEXTURE_2D, 0);
context_->doneCurrent();

    return result;

我尽量简化代码,但还是很冗长

【问题讨论】:

你的#version指令在哪里? 我对我应该使用哪个版本感到困惑,但我发现即使我没有指定#version,只要我坚持使用opengl es 2.0,带有openGL的Qt也可以理解glsl。请如果我错了,请纠正我,一个发现 openGL 非常复杂的新手。 【参考方案1】:

对于离屏渲染,尝试渲染到QOpenGLFramebufferObject,可以是converted into a QImage,然后可以很容易地保存到磁盘。

然而,为此,您仍然需要一个要渲染的表面(根据QOpenGLContext::makeCurrent() 的要求),因此您唯一的选择确实是使用QWindowQGLWidget 来获得这样的表面。

在 Qt 5.1 中,将有一个名为 QOffscreenSurface 的新类,它可能最适合您的用例。您将使用QOffscreenSurface 为您的OpenGL context 获取表面,然后使用QOpenGLFramebufferObject 渲染到FBO,然后调用QOpenGLFramebufferObject::toImage() 以访问像素。

【讨论】:

谢谢,我正在研究“OpenGL Window 示例”,openGL 上下文并尝试了解什么是 FBO,相当复杂的东西(我是唯一认为 openGL 非常复杂的人吗?)。将发布如果我能弄清楚如何做我想做的事,就会回答。即使qml2好用,当你需要更多的图形能力时,openGL的知识看起来是必须的。

以上是关于使用 Qt5 进行屏幕外渲染(openGL)的主要内容,如果未能解决你的问题,请参考以下文章

使用 Docker 使用 QOffscreenSurface 进行屏幕外渲染

使用带有 FBO 的 OpenGL 3.2+ 的 Linux 屏幕外渲染

使用 QOpenGLWidget 进行屏幕外渲染的最简单方法

opengl 3 qt渲染空白屏幕

为啥在使用 FBO 进行多重采样时,OpenGL 会使我的场景变亮?

OpenGL屏幕外渲染