OpenGL(三)之Qt窗口(QOpenGLWidget详解)
在上一篇中窗口类渲染OpenGL部件是基于QWindow,但在实际应用开发中比较常用的窗口是基于QWidget(当然还有Qt Quick这里并不展开讲)。至于
QOpenGLWidget 类详解
paintGL() : 渲染OpenGL的场景。当窗口需要被更新时,调用这个函数。resizeGL() : 设置OpenGL的视口,投影矩阵等;当窗口改变大小时被调用,以及在第一次显示时会被调用(这是因为新建一个窗口时
会自动发送重置窗口大小事件[resize event ])initializeGL() : 设置OpenGL的资源和状态。这个函数需要在paintGL() ,resizeGL() 之前调用。
如果想在其它地方触发
当调用
所有的渲染都发生在OpenGL的一个框架缓冲区对象中(framebuffer object)。
在平台支持时,
【注意】:当请求OpenGL核心配置(即上下文)时,在构造QApplication实例之前调用QSurfaceFormat::setDefaultFormat()是必须的,在一些平台(例如macOS)上。这是为了确保在使用正确的版本和配置文件创建所有内部上下文时,上下文之间的资源共享保持正常工作。
OpenGL Function Calls, Headers and QOpenGLFunctions
在进行OpenGL函数调用时,强烈建议避免直接调用函数。相反,更喜欢使用QOpenGLFunctions(在制作可移植应用程序时)或版本化变体(例如,QOpenGLFunctions_3_2_Core等,当针对现代的,仅限桌面的OpenGL时)。这样,应用程序将在所有Qt构建配置中正常工作,包括执行动态OpenGL实现加载的应用程序,这意味着应用程序不直接链接到GL实现,因此直接函数调用是不可行的。
在paintGL()中,当前场景(context)始终可以通过调用QOpenGLContext :: currentContext()来访问。从这个context中,可以通过调用QOpenGLContext :: functions()来检索已经初始化的,准备好使用的QOpenGLFunctions实例。为每个GL调用添加前缀的替代方法是从QOpenGLFunctions继承并在initializeGL()中调用QOpenGLFunctions :: initializeOpenGLFunctions()。
至于OpenGL头文件,请注意,在大多数情况下,不需要直接包含任何头文件,如GL.h.与OpenGL相关的Qt头文件将包含qopengl.h,后者将包含适用于系统的标头。这可能是OpenGL ES 3.x或2.0标头,可用的最高版本,或系统提供的gl.h.此外,作为OpenGL和OpenGL ES的Qt的一部分,提供了扩展头的副本(在某些系统上称为glext.h)。这些将在可行的情况下自动包含在平台上。这意味着来自ARB,EXT,OES扩展的常量和函数指针typedef自动可用。
实例一
直接继承
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 | class MyGLWidget : public QOpenGLWidget { public: MyGLWidget(QWidget *parent) : QOpenGLWidget(parent) { } protected: void initializeGL() { //设置渲染的上下文,加载着色器和其他资源,等等... QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f); ... } void resizeGL(int w, int h) { //更新投影矩阵和,设置视口等其它相关设置 m_projection.setToIdentity(); m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f); ... } void paintGL() { // 绘制场景 QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); f->glClear(GL_COLOR_BUFFER_BIT); ... } }; |
实例二
直接继承
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 | class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions { public: MyGLWidget(QWidget *parent) : QOpenGLWidget(parent) { } protected: void initializeGL() { initializeOpenGLFunctions(); glClearColor(...); ... } void resizeGL(int w, int h) { //更新投影矩阵和,设置视口等其它相关设置 m_projection.setToIdentity(); m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f); ... } void paintGL() { // 绘制场景 glClear(GL_COLOR_BUFFER_BIT); ... } }; |
实例三:上下文配置格式
- 配置格式:要获取与给定OpenGL版本或配置文件兼容的上下文,或请求深度和模具缓冲区,调用setFormat():
1 2 3 4 5 6 7 8 | QOpenGLWidget *widget = new QOpenGLWidget(parent); QSurfaceFormat format; format.setDepthBufferSize(24); format.setStencilBufferSize(8); format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); //必须在显示之前调用 widget->setFormat(format); |
- 对于OpenGL 3.0+上下文,当可移植性不重要时,我们可以使用下面这种方式来访问具体版本的的函数功能。
1 2 3 4 5 6 7 8 9 | ... void paintGL() { QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()- >versionFunctions<QOpenGLFunctions_3_2_Core>(); ... f->glDrawArraysInstanced(...); ... } ... |
- 如上所述,全局地设置请求的格式,以便在应用程序的生命周期中应用于所有窗口和上下文,这样更简单、更健壮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | int main(int argc, char **argv) { QApplication app(argc, argv); QSurfaceFormat format; format.setDepthBufferSize(24); format.setStencilBufferSize(8); format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); QSurfaceFormat::setDefaultFormat(format); MyWidget widget; widget.show(); return app.exec(); } |
与QGLWidget 的联系
传统的QtOpenGL模块(以QGL为前缀的类)提供了一个名为QGLWidget的widget。QOpenGLWidget旨在成为它的替代品。因此,特别是在新的应用程序中,一般建议使用QOpenGLWidget。
虽然API非常相似,但两者之间存在重要差异:QOpenGLWidget始终使用帧缓冲对象在屏幕外渲染。另一方面,QGLWidget使用原生窗口和曲面。后者在复杂的用户界面中使用它时会引起问题,因为根据平台,这种本机子窗口小部件可能具有各种限制,例如关于堆叠命令。QOpenGLWidget通过不创建单独的本机窗口来避免这种情况。
由于帧缓冲对象的支持,QOpenGLWidget的行为与QOpenGLWindow非常相似,更新行为设置为PartialUpdateBlit或PartialUpdateBlend。这意味着在paintGL()调用之间保留内容,以便可以进行增量渲染。使用QGLWidget(当然QOpenGLWindow具有默认的更新行为)通常不是这种情况,因为交换缓冲区会使后台缓冲区中的内容不确定。
注意:大多数应用程序不需要增量渲染,因为它们将在每次调用绘制时呈现视图中的所有内容。在这种情况下,在paintGL()中尽早调用glClear()非常重要。这有助于使用基于图块的体系结构的移动GPU识别出图块缓冲区不需要使用帧缓冲区的先前内容重新加载。省略明确的呼叫可能导致此类系统的性能显着下降。
注意:避免在QOpenGLWidget上调用winId()。此功能触发创建本机窗口,从而降低性能并可能出现毛刺。
与QGLWidget的差异
除了framebuffer对象支持的主要概念差异之外,QOpenGLWidget和旧的QGLWidget之间存在许多较小的内部差异:
调用paintGL()时的OpenGL状态。 QOpenGLWidget通过glViewport()设置视口。 它不执行任何清算。
当开始绘画时通过QPainter清除。 与常规widget不同,QGLWidget默认为autoFillBackground的值为true。 然后,每次使用QPainter :: begin()时,它都会清除调色板的背景颜色。 QOpenGLWidget不遵循:autoFillBackground默认为false,就像任何其他widget一样。 唯一的例外是当用作QGraphicsView等其他小部件的视口时。 在这种情况下,autoFillBackground将自动设置为true,以确保与基于QGLWidget的视口兼容。
多重采样
要启用多采样,请在传递给setFormat()的QSurfaceFormat上设置请求样本的数量。在不支持它的系统上,请求可能会被忽略。
Context Sharing
当多个QOpenGLWidgets作为子组件添加到同一个顶级小部件时,它们的上下文将相互共享。这不适用于属于不同窗口的QOpenGLWidget实例。
这意味着同一个窗口中的所有QOpenGLWidgets都可以访问彼此共享的资源,比如纹理,而不需要额外的“全局共享”上下文,就像QGLWidget一样。
要在属于不同窗口的QOpenGLWidget实例之间建立共享,请在实例化QApplication之前设置Qt::AA_ShareOpenGLContexts应用程序属性。这将触发所有QOpenGLWidget实例之间的共享,而无需进行任何步骤。