现代OpenGL系列教程(零)---在Qt/Quick中使用OpenGL 本是古典 何须时尚 2022-05-11 13:30 867阅读 0赞 ### **【写在前面】** ### 首先,想要说明的是,本系列学习教程是根据我自己学习的经历而写,并非完全科普性的,零基础的教程,而且其水平也很受我本身的水平影响,so 如果有不足之处,还请多多指教~~ 其次,本系列使用 Qt/Quick 来编写所有的opengl程序,所以和原生的opengl有一些区别,当然也不要担心,我会另开一个使用glfw的教程来完成同样的opengl程序。 -------------------- ### **【正文开始】** ### 在Qt中使用OpenGL,我所知的有三种方法: 1. 继承QOpenGLWidget(老版本Qt为QGLWidget),然后重新实现: \+void initializeGL() 此函数在第一次调用paintGL()或resizeGL()之前调用一次,主要用于初始化opengl环境,以及设置任何需要的OpenGL资源和状态。 \+viod resizeGL(int w, int h) 当该部件大小发生改变时调用此函数,主要用于重新设置纵横比(用于投影矩阵)。 \+void paintGL() 重新绘制该部件时调用此函数,主要用于实际的绘制。 2. 继承QOpenGLWindow,同QOpenGLWidget,差别是继承自QWindow,并且提供比widget更好的性能。 3. 本系列所使用的方法,继承QQuickItem,并connect必要的信号,这种方法好处是可以很轻松的和其他的Qml组件搭配使用,接下来我将详细讲解其步骤。 首先,我们需要一个继承QQuickItem的类,我将它命名为OpenGLItem: #ifndef OPENGLWINDOW_H #define OPENGLWINDOW_H #include "render.h" #include <QTime> #include <QQuickItem> #include <QBasicTimer> class MyRender : public Render { public: MyRender() { } ~MyRender() { } void render() { glClearColor(0.2, 0.3, 0.3, 1.0); glClear(GL_COLOR_BUFFER_BIT); } }; class OpenGLItem : public QQuickItem { Q_OBJECT public: OpenGLItem(); ~OpenGLItem(); public slots: void sync(); void cleanup(); protected: void timerEvent(QTimerEvent *event); private: QBasicTimer m_timer; Render *m_render; }; #endif // OPENGLWINDOW_H 先看OpenGLItem,MyRender待会再进行讲解,关键的两个槽函数 :void sync(), void cleanup() 实现如下: void OpenGLItem::sync() { if (!m_render) { m_render = new MyRender(); m_render->initializeGL(); //可以放在Render的构造函数中 m_render->resizeGL(window()->width(), window()->height()); connect(window(), &QQuickWindow::beforeRendering, this, [this]() { //window()->resetOpenGLState(); m_render->render(); }, Qt::DirectConnection); connect(window(), &QQuickWindow::afterRendering, this, [this]() { //渲染后调用,计算fps }, Qt::DirectConnection); connect(window(), &QQuickWindow::widthChanged, this, [this]() { m_render->resizeGL(window()->width(), window()->height()); }); connect(window(), &QQuickWindow::heightChanged, this, [this]() { m_render->resizeGL(window()->width(), window()->height()); }); } } void OpenGLItem::cleanup() { if (m_render) { delete m_render; m_render = nullptr; } } 可以看到,sync()函数主要是进行一些初始化工作,并连接相应的信号,其中beforeRendering是在真正渲染之前发出的,要理解Qt Quick整个渲染过程,我先上一张图片: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEyODMyMjY_size_27_color_FFFFFF_t_70][] 所以我们要把渲染工作放到beforeRendering()和afterRender()之间,我这里直接在beforeRendering()发出之后立即开始绘制:m\_render->render(),这里有一个地方必须要注意,那就是要确保connect的连接类型为:Qt::DirectConnection。 而afterRender()应该是计算fps(帧率)的地方,这里不会用到,当窗口大小发生改变时,就必须重置opengl的视口,计算纵横比等等,这些应该在Render::resizeGL(int w, int h)中进行,所以这里只需要连接信号并调用:m\_render->resizeGL()。 cleanup()做一些清理工作,这里仅仅是释放并置空m\_render。 那么,sync()和cleanup()在何时调用呢?来看OpenGLItem的构造函数: OpenGLItem::OpenGLItem() : m_render(0) { m_timer.start(12, this); connect(this, &QQuickItem::windowChanged, this, [this](QQuickWindow *window) { if (window) { connect(window, &QQuickWindow::beforeSynchronizing, this, &OpenGLItem::sync, Qt::DirectConnection); connect(window, &QQuickWindow::sceneGraphInvalidated, this, &OpenGLItem::cleanup, Qt::DirectConnection); window->setClearBeforeRendering(false); } else return; }); } 我们看到,在beforeSynchronizing()信号发出时调用sync(),此信号在场景图与QML状态同步之前发出,可以理解为:绘制准备的信号,所以sync()也确实是做绘制准备的工作,而sceneGraphInvalidated()是连接到cleanup()的,这个信号意味着所使用的图形呈现上下文已经失效,所有与该上下文相关的用户资源都应该被释放,并且,两个connect的类型还是:Qt::DirectConnection。 还有一点要注意的就是最后一行:window->setClearBeforeRendering(false); setClearBeforeRendering()设置QML场景图形渲染是否在开始渲染之前清除颜色缓冲区,禁用它可以保证呈现我们自己的OpenGL内容。 这些工作是在QQuickItem发出windowChanged()信号后进行的,windowChanged()在Item的窗口发生改变时发出。 还有一个timerEvent(),它提醒窗口进行重绘,间隔为12毫秒: void OpenGLItem::timerEvent(QTimerEvent *event) { Q_UNUSED(event); window()->update(); } 接下来是Render类,写的很简单,但是已经可以看出OpenGL的绘制步骤了: #ifndef RENDER_H #define RENDER_H #include <QOpenGLFunctions> class Render : protected QOpenGLFunctions { public: Render() { } virtual ~Render() { } public: virtual void initializeGL(); virtual void initializeShader(); virtual void resizeGL(int w, int h); virtual void render(); //与paintGL相似,但我更喜欢叫render }; #endif // RENDER_H #include "render.h" void Render::initializeGL() { initializeOpenGLFunctions(); initializeShader(); } void Render::initializeShader() { } void Render::resizeGL(int w, int h) { glViewport(0, 0, w, h); } void Render::render() { } 关于这些函数的命名我还是按照QOpenGLWidget里面的来的,当然大致的工作也可以从函数名可以看出,不过这一篇只讲怎么在Qt Quick中使用opengl,所以这些函数都只做了最基本的工作。 然后自己的render类只需要继承Render,重写这几个虚函数就行了,就像我上面的MyRender。 最后还是老样子,注册到qml中就可以愉快的使用了: #include "openglItem.h" #include <QGuiApplication> #include <QQmlApplicationEngine> int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); #ifdef Q_OS_ANDROID app.setAttribute(Qt::AA_UseOpenGLES); #else app.setAttribute(Qt::AA_UseDesktopOpenGL); #endif qmlRegisterType<OpenGLItem>("an.OpenGLItem", 1, 0, "OpenGLItem"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); } main.qml: import QtQuick 2.9 import QtQuick.Window 2.2 import an.OpenGLItem 1.0 Window { visible: true width: Qt.platform.os == "android" ? Screen.desktopAvailableWidth : 640 height: Qt.platform.os == "android" ? Screen.desktopAvailableHeight : 480 title: qsTr("MPS Opengl Qt/Quick 教程(0)!") OpenGLItem { id: openGLItem visible: true anchors.fill: parent } } 最后来看一下效果图(只是一个灰绿色的空窗口而已): ![70][] -------------------- ### **【结语】** ### 啊终于讲完了这最开始的一篇,主要还是窗口相关的东西,接下来将会把注意力转移到render中,毕竟那才是真正的核心,但就像我开头所说,很多基础的东西都不会仔细讲,但我一般会给出在哪可以学习这些东西。 整个系列教程的源码在我的Github上可以找到,地址是:[https://github.com/mengps/OpenGL-Totural-Qt-Quick][https_github.com_mengps_OpenGL-Totural-Qt-Quick],会不定时进行更新~~ [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEyODMyMjY_size_27_color_FFFFFF_t_70]: /images/20220505/8ac7537da39f464092006ca6bc3f3aaa.png [70]: /images/20220505/c7195a76936c4a38890f47778fe1ae78.png [https_github.com_mengps_OpenGL-Totural-Qt-Quick]: https://github.com/mengps/OpenGL-Totural-Qt-Quick
还没有评论,来说两句吧...