OpenGL 窗口示例

本示例说明如何创建一个基于QWindow 的最小应用程序,以便使用 OpenGL。

OpenGLWindow 示例截图

注: 这是一个低级示例,说明如何使用QWindow 和 OpenGL。在实际应用中,您应考虑使用更高级别的QOpenGLWindow 类。有关QOpenGLWindow 方便类的演示,请参阅Hello GLES3 示例

OpenGLWindow 超级类

我们的 OpenGLWindow 类充当 API,然后通过子类来进行实际渲染。它具有调用 render() 的请求函数,可以立即调用 renderNow() 或在事件循环处理完当前事件后立即调用 renderLater()。OpenGLWindow 子类可以重新实现用于基于 OpenGL 渲染的 render(),也可以重新实现用于使用QPainter 渲染的 render(QPainter *)。使用 OpenGLWindow::setAnimating(true) 可以以垂直刷新率调用 render(),前提是在底层 OpenGL 驱动程序中启用了垂直同步。

在执行 OpenGL 渲染的类中,您通常会希望像我们的 OpenGLWindow 一样继承自QOpenGLFunctions ,以便获得与平台无关的 OpenGL ES 2.0 函数访问权限。通过从QOpenGLFunctions 继承,它所包含的 OpenGL 函数将获得优先权,如果您希望您的应用程序既能使用 OpenGL 又能使用 OpenGL ES 2.0,您就不必担心解析这些函数的问题。

class OpenGLWindow : public QWindow, protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit OpenGLWindow(QWindow *parent = nullptr);
    ~OpenGLWindow();

    virtual void render(QPainter *painter);
    virtual void render();

    virtual void initialize();

    void setAnimating(bool animating);

public slots:
    void renderLater();
    void renderNow();

protected:
    bool event(QEvent *event) override;

    void exposeEvent(QExposeEvent *event) override;

private:
    bool m_animating = false;

    QOpenGLContext *m_context = nullptr;
    QOpenGLPaintDevice *m_device = nullptr;
};

窗口的表面类型必须设置为QSurface::OpenGLSurface ,以表明窗口将用于 OpenGL 渲染,而不是使用QBackingStore 渲染QPainter 栅格内容。

OpenGLWindow::OpenGLWindow(QWindow *parent)
    : QWindow(parent)
{
    setSurfaceType(QWindow::OpenGLSurface);
}

任何所需的 OpenGL 初始化都可以通过重载 initialize() 函数来完成,该函数会在首次调用 render() 之前调用一次,并使用一个有效的当前QOpenGLContext 。从下面的代码片段可以看出,默认的 render(QPainter *) 和 initialize() 实现都是空的,而默认的 render() 实现会初始化一个QOpenGLPaintDevice ,然后调用 render(QPainter *)。

void OpenGLWindow::render(QPainter *painter)
{
    Q_UNUSED(painter);
}

void OpenGLWindow::initialize()
{
}

void OpenGLWindow::render()
{
    if (!m_device)
        m_device = new QOpenGLPaintDevice;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    m_device->setSize(size() * devicePixelRatio());
    m_device->setDevicePixelRatio(devicePixelRatio());

    QPainter painter(m_device);
    render(&painter);
}

renderLater() 函数只是调用QWindow::requestUpdate() 来安排系统准备好重新绘制时的更新。

当我们收到 expose 事件时,也会调用 renderNow()。exposeEvent() 是窗口在屏幕上的曝光(即可见性)发生变化的通知。收到 expose 事件后,您可以查询QWindow::isExposed() 以了解窗口当前是否曝光。在窗口接收到第一次曝光事件之前,请勿对其进行渲染或调用QOpenGLContext::swapBuffers() ,因为在此之前,窗口的最终大小可能是未知的,此外,渲染的内容甚至可能不会出现在屏幕上。

void OpenGLWindow::renderLater()
{
    requestUpdate();
}

bool OpenGLWindow::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::UpdateRequest:
        renderNow();
        return true;
    default:
        return QWindow::event(event);
    }
}

void OpenGLWindow::exposeEvent(QExposeEvent *event)
{
    Q_UNUSED(event);

    if (isExposed())
        renderNow();
}

在 renderNow() 中,我们会返回当前是否尚未暴露,在这种情况下,渲染会延迟到实际收到暴露事件时进行。如果我们还没有这样做,我们将使用与 OpenGLWindow 相同的QSurfaceFormat 创建QOpenGLContext ,并为子类调用 initialize() 和 initializeOpenGLFunctions(),以便QOpenGLFunctions 超级类与正确的QOpenGLContext 关联。在任何情况下,我们都会调用QOpenGLContext::makeCurrent() 使上下文处于当前状态,调用 render() 进行实际渲染,最后调用QOpenGLContext::swapBuffers() 并将 OpenGLWindow 作为参数,使渲染后的内容可见。

一旦调用QOpenGLContext::makeCurrent() 启动了使用 OpenGL 上下文的帧渲染,并将渲染表面作为参数,就可以发出 OpenGL 命令。QOpenGLContext::functions这些命令可以通过直接包含 <qopengl.h>(其中也包含系统的 OpenGL 头文件)或使用QOpenGLFunctions (为方便起见,可以继承该头文件)来发出。QOpenGLFunctions 可以访问所有 OpenGL ES 2.0 级别的 OpenGL 调用,这些调用在 OpenGL ES 2.0 和桌面 OpenGL 中尚未成为标准。有关 OpenGL 和 OpenGL ES API 的更多信息,请参阅官方OpenGL 注册表Khronos OpenGL ES API 注册表

如果使用 OpenGLWindow::setAnimating(true) 启用了动画,我们就会调用 renderLater() 来安排另一次更新请求。

void OpenGLWindow::renderNow()
{
    if (!isExposed())
        return;

    bool needsInitialize = false;

    if (!m_context) {
        m_context = new QOpenGLContext(this);
        m_context->setFormat(requestedFormat());
        m_context->create();

        needsInitialize = true;
    }

    m_context->makeCurrent(this);

    if (needsInitialize) {
        initializeOpenGLFunctions();
        initialize();
    }

    render();

    m_context->swapBuffers(this);

    if (m_animating)
        renderLater();
}

如以下代码片段所示,启用动画也会调度更新请求。

void OpenGLWindow::setAnimating(bool animating)
{
    m_animating = animating;

    if (animating)
        renderLater();
}

OpenGL 渲染子类示例

在这里,我们通过子类 OpenGLWindow 来展示如何使用 OpenGL 渲染旋转三角形。通过间接子类QOpenGLFunctions ,我们可以访问所有 OpenGL ES 2.0 级别的功能。

class TriangleWindow : public OpenGLWindow
{
public:
    using OpenGLWindow::OpenGLWindow;

    void initialize() override;
    void render() override;

private:
    GLint m_matrixUniform = 0;
    QOpenGLBuffer m_vbo;
    QOpenGLShaderProgram *m_program = nullptr;
    int m_frame = 0;
};

在主函数中,我们初始化QGuiApplication 并实例化 TriangleOpenGLWindow。我们给它一个QSurfaceFormat ,指定我们需要四个多采样抗锯齿样本以及一个默认几何图形。由于我们想要动画效果,因此我们调用了上述参数为 true 的 setAnimating() 函数。

int main(int argc, char **argv)
{
    QGuiApplication app(argc, argv);

    QSurfaceFormat format;
    format.setSamples(16);

    TriangleWindow window;
    window.setFormat(format);
    window.resize(640, 480);
    window.show();

    window.setAnimating(true);

    return app.exec();
}

以下代码片段显示了本示例中使用的 OpenGL 着色器程序。顶点和片段着色器相对简单,只需进行顶点变换和插值顶点着色。

static const char *vertexShaderSource = "attribute highp vec4 posAttr;\n"
                                        "attribute lowp vec4 colAttr;\n"
                                        "varying lowp vec4 col;\n"
                                        "uniform highp mat4 matrix;\n"
                                        "void main() {\n"
                                        "   col = colAttr;\n"
                                        "   gl_Position = matrix * posAttr;\n"
                                        "}\n";

static const char *fragmentShaderSource = "varying lowp vec4 col;\n"
                                          "void main() {\n"
                                          "   gl_FragColor = col;\n"
                                          "}\n";

下面是加载着色器和初始化着色器程序的代码 通过使用QOpenGLShaderProgram 而不是原始 OpenGL,我们可以方便地去除桌面 OpenGL 上的 highp、mediump 和 lowp 限定符,因为它们不是标准的一部分。我们将属性和统一位置存储在成员变量中,以避免每帧都进行位置查找。

void TriangleWindow::initialize()
{
    static const GLfloat vertices_colors[] = { +0.0f, +0.707f, 1.0f, 0.0f, 0.0f,
                                               -0.5f, -0.500f, 0.0f, 1.0f, 0.0f,
                                               +0.5f, -0.500f, 0.0f, 0.0f, 1.0f };

    m_vbo.create();
    m_vbo.bind();
    m_vbo.allocate(vertices_colors, sizeof(vertices_colors));

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), nullptr);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat),
                          reinterpret_cast<void *>(2 * sizeof(GLfloat)));

    m_program = new QOpenGLShaderProgram(this);
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    m_program->bindAttributeLocation("posAttr", 0);
    m_program->bindAttributeLocation("colAttr", 1);
    m_program->link();
    m_program->bind();

    m_matrixUniform = m_program->uniformLocation("matrix");
    Q_ASSERT(m_matrixUniform != -1);
}

最后,这里是我们的 render() 函数,我们使用 OpenGL 设置视口、清除背景并渲染旋转三角形。

void TriangleWindow::render()
{
    const qreal retinaScale = devicePixelRatio();
    glViewport(0, 0, width() * retinaScale, height() * retinaScale);

    glClear(GL_COLOR_BUFFER_BIT);

    m_program->bind();

    QMatrix4x4 matrix;
    matrix.perspective(60.0f, 4.0f / 3.0f, 0.1f, 100.0f);
    matrix.translate(0, 0, -2);
    matrix.rotate(100.0f * m_frame / screen()->refreshRate(), 0, 1, 0);

    m_program->setUniformValue(m_matrixUniform, matrix);

    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glDrawArrays(GL_TRIANGLES, 0, 3);

    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

    m_program->release();

    ++m_frame;
}

示例项目 @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.