LearnOpenGL学习笔记:OpenGL在QML中绘制一个三角形

0.前言

由于工作主要是Qt应用开发,所以学OpenGL自然想和Qt框架结合起来。在QtCreater中,搜索示例 "opengl" ,发现有一个 "opengl under qml" 的例子,照着做了之后才发现他渲染的是整个QWindow,而Window也没发现可以作为小部件嵌套,这明显不符合绘制小部件的需求。

无意间发现,示例 "fbo" 相关的正好是可以用OpenGL来绘制小部件的。经过一番折腾,总算画出了三角形,但是也发现很多奇怪的地方。按照LearnOpenGL教程上的VAO使用方式,同样的代码在Qt FBO中update刷新后图像就不见了,只剩个背景,无奈只好用Qt示例中的写法来绘制三角形。

目前还存在的问题是,缩放的时候窗口会闪烁。

1.重要的东西

首先,要使用 OpenGL 绘制一个 QML 小部件,需要继承 QQuickFramebufferObject (他也是QQuickItem的子类),然后实现 createRenderer() 虚函数,该函数返回一个渲染类型 Renderer 的指针,我们要做的就是继承 Renderer ,使用 OpenGL 进行绘制。

根据示例,我们自定义的渲染类继承 QQuickFramebufferObject::Renderer ,并实现 render() 和 createFramebufferObject() 两个虚函数。

在渲染类初始化时进行OpenGL上下文的初始化,着色器编译等。Qt封装了一个表示着色器程序对象的类 QOpenGLShaderProgram ,这样简化了不少操作。

渲染的时候会调用 render() 函数,这里面类似于 LearnOpenGL 教程 while 循环,进行我们的绘制操作。

初始化或改变控件尺寸时,会调用 createFramebufferObject() 函数,返回的是一个 QOpenGLFramebufferObject 类型指针,该类型封装一个OpenGL帧缓冲区对象,具体的设置还待进一步学习。

参照着 Qt 示例的代码,很快就实现了三角形的绘制,但也有一些小问题,比如:Qt FBO 绘制出来的图像和 OpenGL 是上下颠倒的,可能这样设计和 Qt 默认使用的屏幕坐标系有关,可以在 QQuickFramebufferObject 子类中设置 "setMirrorVertically(true);" 使上下镜像翻转。还有个问题是有一些 OpenGL 的设置会导致图像画不出来,也不知道是 Qt FBO 的问题,还是我写错了,比如禁用了 glCullFace 并开启了上下翻转,顶点设置了某些坐标就不可见了。 

最大的困扰还是编码上的变化,虽然 Qt 的 QOpenGLFunctions 提供了大部分 OpenGL 的接口,但是在 Qt FBO 框架下不好用,总是遇到图像没绘制的情况。没办法,尽量参照 Qt 示例的风格来写。

2.实现代码

这里贴出 Renderer 类的实现,完整工程见文末链接。

#ifndef FBORENDERER_H
#define FBORENDERER_H

#include <QtQuick/QQuickFramebufferObject>

//封装一个OpenGL帧缓冲区对象
#include <QtGui/QOpenGLFramebufferObject>

//代表了本地的OpenGL背景下,支持在OpenGL渲染QSurface
#include <QtGui/QOpenGLContext>

//允许OpenGL着色程序链接和使用
#include <QtGui/QOpenGLShaderProgram>

//提供了跨平台访问的OpenGL ES 2.0 API
//Qt Quick2.0使用专用的基于OpenGL ES2.0的Qt Quick Scene Graph场景图进行所有渲染
//#include <QtGui/QOpenGLFunctions>

//提供OpenGL 3.3核心配置文件
#include <QtGui/QOpenGLFunctions_3_3_Core>

class FBORenderer
        : public QQuickFramebufferObject::Renderer,
        protected QOpenGLFunctions_3_3_Core
{
public:
    FBORenderer();
    void render() override;
    QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) override;

private:
    void doInitialize();
    void doRender();

private:
    //着色器程序
    QOpenGLShaderProgram _program;
};

#endif // FBORENDERER_H
#include "FBORenderer.h"

#include <QDebug>

FBORenderer::FBORenderer()
{
    doInitialize();
}

void FBORenderer::render()
{
    doRender();
    //不知道为什么示例在这里加了update,加了在缩放的时候明显闪烁感更强
    //update();
}

QOpenGLFramebufferObject *FBORenderer::createFramebufferObject(const QSize &size)
{
    QOpenGLFramebufferObjectFormat format;
    format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
    format.setSamples(4);
    return new QOpenGLFramebufferObject(size, format);
}

void FBORenderer::doInitialize()
{
    //为当前上下文初始化OpenGL函数解析
    initializeOpenGLFunctions();

    //着色器代码
    //in输入,out输出,uniform从cpu向gpu发送
    const char *vertex_str=R"(#version 330 core
                           layout (location = 0) in vec3 vertices;
                           void main() {
                           gl_Position = vec4(vertices,1.0);
                           })";
    const char *fragment_str=R"(#version 330 core
                             uniform vec3 myVar;
                             void main() {
                             gl_FragColor = vec4(myVar,1.0);
                             })";

    //将source编译为指定类型的着色器,并添加到此着色器程序
    if(!_program.addCacheableShaderFromSourceCode(
                QOpenGLShader::Vertex,vertex_str)){
        qDebug()<<"compiler vertex error";
        exit(0);
    }
    //界面定义了变量myVar,将在程序中设定这个变量
    if(!_program.addCacheableShaderFromSourceCode(
                QOpenGLShader::Fragment,fragment_str)){
        qDebug()<<"compiler fragment error";
        exit(0);
    }
    //使用addShader()将添加到该程序的着色器链接在一起。
    _program.link();

    //将属性名称绑定到指定位置(这里location=0)
    _program.bindAttributeLocation("vertices", 0);
}

void FBORenderer::doRender()
{
    //【1】
    //启用或禁用写入深度缓冲区
    glDepthMask(true);

    glClearColor(0.1f, 0.1f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //glFrontFace()是opengl的初级命令,有两个基本作用:
    //一是可以用来用在某些特殊场合(比如剔除面片),二是可以提高渲染效率。
    //GL_CCW 表示窗口坐标上投影多边形的顶点顺序为逆时针方向的表面为正面。(默认值)
    //GL_CW 表示顶点顺序为顺时针方向的表面为正面。
    glFrontFace(GL_CW);
    //glCullFace()参数包括GL_FRONT和GL_BACK。
    //分别表示禁用多边形正面或者背面上的光照、阴影和颜色计算及操作,消除不必要的渲染计算。
    //glCullFace(GL_FRONT);
    //开启剔除操作效果(见glCullFace命令)
    //glEnable(GL_CULL_FACE);
    //当我们需要绘制透明图片时,就需要关闭GL_DEPTH_TEST并且打开混合glEnable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);

    //【2】
    //将此着色器程序绑定到活动的QOpenGLContext,并使其成为当前的着色器程序
    //同于调用glUseProgram(programid)
    _program.bind();
    //传递值
    _program.setUniformValue("myVar", QVector3D(0,1,0));

    //不知道为什么再FBO框架下,使用glBindVertexArray(VAO)缩放之后图像就没了
    //Qt默认是和OpenGL里颠倒过来的,上负下正,
    //但是可以在QQuickFramebufferObject设置setMirrorVertically(true);
    float vertices[] = {
        0.5f, -0.5f, 0.0f,  // bottom right
        -0.5f,-0.5f, 0.0f,  // bottom left
        0.0f,  0.5f, 0.0f   // top
    };
    //在此着色器程序中的位置处启用顶点数组,
    //以便着色器程序将使用在位置上由setAttributeArray()设置的值。
    _program.enableAttributeArray(0);
    //给对应位置设置顶点数组
    _program.setAttributeArray(0, GL_FLOAT, vertices, 3);
    //从数组数据渲染基元(render primitives from array data)
    glDrawArrays(GL_TRIANGLES, 0, 3);
    _program.disableAttributeArray(0);

    //从当前QOpenGLContext释放活动的着色器程序
    //相当于调用glUseProgram(0)
    _program.release();

    //【3】
    glDisable(GL_DEPTH_TEST);
    //glDisable(GL_CULL_FACE);
}

 GitHub项目链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qml/TestQml_20200128_FBO

(注意:我使用的Qt版本为5.12.6,编译器 MSVC2019 )

3.参考

Qt示例:路径如 E:\Qt\Qt5.12.6\Examples\Qt-5.12.6\quick\scenegraph\textureinsgnode\textureinsgnode.pro,或QtCreater示例搜索 "FBO"

LearnOpenGL三角:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/

博客:https://blog.csdn.net/brain2004/article/details/70768411?utm_source=blogxgwz0

发布了106 篇原创文章 · 获赞 31 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/gongjianbo1992/article/details/104107692