0.前言

(摘抄自 LearnOpenGL 教程中文版)

到目前为止,我们已经使用了很多屏幕缓冲了:用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲。这些缓冲结合起来叫做帧缓冲(Framebuffer),它被储存在内存中。OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,甚至是深度缓冲和模板缓冲。

我们目前所做的所有操作都是在默认帧缓冲的渲染缓冲上进行的。默认的帧缓冲一般框架或库会为我们生成和配置。有了我们自己的帧缓冲,我们就能够有更多方式来渲染了。

非默认的帧缓冲,渲染指令不会对窗口的视觉输出有任何影响,因此,渲染到一个自定义的帧缓冲也被叫做离屏渲染(Off-screen Rendering)。我们将场景渲染到一个附加到帧缓冲对象上的颜色纹理中,之后在默认帧缓冲绘制这个纹理就能将我们处理好的图像输出到窗口上。

1.知识点

一个完整的帧缓冲需要满足以下的条件:

  • 附加至少一个缓冲(颜色、深度或模板缓冲)。
  • 至少有一个颜色附件(Attachment)。
  • 所有的附件都必须是完整的(保留了内存)。
  • 每个缓冲都应该有相同的样本数。
//为了简化操作,我这里将帧缓冲固定为400x400大小
    int SCR_WIDTH = 400;
    int SCR_HEIGHT = 400;
    //创建帧缓冲对象
    unsigned int framebuffer;
    glGenFramebuffers(1, &framebuffer);
    //绑定为激活的帧缓冲
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    //添加一个纹理附件
    unsigned int texturebuffer;
    glGenTextures(1, &texturebuffer);
    glBindTexture(GL_TEXTURE_2D, texturebuffer);
    //只设置了大小,分配了内存但是没填充数据(NULL),填充这个纹理将会在我们渲染到帧缓冲之后来进行
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    //纹理附加到帧缓冲
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texturebuffer, 0);
    //添加一个渲染缓冲附件
    unsigned int rbo;
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT); // use a single renderbuffer object for both a depth AND stencil buffer.
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo); // now actually attach it
    //完成所有操作后,检测帧缓冲是否完整
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
        qDebug() << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!";
    }
    //切换到OpenGL默认帧缓冲
    //glBindFramebuffer(GL_FRAMEBUFFER, 0);
    //释放帧缓冲对象
    //glDeleteFramebuffers(1, &framebuffer);

教程里还讲到了渲染缓冲,渲染缓冲对象附加的好处是,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。渲染缓冲对象直接将所有的渲染数据储存到它的缓冲中,不会做任何针对纹理格式的转换,让它变为一个更快的可写储存介质。然而,渲染缓冲对象通常都是只写的,所以你不能读取它们(比如使用纹理访问)。当然你仍然还是能够使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素。

通常的规则是,如果你不需要从一个缓冲中采样数据,那么对这个缓冲使用渲染缓冲对象会是明智的选择。如果你需要从缓冲中采样颜色或深度值等数据,那么你应该选择纹理附件。性能方面它不会产生非常大的影响的。

需要注意的是, QOpenGLWidget 中的默认帧缓冲不能绑定到 0,因为此类的上下文定义了一个自己的帧缓冲对象进行渲染,所以我们应该使用接口来获取当前的默认帧缓冲 ID:

//GLuint QOpenGLWidget::defaultFramebufferObject() const
glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());

Qt 提供了  QOpenGLFramebufferObject 类来管理帧缓冲相关的操作,参考文档:

https://doc.qt.io/qt-5/qopenglframebufferobject.html

大致的使用流程:

QOpenGLFramebufferObjectFormat format;
    format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    format.setMipmap(true); //默认为false
    //format.setSamples(8);
    format.setTextureTarget(GL_TEXTURE_2D); //默认为GL_TEXTURE_2D
    format.setInternalTextureFormat(GL_RGBA);

    const QSize device_size = size() * devicePixelRatioF();
    QOpenGLFramebufferObject *fboBuffer = new QOpenGLFramebufferObject(device_size, format);
    fboBuffer->bind();
    glBindTexture(GL_TEXTURE_2D, fboBuffer->texture());
    fboBuffer->addColorAttachment(fboBuffer->size(), GL_RGBA);
    fboBuffer->release();

2.实现代码

(项目git链接:https://github.com/gongjianbo/OpenGLwithQtWidgets.git

实现效果:

android openGL native 离屏渲染 qt opengl 离屏渲染_帧缓冲

#pragma once
#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLTexture>
#include <QOpenGLFramebufferObject>
#include <QOpenGLFramebufferObjectFormat>
#include <QVector3D>
#include <QMatrix4x4>
#include <QQuaternion>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QMenu>

//帧缓冲
//QOpenGLWidget窗口上下文
//QOpenGLFunctions访问OpenGL接口,可以不继承作为成员变量使用
class GLFrameBufferQt
        : public QOpenGLWidget
        , protected QOpenGLFunctions_4_5_Core
{
    Q_OBJECT
public:
    explicit GLFrameBufferQt(QWidget *parent = nullptr);
    ~GLFrameBufferQt();

protected:
    //【】继承QOpenGLWidget后重写这三个虚函数
    //设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
    void initializeGL() override;
    //渲染OpenGL场景,每当需要更新小部件时使用
    void paintGL() override;
    //设置OpenGL视口、投影等,每当尺寸大小改变时调用
    void resizeGL(int width, int height) override;

    //鼠标操作,重载Qt的事件处理
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;

private:
    //初始化
    void initScreen();
    void initFbo();
    //初始化或resize之后重置帧缓冲区
    void resetFbo();
    //在paintGL中调用
    void paintScreen();
    void paintFbo();
    //析构时调用
    void freeScreen();
    void freeFbo();

private:
    //【】命名screen表示默认缓冲区相关
    //着色器程序
    QOpenGLShaderProgram screenShaderProgram;
    //顶点数组对象
    QOpenGLVertexArrayObject screenVao;
    //顶点缓冲
    QOpenGLBuffer screenVbo;

    //【】命名fbo表示自定义缓冲区相关
    //着色器程序
    QOpenGLShaderProgram fboShaderProgram;
    //顶点数组对象
    QOpenGLVertexArrayObject fboCubeVao, fboPlaneVao;
    //顶点缓冲
    QOpenGLBuffer fboCubeVbo, fboPlaneVbo;
    //纹理
    QOpenGLTexture *fboCubeTexture{ nullptr }, *fboPlaneTexture{ nullptr };
    //帧缓冲
    QOpenGLFramebufferObject *fboBuffer{ nullptr };

    //旋转
    QVector3D rotationAxis;
    QQuaternion rotationQuat;
    //透视投影的fovy参数,视野范围
    float projectionFovy{ 45.0f };
    //鼠标位置
    QPoint mousePos;
    //右键菜单
    QMenu *menu;
    //图像处理算法选择
    int algorithmType{ 0 };
    int drawMode{ 0 };
};
#include "GLFrameBufferQt.h"

GLFrameBufferQt::GLFrameBufferQt(QWidget *parent)
    : QOpenGLWidget(parent)
{
    setFocusPolicy(Qt::ClickFocus); //默认没有焦点

    QSurfaceFormat fmt = format();
    fmt.setRenderableType(QSurfaceFormat::OpenGL);
    fmt.setProfile(QSurfaceFormat::CoreProfile);
    fmt.setVersion(4, 5);
    setFormat(fmt);

    //右键菜单
    menu = new QMenu(this);
    //0默认,1反相,2灰度,3锐化,4模糊,5边缘检测
    menu->addAction("Default", [this]{ algorithmType = 0; update(); });
    menu->addAction("Inversion", [this]{ algorithmType = 1; update(); });
    menu->addAction("Gray", [this]{ algorithmType = 2; update(); });
    menu->addAction("Sharpen", [this]{ algorithmType = 3; update(); });
    menu->addAction("Blur", [this]{ algorithmType = 4; update(); });
    menu->addAction("Edge-detection", [this]{ algorithmType = 5; update(); });
    menu->addAction("Set Fill Mode", [this]{ drawMode = 0; update(); });
    menu->addAction("Set Line Mode", [this]{ drawMode = 1; update(); });
    menu->addAction("Set Point Mode", [this]{ drawMode = 2; update(); });
}

GLFrameBufferQt::~GLFrameBufferQt()
{
    //initializeGL在显示时才调用,释放未初始化的会异常
    if(!isValid())
        return;
    //QOpenGLWidget
    //三个虚函数不需要makeCurrent,对应的操作已由框架完成
    //但是释放时需要设置当前上下文
    makeCurrent();
    freeScreen();
    freeFbo();
    doneCurrent();
}

void GLFrameBufferQt::initializeGL()
{
    //QOpenGLFunctions
    //为当前上下文初始化opengl函数解析
    initializeOpenGLFunctions();

    initScreen();
    initFbo();
}

void GLFrameBufferQt::paintGL()
{
    //渲染自定义帧缓冲
    paintFbo();
    //渲染默认帧缓冲
    paintScreen();
}

void GLFrameBufferQt::resizeGL(int width, int height)
{
    if (width < 1 || height < 1) {
        return;
    }
    //重置自定义帧缓冲大小
    resetFbo();
}

void GLFrameBufferQt::mousePressEvent(QMouseEvent *event)
{
    event->accept();
    if(event->button()==Qt::RightButton){
        menu->popup(QCursor::pos());
    }else{
        mousePos = event->pos();
    }
}

void GLFrameBufferQt::mouseReleaseEvent(QMouseEvent *event)
{
    event->accept();
}

void GLFrameBufferQt::mouseMoveEvent(QMouseEvent *event)
{
    event->accept();
    //参照示例cube
    QVector2D diff = QVector2D(event->pos()) - QVector2D(mousePos);
    mousePos = event->pos();
    QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();
    rotationAxis = (rotationAxis + n).normalized();
    //不能对换乘的顺序
    rotationQuat = QQuaternion::fromAxisAndAngle(rotationAxis, 2.0f) * rotationQuat;

    update();
}

void GLFrameBufferQt::wheelEvent(QWheelEvent *event)
{
    event->accept();
    //fovy越小,模型看起来越大
    if(event->delta() < 0){
        //鼠标向下滑动为-,这里作为zoom out
        projectionFovy += 0.5f;
        if(projectionFovy > 90)
            projectionFovy = 90;
    }else{
        //鼠标向上滑动为+,这里作为zoom in
        projectionFovy -= 0.5f;
        if(projectionFovy < 1)
            projectionFovy = 1;
    }
    update();
}

void GLFrameBufferQt::initScreen()
{
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
    //着色器代码
    //in输入,out输出,uniform从cpu向gpu发送
    const char *vertex_str = R"(#version 450
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;
out vec2 TexCoords;
void main()
{
    TexCoords = aTexCoords;
    gl_Position = vec4(aPos.x, aPos.y, 0.0f, 1.0f);
})";
    //算法选择:0默认,1反相,2灰度,3锐化,4模糊,5边缘检测
    const char *fragment_str = R"(#version 450
out vec4 FragColor;
in vec2 TexCoords;
uniform int algorithm;
const float offset = 1.0f / 300.0f;
uniform sampler2D screenTexture;
void main()
{
vec2 offsets[9] = vec2[](
    vec2(-offset,  offset),
    vec2( 0.0f,    offset),
    vec2( offset,  offset),
    vec2(-offset,  0.0f),
    vec2( 0.0f,    0.0f),
    vec2( offset,  0.0f),
    vec2(-offset, -offset),
    vec2( 0.0f,   -offset),
    vec2( offset, -offset)
);
switch(algorithm)
{
case 0:{
vec3 color = texture(screenTexture, TexCoords).rgb;
FragColor = vec4(color, 1.0f);
}break;
case 1:{
vec3 color = texture(screenTexture, TexCoords).rgb;
FragColor = vec4(1.0f - color, 1.0f);
}break;
case 2:{
vec3 color = texture(screenTexture, TexCoords).rgb;
float average = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
FragColor = vec4(average, average, average, 1.0f);
}break;
case 3:{
float kernels[9] = float[](
-1, -1, -1,
-1,  9, -1,
-1, -1, -1
);
vec3 sampleTex[9];
for(int i = 0; i < 9; ++i)
    sampleTex[i] = texture(screenTexture, TexCoords.st + offsets[i]).rgb;
vec3 color;
for(int i = 0; i < 9; ++i)
    color += sampleTex[i] * kernels[i];
FragColor = vec4(color, 1.0f);
}break;
case 4:{
float kernels[9] = float[](
1.0f/16.0f, 2.0f/16.0f, 1.0f/16.0f,
2.0f/16.0f, 4.0f/16.0f, 2.0f/16.0f,
1.0f/16.0f, 2.0f/16.0f, 1.0f/16.0f
);
vec3 sampleTex[9];
for(int i = 0; i < 9; ++i)
    sampleTex[i] = texture(screenTexture, TexCoords.st + offsets[i]).rgb;
vec3 color;
for(int i = 0; i < 9; ++i)
    color += sampleTex[i] * kernels[i];
FragColor = vec4(color, 1.0f);
}break;
case 5:{
float kernels[9] = float[](
1,  1,  1,
1, -9,  1,
1,  1,  1
);
vec3 sampleTex[9];
for(int i = 0; i < 9; ++i)
    sampleTex[i] = texture(screenTexture, TexCoords.st + offsets[i]).rgb;
vec3 color;
for(int i = 0; i < 9; ++i)
    color += sampleTex[i] * kernels[i];
FragColor = vec4(color, 1.0f);
}break;
default:{
vec3 color = texture(screenTexture, TexCoords).rgb;
FragColor = vec4(color, 1.0f);
}break;
}
})";

    //将source编译为指定类型的着色器,并添加到此着色器程序
    if(!screenShaderProgram.addCacheableShaderFromSourceCode(
                QOpenGLShader::Vertex,vertex_str)){
        qDebug()<<"compiler vertex error"<<screenShaderProgram.log();
    }
    if(!screenShaderProgram.addCacheableShaderFromSourceCode(
                QOpenGLShader::Fragment,fragment_str)){
        qDebug()<<"compiler fragment error"<<screenShaderProgram.log();
    }
    //使用addShader()将添加到该程序的着色器链接在一起。
    if(!screenShaderProgram.link()){
        qDebug()<<"link shaderprogram error"<<screenShaderProgram.log();
    }

    //顶点数据
    GLfloat quadVertices[] = {
        // positions   // texCoords
        -1.0f,  1.0f,  0.0f, 1.0f,
        -1.0f, -1.0f,  0.0f, 0.0f,
        1.0f, -1.0f,  1.0f, 0.0f,

        -1.0f,  1.0f,  0.0f, 1.0f,
        1.0f, -1.0f,  1.0f, 0.0f,
        1.0f,  1.0f,  1.0f, 1.0f
    };

    screenVao.create();
    screenVao.bind();
    screenVbo = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    screenVbo.create();
    screenVbo.bind();
    screenVbo.allocate(quadVertices, sizeof(quadVertices));
    screenShaderProgram.setAttributeBuffer(0, GL_FLOAT, 0, 2, sizeof(GLfloat) * 4);
    screenShaderProgram.enableAttributeArray(0);
    screenShaderProgram.setAttributeBuffer(1, GL_FLOAT, sizeof(GLfloat) * 2, 2, sizeof(GLfloat) * 4);
    screenShaderProgram.enableAttributeArray(1);

    screenShaderProgram.bind();
    screenShaderProgram.setUniformValue("screenTexture", 0);
    screenShaderProgram.release();
}

void GLFrameBufferQt::initFbo()
{
    resetFbo();
    glBindFramebuffer(GL_FRAMEBUFFER, fboBuffer->handle());

    //着色器代码
    //in输入,out输出,uniform从cpu向gpu发送
    const char *vertex_str = R"(#version 450
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
    TexCoords = aTexCoords;
    gl_Position = projection * view * model * vec4(aPos, 1.0f);
})";
    const char *fragment_str = R"(#version 450
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D texture1;
void main()
{
    FragColor = texture(texture1, TexCoords);
})";

    //将source编译为指定类型的着色器,并添加到此着色器程序
    if(!fboShaderProgram.addCacheableShaderFromSourceCode(
                QOpenGLShader::Vertex,vertex_str)){
        qDebug()<<"compiler vertex error"<<fboShaderProgram.log();
    }
    if(!fboShaderProgram.addCacheableShaderFromSourceCode(
                QOpenGLShader::Fragment,fragment_str)){
        qDebug()<<"compiler fragment error"<<fboShaderProgram.log();
    }
    //使用addShader()将添加到该程序的着色器链接在一起。
    if(!fboShaderProgram.link()){
        qDebug()<<"link shaderprogram error"<<fboShaderProgram.log();
    }

    //顶点数据,一个立方体cube和一个平面plane
    GLfloat cubeVertices[] = {
        // positions          // texture Coords
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
        0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };
    GLfloat planeVertices[] = {
        // positions          // texture Coords
        5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
        -5.0f, -0.5f,  5.0f,  0.0f, 0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,

        5.0f, -0.5f,  5.0f,  2.0f, 0.0f,
        -5.0f, -0.5f, -5.0f,  0.0f, 2.0f,
        5.0f, -0.5f, -5.0f,  2.0f, 2.0f
    };

    //cube
    fboCubeVao.create();
    fboCubeVao.bind();
    fboCubeVbo = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    fboCubeVbo.create();
    fboCubeVbo.bind();
    fboCubeVbo.allocate(cubeVertices, sizeof(cubeVertices));
    fboShaderProgram.setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(GLfloat) * 5);
    fboShaderProgram.enableAttributeArray(0);
    fboShaderProgram.setAttributeBuffer(1, GL_FLOAT, sizeof(GLfloat) * 3, 2, sizeof(GLfloat) * 5);
    fboShaderProgram.enableAttributeArray(1);

    //plane
    fboPlaneVao.create();
    fboPlaneVao.bind();
    fboPlaneVbo = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    fboPlaneVbo.create();
    fboPlaneVbo.bind();
    fboPlaneVbo.allocate(planeVertices, sizeof(planeVertices));
    fboShaderProgram.setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(GLfloat) * 5);
    fboShaderProgram.enableAttributeArray(0);
    fboShaderProgram.setAttributeBuffer(1, GL_FLOAT, sizeof(GLfloat) * 3, 2, sizeof(GLfloat) * 5);
    fboShaderProgram.enableAttributeArray(1);

    fboShaderProgram.bind();
    fboShaderProgram.setUniformValue("texture1", 0);
    fboShaderProgram.release();

    //2d纹理
    fboCubeTexture = new QOpenGLTexture(QImage(":/container.jpg").mirrored(), QOpenGLTexture::GenerateMipMaps);
    if(!fboCubeTexture->isCreated()){
        qDebug() << "Failed to load texture";
    }
    fboCubeTexture->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
    fboCubeTexture->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);//
    fboCubeTexture->setMinificationFilter(QOpenGLTexture::Linear);
    fboCubeTexture->setMagnificationFilter(QOpenGLTexture::Linear);

    fboPlaneTexture = new QOpenGLTexture(QImage(":/metal.png").mirrored(), QOpenGLTexture::GenerateMipMaps);
    if(!fboPlaneTexture->isCreated()){
        qDebug() << "Failed to load texture";
    }
    fboPlaneTexture->setWrapMode(QOpenGLTexture::DirectionS, QOpenGLTexture::Repeat);
    fboPlaneTexture->setWrapMode(QOpenGLTexture::DirectionT, QOpenGLTexture::Repeat);//
    fboPlaneTexture->setMinificationFilter(QOpenGLTexture::Linear);
    fboPlaneTexture->setMagnificationFilter(QOpenGLTexture::Linear);
}

void GLFrameBufferQt::resetFbo()
{
    if(fboBuffer){
        delete fboBuffer;
    }

    QOpenGLFramebufferObjectFormat format;
    format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    format.setMipmap(true); //默认为false
    //format.setSamples(8);
    format.setTextureTarget(GL_TEXTURE_2D); //默认为GL_TEXTURE_2D
    format.setInternalTextureFormat(GL_RGBA);

    const QSize device_size = size() * devicePixelRatioF();
    fboBuffer = new QOpenGLFramebufferObject(device_size, format);
    fboBuffer->bind();
    glBindTexture(GL_TEXTURE_2D, fboBuffer->texture());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    fboBuffer->addColorAttachment(fboBuffer->size(), GL_RGBA);
    fboBuffer->release();
}

void GLFrameBufferQt::paintScreen()
{
    if(!fboBuffer){
        return;
    }
    //切换到QOpenGLWidget.context默认的帧缓冲,这里不能用0来作为默认帧缓冲
    glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    //绘制模式
    if(drawMode == 0){
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    }else if(drawMode == 1){
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }else{
        glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
        glPointSize(4.0f);
    }

    screenShaderProgram.bind();
    screenVao.bind();
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, fboBuffer->texture());
    screenShaderProgram.setUniformValue("algorithm", algorithmType);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    screenShaderProgram.release();
    //恢复
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

void GLFrameBufferQt::paintFbo()
{
    if(!fboBuffer){
        return;
    }
    //渲染自定义帧缓冲
    fboBuffer->bind();
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //激活纹理
    glActiveTexture(GL_TEXTURE0);

    fboShaderProgram.bind();
    //透视投影
    QMatrix4x4 projection;
    projection.perspective(projectionFovy, 1.0f * width() / height(), 0.1f, 100.0f);
    QMatrix4x4 view;
    view.translate(0.0f, 0.0f, -5.0f);
    view.rotate(rotationQuat);
    QMatrix4x4 model;
    fboShaderProgram.setUniformValue("view", view);
    fboShaderProgram.setUniformValue("projection", projection);

    fboCubeTexture->bind();
    fboCubeVao.bind();
    model.setToIdentity();
    model.translate(-1.0f, 0.0f, -1.0f);
    fboShaderProgram.setUniformValue("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    model.setToIdentity();
    model.translate(2.0f, 0.0f, 0.0f);
    fboShaderProgram.setUniformValue("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 36);

    fboPlaneTexture->bind();
    fboPlaneVao.bind();
    model.setToIdentity();
    model.translate(0.0f, -0.01f, 0.0f);
    fboShaderProgram.setUniformValue("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 6);

    fboShaderProgram.release();
    fboBuffer->release();
}

void GLFrameBufferQt::freeScreen()
{
    screenVbo.destroy();
    screenVao.destroy();
}

void GLFrameBufferQt::freeFbo()
{
    fboCubeVbo.destroy();
    fboCubeVao.destroy();
    if(fboCubeTexture){
        delete fboCubeTexture;
    }
    fboPlaneVbo.destroy();
    fboPlaneVao.destroy();
    if(fboPlaneTexture){
        delete fboPlaneTexture;
    }
    if(fboBuffer){
        delete fboBuffer;
    }
}