QT+OpenGL(3):FBO离屏渲染

目录

[1 FBO应用场景(AI)](#1 FBO应用场景(AI))

[1.1 核心场景(按你Qt项目的相关性排序)](#1.1 核心场景(按你Qt项目的相关性排序))

[1. 屏幕后期特效(最常用)](#1. 屏幕后期特效(最常用))

[2. 渲染到纹理(Render To Texture)](#2. 渲染到纹理(Render To Texture))

[3. 阴影贴图 ShadowMap(3D渲染)](#3. 阴影贴图 ShadowMap(3D渲染))

[4. MRT 多渲染目标(Multiple Render Targets)](#4. MRT 多渲染目标(Multiple Render Targets))

[5. 离屏导出图片(不显示窗口,直接保存图片)](#5. 离屏导出图片(不显示窗口,直接保存图片))

[6. 低分辨率渲染(提升性能)](#6. 低分辨率渲染(提升性能))

[1.2 区分两个附件类型(写代码必懂)](#1.2 区分两个附件类型(写代码必懂))

[1.3 使用流程](#1.3 使用流程)

[1.3.1 极简流程示例](#1.3.1 极简流程示例)

[2 Demo 源码](#2 Demo 源码)

[2.1 效果](#2.1 效果)

[2.2 myopengl.h文件](#2.2 myopengl.h文件)

[2.3 myopengl.cpp](#2.3 myopengl.cpp)


1 FBO应用场景(AI)

默认渲染是画到窗口屏幕(默认帧缓冲) ;FBO是自定义的离屏渲染目标 ,可以把画面渲染到一张纹理或者RenderBuffer,不直接显示在窗口上,渲染结果后续可以作为纹理继续采样处理,非常适合Qt+OpenGL做图像、3D特效开发。

1.1 核心场景(按你Qt项目的相关性排序)

1. 屏幕后期特效(最常用)

先把整个场景渲染到FBO的纹理,再拿这张纹理画全屏四边形,shader做图像处理。

  • 模糊、锐化、灰度、反相、LUT调色
  • 景深、运动模糊、胶片颗粒 流程:绑定FBO → 渲染场景到纹理 → 切回默认窗口帧缓冲 → 使用刚才生成的纹理绘制全屏矩形做后处理。

2. 渲染到纹理(Render To Texture)

你可以动态渲染内容生成一张GPU纹理,再贴到模型/UI上:

  • 镜子反射、水面反射(从镜子视角渲染场景到FBO纹理,贴到水面平面)
  • 动态看板:3D模型上的显示屏,实时渲染文字/视频画面
  • 数字孪生、大屏可视化里面动态贴图

3. 阴影贴图 ShadowMap(3D渲染)

从光源视角渲染场景,把深度值渲染到FBO深度纹理,主渲染pass采样这张深度图做阴影判断。

4. MRT 多渲染目标(Multiple Render Targets)

FBO可以一次性绑定多个颜色附件,一次drawcall同时输出多张纹理。

  • G-Buffer(延迟渲染):一次渲染输出 颜色、法线、位置、金属粗糙度多张贴图,后面做光照计算,游戏/数字孪生常用。

5. 离屏导出图片(不显示窗口,直接保存图片)

不弹出窗口,直接渲染画面到FBO,然后glReadPixels读像素到内存,保存图片。 适合:批量渲染截图、生成缩略图、服务端无窗口渲染。

6. 低分辨率渲染(提升性能)

用更小分辨率FBO渲染场景,再放大绘制到窗口,降低GPU开销;比如VR、性能弱的嵌入式Qt设备。

1.2 区分两个附件类型(写代码必懂)

  1. 纹理附件 Texture:FBO绑定纹理 → 渲染结果后续可以在shader里采样(上面绝大多数场景都用它)
  2. RenderBuffer渲染缓冲:不能当纹理采样,只做深度/模板缓冲,配合FBO使用,只用于深度测试存储。

1.3 使用流程

  1. FBO需要完整性检查 glCheckFramebufferStatus(GL_FRAMEBUFFER),附件格式/尺寸不匹配会渲染失败黑屏。
  2. 绑定FBO后,记得修改glViewport,尺寸要和FBO纹理分辨率一致。
  3. 绘制完成要解绑FBO,切回默认帧缓冲(0),不然继续往FBO绘制,窗口黑屏。
  4. FBO本身不存储像素,像素存储在绑定的纹理或RenderBuffer。

1.3.1 极简流程示例

复制代码
// 1. 创建FBO、创建颜色纹理附件、深度RenderBuffer,绑定到FBO
glBindFramebuffer(GL_FRAMEBUFFER, fboId);

// 2. 渲染场景,输出到FBO绑定的纹理
renderScene();

//3. 切回默认屏幕帧缓冲
glBindFramebuffer(GL_FRAMEBUFFER,0);

//4. 使用FBO生成的纹理,绘制到屏幕做后处理
drawQuadWithTexture(fboTexture);

2 Demo 源码

2.1 效果

2.2 myopengl.h文件

复制代码
#ifndef MYOPENGL_H
#define MYOPENGL_H

// 继承字OpenGLWidget
#include <QOpenGLWidget>
// 使用3.3的版本
#include <QOpenGLFunctions_3_3_Core>
// Qt的shaderProgram对象
#include <QOpenGLShaderProgram>

class MyOpenGL : public QOpenGLWidget,protected QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    explicit MyOpenGL(QWidget* parent = nullptr);
    ~MyOpenGL();
protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();
private:

    // 输入的图像纹理
    GLuint m_texture;

    // FBO帧缓冲对象,fbo渲染输出纹理
    GLuint fbo_VAO,fbo_VBO,fbo_EBO;
    GLuint FBO, fboTex, loc_fboTex;//fboTex 为输出纹理
    GLuint m_fboProgram;

    // shader 用于渲染的纹理位置
    GLuint screen_VAO,screen_VBO,screen_EBO;
    GLuint fbo_screen, loc_screenTex;
    GLuint m_screenProgram;


    // shader程序对象
    void initFBOShader();
    void initScreenShader();

    void createFBO();
    void setupFBOGeometry();
    void setupScreenGeometry();

    bool loadTextureFromQImage(const QString &path, GLuint &outTextureId);

};

#endif // MYOPENGL_H
#ifndef MYOPENGL_H
#define MYOPENGL_H

// 继承字OpenGLWidget
#include <QOpenGLWidget>
// 使用3.3的版本
#include <QOpenGLFunctions_3_3_Core>
// Qt的shaderProgram对象
#include <QOpenGLShaderProgram>

class MyOpenGL : public QOpenGLWidget,protected QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
public:
    explicit MyOpenGL(QWidget* parent = nullptr);
    ~MyOpenGL();
protected:
    virtual void initializeGL();
    virtual void resizeGL(int w, int h);
    virtual void paintGL();
private:

    // 输入的图像纹理
    GLuint m_texture;

    // FBO帧缓冲对象,fbo渲染输出纹理
    GLuint fbo_VAO,fbo_VBO,fbo_EBO;
    GLuint FBO, fboTex, loc_fboTex;//fboTex 为输出纹理
    GLuint m_fboProgram;

    // shader 用于渲染的纹理位置
    GLuint screen_VAO,screen_VBO,screen_EBO;
    GLuint fbo_screen, loc_screenTex;
    GLuint m_screenProgram;


    // shader程序对象
    void initFBOShader();
    void initScreenShader();

    void createFBO();
    void setupFBOGeometry();
    void setupScreenGeometry();

    bool loadTextureFromQImage(const QString &path, GLuint &outTextureId);

};

#endif // MYOPENGL_H

2.3 myopengl.cpp

复制代码
#include "myopengl.h"

MyOpenGL::MyOpenGL(QWidget *parent)
    :QOpenGLWidget(parent)
{

    qDebug()<< "inited";

}

MyOpenGL::~MyOpenGL()
{
    // 切换到当前widget的上下文
    makeCurrent();

    glDeleteVertexArrays(1, &screen_VAO);
    glDeleteBuffers(1,&screen_VBO);

    glDeleteBuffers(1, &screen_EBO);

    glDeleteProgram(m_screenProgram);
    doneCurrent();

}

void MyOpenGL::initializeGL()
{
    // 获取OpenGL的函数指针
    this->initializeOpenGLFunctions();

    // 获取屏幕的默认fbo


    // 1 opengl原生方法初始化Shader程序
    initScreenShader();
    // 2 初始化物体信息
    setupScreenGeometry();



    // fbo 帧缓冲
    createFBO();
    initFBOShader();
    setupFBOGeometry();

    // 加载纹理
    loadTextureFromQImage(":/res/2.png",m_texture);

}

void MyOpenGL::resizeGL(int w, int h)
{

    // 好像内部会重建,不用自己写
    // glBindTexture(GL_TEXTURE_2D,fboTex);
    // glTexImage2D(GL_TEXTURE_2D,0,GL_BGRA,w,h,0,GL_BGRA,GL_UNSIGNED_BYTE,nullptr);
    fbo_screen = defaultFramebufferObject();
}


void MyOpenGL::paintGL()
{
    // 1 进入到FBO缓冲
    glBindFramebuffer(GL_FRAMEBUFFER,FBO);

    glClearColor(1,0,0,1);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(m_fboProgram);

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D,m_texture);
    glUniform1i(loc_fboTex,0); // 将纹理索引链接到0号

    glBindVertexArray(fbo_VAO);

    glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,nullptr);

    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D,0);
    glUseProgram(0);


    // 2 进入到默认的屏幕渲染缓冲
    glBindFramebuffer(GL_FRAMEBUFFER, fbo_screen);

    // 清理屏幕
    glClearColor(0,1,0,1);
    glClear(GL_COLOR_BUFFER_BIT);

    // 使用shader程序
    glUseProgram(m_screenProgram); //原生写法
    // m_shaderProgram->bind(); // qt 的写法

    // 激活 shader 中的纹理
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, fboTex);
    glUniform1i(loc_screenTex,0);

    glBindVertexArray(screen_VAO);

    glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,nullptr);

    glBindVertexArray(0);
    glUseProgram(0);
}

void MyOpenGL::setupScreenGeometry(){

    float vertices[24] = {
        // 位置            纹理坐标
        -0.5f,  -0.5f, 0.0f,  0.0f,1.0f, // QImage的原点坐标在左上,而OpenGL在左下角
        -0.5f, 0.5f, 0.0f,  0.0f,0.0f,
        0.5f, -0.5f, 0.0f,  1.0f,1.0f,
        0.5f, 0.5f, 0.0f,  1.0f,0.0f
    };

    // 每3个点读一次
    unsigned int indexes[6] = {0,1,2,1,2,3};


    glGenVertexArrays(1,&screen_VAO);

    glGenBuffers(1, &screen_VBO);
    glGenBuffers(1, &screen_EBO);

    // 激活定点数组配置,开始记录定点属性
    glBindVertexArray(screen_VAO);

    // 绑定VBO 包含数据
    glBindBuffer(GL_ARRAY_BUFFER, screen_VBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);


    // EBO 缓冲
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, screen_EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indexes),indexes,GL_STATIC_DRAW);


    // 参数1:索引
    // 参数2:几个数据
    // 参数3:数据类型
    // 参数4:是否归一化,如果是0-255需要转为0-1
    // 参数5:步长
    // 参数6:偏移量
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,5 * sizeof(float),(void*)0);
    glEnableVertexAttribArray(0); // 指定为程序的0号layout

    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,5 * sizeof(float),(void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // 解绑VBO
    glBindBuffer(GL_ARRAY_BUFFER,0);
    // 解绑VAO 不需要解绑EBO,当VAO解绑时自动解绑
    glBindVertexArray(0);


}

bool MyOpenGL::loadTextureFromQImage(const QString &path, GLuint &outTextureId)
{

    QImage img(path);

    if(img.isNull()){
        qWarning()<< "加载失败";
        return false;
    }

    // 1 opengl 创建纹理
    glGenTextures(1, &outTextureId);
    // 2 opengl 绑定纹理类型
    glBindTexture(GL_TEXTURE_2D,outTextureId);
    // 3 opengl 上传数据
    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 GL_RGBA,
                 img.width(),
                 img.height(),
                 0,
                 GL_RGBA,
                 GL_UNSIGNED_BYTE,
                 img.bits());


    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);
    return true;
}


void MyOpenGL::initFBOShader()
{
    const char* vSrc = R"(
        #version 330 core
        layout(location=0) in vec3 aPos;
        layout(location=1) in vec2 aTex;
        out vec2 texCoord;
        void main(){
            gl_Position = vec4(aPos,1.0);
            texCoord = aTex;
        }
        )";

    const char* fSrc = R"(
        #version 330 core
        in vec2 texCoord;
        out vec4 FragColor;
        uniform sampler2D texture1;
        void main(){

            //FragColor = vec4(ourColor,1.0);
            // 加一点蓝色
            FragColor = texture(texture1,texCoord) + vec4(0,0,0.5,0);
        }
        )";

    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader,1,&vSrc,nullptr);
    glCompileShader(vertexShader);

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader,1,&fSrc,nullptr);
    glCompileShader(fragmentShader);

    m_fboProgram = glCreateProgram();
    glAttachShader(m_fboProgram,vertexShader);
    glAttachShader(m_fboProgram,fragmentShader);

    glLinkProgram(m_fboProgram);

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    loc_fboTex = glGetUniformLocation(m_fboProgram,"texture1");
}


void MyOpenGL::initScreenShader(){

    const char* vSrc = R"(
        #version 330 core
        layout(location=0) in vec3 aPos;
        layout(location=1) in vec2 aTex;
        out vec2 texCoord;
        void main(){
            gl_Position = vec4(aPos,1.0);
            texCoord = aTex;
        }
        )";

    const char* fSrc = R"(
        #version 330 core
        in vec2 texCoord;
        out vec4 FragColor;
        uniform sampler2D texture1;
        void main(){

            //FragColor = vec4(ourColor,1.0);
            FragColor = texture(texture1,texCoord);
        }
        )";

    // 创建shader,加载源码,编译
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader,1,&vSrc,nullptr);
    glCompileShader(vertexShader);

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader,1,&fSrc,nullptr);
    glCompileShader(fragmentShader);

    // 创建程序,绑定shader,链接为GPU程序
    m_screenProgram = glCreateProgram();
    glAttachShader(m_screenProgram,vertexShader);
    glAttachShader(m_screenProgram,fragmentShader);

    glLinkProgram(m_screenProgram);

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    loc_screenTex = glGetUniformLocation(m_screenProgram,"texture1");

}

void MyOpenGL::createFBO()
{
    glGenFramebuffers(1, &FBO);
    glBindFramebuffer(GL_FRAMEBUFFER,FBO);

    glGenTextures(1,&fboTex);
    glBindTexture(GL_TEXTURE_2D,fboTex);


    // 创建一个空纹理
    glTexImage2D(GL_TEXTURE_2D,
                 0,
                 GL_BGRA,
                 800,
                 800,
                 0,
                 GL_BGRA,
                 GL_UNSIGNED_BYTE,
                 nullptr);

    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);
    // 指定Framebuffer的纹理id
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,fboTex,0);

    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if(status != GL_FRAMEBUFFER_COMPLETE)
    {
        qDebug() << "FBO创建失败 status:" << status;
    }else{
             qDebug() << "FBO创建成功 status:" << status;
    }

    // 清空
    glBindFramebuffer(GL_FRAMEBUFFER,0);
}

void MyOpenGL::setupFBOGeometry()
{

    float vertices[24] = {
        // 位置            纹理坐标
        -0.5f,  -0.5f, 0.0f,  0.0f,0.0f,
        -0.5f, 0.5f, 0.0f,  0.0f,1.0f,
        0.5f, -0.5f, 0.0f,  1.0f,0.0f,
        0.5f, 0.5f, 0.0f,  1.0f,1.0f
    };

    // 每3个点读一次
    unsigned int indexes[6] = {0,1,2,1,2,3};

    glGenVertexArrays(1,&fbo_VAO);
    glGenBuffers(1,&fbo_VBO);
    glGenBuffers(1,&fbo_EBO);


    glBindVertexArray(fbo_VAO);


    glBindBuffer(GL_ARRAY_BUFFER,fbo_VBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(vertices),vertices,GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,fbo_EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indexes),indexes,GL_STATIC_DRAW);


    glVertexAttribPointer(0,3,GL_FLOAT,false,5 * sizeof(GLfloat),(void*)0);
    glVertexAttribPointer(1,2,GL_FLOAT,false,5 * sizeof(GLfloat),(void*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);


    glBindVertexArray(0); // vao ebo
    glBindBuffer(GL_ARRAY_BUFFER,0); //vbo


}

// void MyOpenGL::initQtShader()
// {

//     const char* vSrc = R"(
//         #version 330 core
//         layout(location=0) in vec3 aPos;
//         layout(location=1) in vec3 aColor;
//         out vec3 ourColor;
//         void main(){
//             gl_Position = vec4(aPos,1.0);
//             ourColor = aColor;
//         }
//         )";

//     const char* fSrc = R"(
//         #version 330 core
//         in vec3 ourColor;
//         out vec4 FragColor;
//         void main(){
//             FragColor = vec4(ourColor,1.0);
//         }
//         )";


//     m_shaderProgram = new QOpenGLShaderProgram();
//     QOpenGLShader * vertexShader = new QOpenGLShader(QOpenGLShader::Vertex);
//     vertexShader->compileSourceCode(vSrc);

//     QOpenGLShader * fragmentShader = new QOpenGLShader(QOpenGLShader::Fragment);
//     fragmentShader->compileSourceCode(fSrc);

//     // 对应原生的attach
//     m_shaderProgram->addShader(vertexShader);
//     m_shaderProgram->addShader(fragmentShader);

//     // 将编译完成的着色器目标对象进行链接,生成完整GPU着色器程序
//     m_shaderProgram->link();

// }
相关推荐
李福春1 小时前
技术思考问题6:技术演讲该晒成功架构,还是交代失败与取舍?
java·开发语言·架构·computer use·深圳同盟·腾讯云架构师技术同盟·反演思维
变与不变8061 小时前
防抖节流详解
开发语言·前端·javascript
雪域迷影2 小时前
Python Django 7 小时课程
开发语言·python·django
Escalating_xu2 小时前
【C 语言】编译和链接:从源文件、目标文件到可执行程序完整解析
c语言·开发语言
Liuqy-053 小时前
线程的互斥与同步机制——互斥锁、条件变量、无名信号量
linux·开发语言·线程·进程·条件变量·同步·互斥
geats人山人海3 小时前
java 7异常
android·java·开发语言
Pniubi3 小时前
分片的意义?
java·开发语言
阿皮 API4 小时前
Shopee虾皮商品详情API接口调用实战:Python获取东南亚商品数据与JSON解析
开发语言·python·json
niucloud-admin4 小时前
NIUCLOUD LITE AI开发文档--系统部署
开发语言·ai