【寫在前面】
首先,想要說明的是,本系列學習教程是根據我自己學習的經歷而寫,並非完全科普性的,零基礎的教程,而且其水平也很受我本身的水平影響,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整個渲染過程,我先上一張圖片:
所以我們要把渲染工作放到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
}
}
最後來看一下效果圖(只是一個灰綠色的空窗口而已):
【結語】
啊終於講完了這最開始的一篇,主要還是窗口相關的東西,接下來將會把注意力轉移到render中,畢竟那纔是真正的核心,但就像我開頭所說,很多基礎的東西都不會仔細講,但我一般會給出在哪可以學習這些東西。
整個系列教程的源碼在我的Github上可以找到,地址是:https://github.com/mengps/OpenGL-Totural-Qt-Quick,會不定時進行更新~~