您当前的位置: 首页 >  qt

龚建波

暂无认证

  • 2浏览

    0关注

    312博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

OpenGL with QtWidgets:帧缓冲/离屏渲染

龚建波 发布时间:2022-03-06 01:26:56 ,浏览量:2

(本文是LearnOpenGL的学习笔记, 教程中文翻译地址https://learnopengl-cn.github.io/(备用地址https://learnopengl-cn.readthedocs.io/zh/latest/),写于 2022-03-06)

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() bind();
    glBindTexture(GL_TEXTURE_2D, fboBuffer->texture());
    fboBuffer->addColorAttachment(fboBuffer->size(), GL_RGBA);
    fboBuffer->release();
2.实现代码

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

实现效果:

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//帧缓冲
//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()            
关注
打赏
1655829268
查看更多评论
0.0524s