1. 开发背景
大多数人学习OpenGL估计都是从LearnOpenGL这个网站开始的吧,这个教程使用的是VS Studio + glfw + glad的开发方式。
好吧,我之前也是这么做的,但是OpenGL本身是C风格的API, 如果用面向对象的思想其写,稍微有一些麻烦,而且在调试参数的时候不太方便。
后来我在Review LearnOpenGL的时候就想换一种开发方式学习:
方案一:
ImGui + OpenGL
- 优点:ImGui集成了glfw环境,可以实现一些简单的控件来修改参数,方便查看实时效果,就如下图。
- 缺点:我懒得去学习一门新的ui库=。=
参考教程:ImGui+OpenGL教程
方案二: Qt+OpenGL
Qt-OpenGL的几个优势:
- Qt内嵌了opengl的相关环境,不需要我们自己来搭建,这对小白来说是很友好的。
- Qt和opengl都具有优良的跨平台特性,使用Qt做opengl开发可谓是强强联合。
- Qt可以轻松的控制窗口的各种处理事件以及窗口属性。
- Qt提供了opengl函数的C++封装,使得opengl原来的C风格API可以通过C++的面向对象技术来实现。
- Qt提供了十分完善的官方文档,有助于我们掌握QtOpenGL的各种细节。
缺点:
- Qt对OpenGL的封装导致代码和Qt框架耦合,很难抽离出来作为独立SDK,这在一些团队中可能不会考虑
由于个人对Qt熟悉一些,所以后来都是用Qt作为OpenGL学习的开发测试环境。
2. Qt+OpenGL开发
Qt中使用OpenGL一般有两种方式:
- 封装成Widget嵌套在其他页面中
- 作为独立Window使用
在Qt5.4之前,可以自定义一个类,继承自QGLWidget来实现,后来推出了### QOpenGLWidget这个类简化了流程。
QOpenGLWidegt
创建opengl窗口只需新建类继承于QOpenGLWidegt,再实现QOpenGL提供的三个虚函数,就可以完成opengl窗口的创建。
- initializeGL()—建立OpenGL的资源和状态。在第一次调用resizeGL()或paintGL()之前调用一次
- resizeGL()—设置OpenGL视口,投影等。每当调整Widget的大小时(第一次显示窗口Widget时会调用它,因为所有新创建Widget都会自动获得调整大小的事件)。
- paintGL()—渲染OpenGL场景,需要更新Widget时就会调用。
QOpenGLExtraFunctions类继承于QOpenGLFunctions,相较于QOpenGLFunctions,额外提供了对OpenGL ES 3.0、3.1和3.2 API的跨平台访问,如果我们需要在类中使用opengl函数,只需要使类继承于QOpenGLExtraFunctions。
以如下代码为例:
#ifndef OPENGL_TOOLS_QGL_WIDGET_HPP #define OPENGL_TOOLS_QGL_WIDGET_HPP #include <QOpenGLWidget> #include <QOpenGLFunctions> #include <QOpenGLBuffer> class QGL_Widget : public QOpenGLWidget, QOpenGLFunctions{ Q_OBJECT
public: using QOpenGLWidget::QOpenGLWidget;
explicit QGL_Widget(QWidget* parent = nullptr) : QOpenGLWidget(parent) { }
void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.2f, 0.1f, 0.8f, 0); };
void resizeGL(int w, int h) override { };
void paintGL() override { };
}; #endif
|
调用如上窗口:
QGL_Widget *gl_widget = new QGL_Widget(this); gl_widget->setGeometry(QRect(0,00,400,300)); gl_widget->show();
|
2.2 作为Window独立显示
Qt6中提供了QOpenGLWindow作为一个gl独立窗口,注意在Qt5和Qt6中稍有不同,建议使用最新的Qt6开发。
我们创建一个类集成自QOpenGLWindow和QOpenGLFunctions,来实现一个绘制三角形的窗口
SimpleTriangleWindow.h
#ifndef OPENGL_TOOLS_SIMPLETRIANGLEWINDOW_H #define OPENGL_TOOLS_SIMPLETRIANGLEWINDOW_H #include <QOpenGLWindow> #include <QOpenGLFunctions> #include <QOpenGLVertexArrayObject> #include <QOpenGLBuffer> #include <QOpenGLShaderProgram> class SimpleTriangleWindow : public QOpenGLWindow, protected QOpenGLFunctions { Q_OBJECT public: SimpleTriangleWindow() { } ~SimpleTriangleWindow() { } protected: void initializeGL() { } void initShader() {
} void resizeGL(int w, int h) {
}; void paintGL() { }; }; #endif
|
创建VAO、VBO、着色器程序:
private: QOpenGLShaderProgram program; QOpenGLVertexArrayObject m_vao; QOpenGLBuffer m_vbo;
|
在初始化方法:
void initializeGL() { initializeOpenGLFunctions(); glEnable(GL_DEPTH_TEST); glClearColor(0.0,0.0,0.0,0.0);
m_vao.create(); m_vbo.create();
initShader();
float _vertex[] = { 0.0, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0, }; m_vao.bind(); m_vbo.bind(); m_vbo.allocate(_vertex,sizeof(_vertex));
program.bind(); program.setAttributeBuffer("vPos",GL_FLOAT,0,3,0); program.enableAttributeArray("vPos");
program.release(); m_vao.release(); }
|
initShader()是着色器加载程序,这里没有对着色器代码做修改
void initShader() { if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/resource/shader/vshader.glsl")) close();
if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/resource/shader/fshader.glsl")) close();
if (!program.link()) close();
if (!program.bind()) close(); }
|
着色器代码:
#version 330 core void main() { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); }
|
#version 330 core in vec3 vPos; void main() { gl_Position = vec4(vPos, 1.0); }
|
到此初始化工作已经完成,在paintGL中绘制:
void paintGL() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); m_vao.bind(); program.bind(); glDrawArrays(GL_TRIANGLES,0,3); program.release(); m_vao.release(); };
|
最终效果:
我们从上述代码可以看到在Qt的环境中开发OpenGL有很多和传统API不一样:
- Qt提供了
QOpenGLVertexArrayObject
类来创建管理VAO对象
QOpenGLBuffer
用来创建管理VBO/IBO/EBO对象
QOpenGLShaderProgram
类来加载、管理着色器程序
对着色器参数修改也非常方便:
m_program->bind(); m_program->setAttributeBuffer("vPos", GL_FLOAT, 0, 3, 0); m_program->enableAttributeArray("vPos");
|
整体代码非常简洁,流程比原生API要清晰很多,但是问题也在这里,对于不熟悉Qt GL Api的人来说,刚开始很难熟悉,即时熟悉了之后的开发也会局限在Qt框架中, 非常不利于扩展。
关于具体使用Qt-OpenGL教程,可以参考如下系列:
后续我不太想在Qt中进行OpenGL开发,局限性太大,据我所知,很多公司在实际开发中,即时选择了Qt框架,也不会选择Qt的OpenGL API开发,而是自己封装一套接口或者使用的其他反方库。优点就是可扩展性好,和Qt框架解耦,在移植的时候代价更小。