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