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