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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章