QtOpenGL使用方法
这里介绍如何在Qt里面使用OpenGL功能。代码在macOS 13 M1 平台上测试通过。
基本架构
在Qt 6中使用OpenGL,需要知道Qt接管了OpenGL的什么功能。在普通的OpenGL程序中,有GLFW和GLAD两部分对OpenGL进行了功能加强,前者提供了窗口管理版本管理等功能,后者负责从系统中获得OpenGL函数地址。在Qt中,前者的窗口管理功能由Qt本身替代,而版本管理和其他属性设置,由QSurfaceFormat
管理。这个名字里面没有出现OpenGL,很是惬意。而GLAD则不用理睬,Qt也处理好了这一方面。
在初次学习中,犯的最大的错误是忽视了SurfaceFormat的介绍。因此首先讲解SurfaceFormat是什么。
QSurfaceFormat Class | Qt GUI 6.4.2,是Qt对于各种Surface提供的配置文件。其中的函数QSurfaceFormat::setDefaultFormat
则是为所有Surface设置配置文件。Surface不是只有OpenGL的,而是可以有多种type,比如:
Constant | Value | Description |
---|---|---|
QSurface::RasterSurface |
0 |
The surface is is composed of pixels and can be rendered to using a software rasterizer like Qt’s raster paint engine. |
QSurface::OpenGLSurface |
1 |
The surface is an OpenGL compatible surface and can be used in conjunction with QOpenGLContext. |
QSurface::RasterGLSurface |
2 |
The surface can be rendered to using a software rasterizer, and also supports OpenGL. This surface type is intended for internal Qt use, and requires the use of private API. |
QSurface::OpenVGSurface |
3 |
The surface is an OpenVG compatible surface and can be used in conjunction with OpenVG contexts. |
QSurface::VulkanSurface |
4 |
The surface is a Vulkan compatible surface and can be used in conjunction with the Vulkan graphics API. |
QSurface::MetalSurface |
5 |
The surface is a Metal compatible surface and can be used in conjunction with Apple’s Metal graphics API. This surface type is only supported on macOS and iOS. |
QSurface::Direct3DSurface |
6 |
The surface is a Direct 3D 11 and 12 compatible surface and can be used in conjunction with the DXGI and Direct3D APIs. This surface type is only supported on Windows. |
而QSurfaceFormat与其说是QSurface的配置,不如说是为了OpenGL专用的设置。因为其中大部分设置都是专属于OpenGL的。
初次学习时犯下的一个重要错误是,没有仔细阅读文档中的注意事项。比如:
- QOpenGLWidget allows using different OpenGL versions and profiles when the platform supports it. Just set the requested format via setFormat(). Keep in mind however that having multiple QOpenGLWidget instances in the same window requires that they all use the same format, or at least formats that do not make the contexts non-sharable. To overcome this issue, prefer using QSurfaceFormat::setDefaultFormat() instead of setFormat().
- Calling QSurfaceFormat::setDefaultFormat() before constructing the QApplication instance is mandatory on some platforms (for example, macOS) when an OpenGL core profile context is requested. This is to ensure that resource sharing between contexts stays functional as all internal contexts are created using the correct version and profile.
这两个提醒,正好对应了我在常见错误部分所写的两个我犯下的错误。后面会详细叙述。
动手绘制三角形
Promote UI
第一步是完成UI设计。在Qt Creator里面设置UI文件,按照喜好安排即可。注意其中需要添加一个QOpenGLWidget的组件,然后在组件右键,选择Promote to
将其提升为自己的类。
创建完成之后,就需要在给出的头文件里面实现自己的myGLWidget
。
GLWidget
首先需要一大堆头文件。1
2
3
4
5
6
7#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLContext>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
然后和往常一样,我们需要实现最简单的Shader。这里直接使用字符串。1
2
3
4
5
6
7
8
9
10
11
12static const char *vertexShaderSource = "#version 400 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
static const char *fragmentShaderSource = "#version 400 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
然后需要实现类的基本结构,最重要的部分有三个,initializeGL, resizeGL, paintGL
。其中第一位是OpenGL的资源准备,第二位是在窗口发生缩放时,OpenGL的应对方式,第三位是渲染的过程。
这里使用OpenGL的核心模式,我们需要实现VAO,VBO,shader和program。QtOpenGL提供了相应的管理方法。具体使用方式基本一致,需要注意的是VAO和VBO提供了bind()
函数,可以自动绑定到当前上下文中。当然如果你愿意,也可以使用VBO中的bufferId
函数得到OpenGL的ID。不过VAO不提供这一手段。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62class myGLWidget : public QOpenGLWidget
{
Q_OBJECT
public:
myGLWidget(QWidget *parent = nullptr): QOpenGLWidget(parent) {}
QOpenGLShader *vert_shader, *frag_shader;
QOpenGLShaderProgram *program;
QOpenGLVertexArrayObject m_vao;
QOpenGLBuffer m_vbo;
protected:
void initializeGL() override
{
// Set up the rendering context, load shaders and other resources, etc.:
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
vert_shader = new QOpenGLShader(QOpenGLShader::Vertex, this);
vert_shader->compileSourceCode(vertexShaderSource);
frag_shader = new QOpenGLShader(QOpenGLShader::Fragment, this);
frag_shader->compileSourceCode(fragmentShaderSource);
program = new QOpenGLShaderProgram(this);
program->addShader(vert_shader);
program->addShader(frag_shader);
program->link();
m_vao.create();
if (m_vao.isCreated())
m_vao.bind();
m_vbo.create();
m_vbo.bind();
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
f->glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
f->glEnableVertexAttribArray(0);
// f->glBindBuffer(GL_ARRAY_BUFFER, 0);
// f->glBindVertexArray(0);
}
void resizeGL(int w, int h) override
{
// Update projection matrix and other size related settings:
// m_projection.setToIdentity();
// m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f);
// ...
}
void paintGL() override
{
// Draw the scene:
QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
f->glClear(GL_COLOR_BUFFER_BIT);
program->bind();
m_vao.bind();
f->glDrawArrays(GL_TRIANGLES, 0, 3);
program->release();
}
};
从代码中可以看到,基本使用方法和普通的OpenGL相似,只是将glBindBuffer
之类换成了更直接的bind()
。
需要注意的是,要想使用OpenGL函数,必须使用QOpenGLFunctions
提供的接口。照搬上面的语句即可。
QOpenGL没有对OpenGL进行全盘迁移。因为全盘迁移之后,它其实就变成了Qt 3D 6.4.2。
调用
因为我们使用的是promote方式进行实现的,所以不用做什么动作就可以启用了。但是默认的version其实是OpenGL2.0,部分平台不支持。所以我们需要在窗口创建之前,完成这一设置。
1 |
|
效果
常见错误
SurfaceFormat
需要在main函数里面设置SurfaceFormat
,否则会报错openglcontext: Could not create NSOpenGLContext with shared context
。这是因为多个Surface必须使用相同的Format,所以如果只设置一个的话,就会发生配置不同的冲突。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#include "mainwindow.h"
#include <QApplication>
#include <QSurfaceFormat>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QSurfaceFormat format;
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
format.setVersion(3, 3);
format.setProfile(QSurfaceFormat::CoreProfile);
QSurfaceFormat::setDefaultFormat(format);
MainWindow w;
w.show();
return a.exec();
}
Version not supported
这种情况依然发生在SurfaceFormat的问题上。如果在设置DefaultFormat
之前创建了QWindow
,那么默认surface不会起作用。因此,无法看到预期的效果。
所以,需要将顺序进行调整。1
2
3QSurfaceFormat::setDefaultFormat(format);
MainWindow w;
w.show();
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!