OpenGL窗口示例

本示例展示了如何创建一个基于QWindow的最小应用程序,用于使用OpenGL。

Screenshot of the OpenGLWindow example

注意:这是一个如何使用QWindow的底层示例,实际操作中应考虑使用更高层的QOpenGLWindow类。有关QOpenGLWindow便捷类的演示,请参阅Hello GLES3示例

OpenGLWindow超类

我们的OpenGLWindow类作为API,然后被子类化以执行实际的渲染。它有请求调用render()的方法,可以立即调用renderNow()或事件循环完成后调用renderLater()。OpenGLWindow子类可以选择重新实现基于OpenGL的render()方法,或者为使用QPainter的渲染调用render(QPainter *)。使用OpenGLWindow::setAnimating(true)来确保render()在垂直刷新率下调用,假设底层OpenGL驱动程序启用了垂直同步。

在执行OpenGL渲染的类中,通常希望从QOpenGLFunctions继承,就像我们的OpenGLWindow一样,以获得对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渲染,而不是使用QPainterQBackingStore渲染光栅内容。

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

可以通过覆盖initialize()函数来执行任何必要的OpenGL初始化,该函数在调用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()来安排系统准备重绘时的更新。

当接收到暴露事件时,我们还会调用renderNow()。exposeEvent() 是通知窗口其暴露(即可见性)在屏幕上已更改的事件。当收到暴露事件时,你可以查询 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() 执行实际渲染,最后通过调用以OpenGLWindow为参数的 QOpenGLContext::swapBuffers() 安排使渲染内容可见。

通过调用 QOpenGLContext::makeCurrent() 初始化OpenGL上下文,并指定渲染的表面后,可以发出OpenGL命令。命令可以通过包含系统OpenGL头文件的 <qopengl.h> 直接发出,或者通过使用 QOpenGLFunctions发出该类,可以为了方便从该类中继承或者通过使用QOpenGLContext::functions() 访问。 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_posAttr = 0;
    GLint m_colAttr = 0;
    GLint m_matrixUniform = 0;

    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中消除了高p、中p和低p的限定符,因为在标准中它们不是组成部分。我们将属性和统一变量存储到成员变量中,以避免在每一帧中进行位置查找。

void TriangleWindow::initialize()
{
    m_program = new QOpenGLShaderProgram(this);
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    m_program->link();
    m_posAttr = m_program->attributeLocation("posAttr");
    Q_ASSERT(m_posAttr != -1);
    m_colAttr = m_program->attributeLocation("colAttr");
    Q_ASSERT(m_colAttr != -1);
    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);

    static const GLfloat vertices[] = {
         0.0f,  0.707f,
        -0.5f, -0.5f,
         0.5f, -0.5f
    };

    static const GLfloat colors[] = {
        1.0f, 0.0f, 0.0f,
        0.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 1.0f
    };

    glVertexAttribPointer(m_posAttr, 2, GL_FLOAT, GL_FALSE, 0, vertices);
    glVertexAttribPointer(m_colAttr, 3, GL_FLOAT, GL_FALSE, 0, colors);

    glEnableVertexAttribArray(m_posAttr);
    glEnableVertexAttribArray(m_colAttr);

    glDrawArrays(GL_TRIANGLES, 0, 3);

    glDisableVertexAttribArray(m_colAttr);
    glDisableVertexAttribArray(m_posAttr);

    m_program->release();

    ++m_frame;
}

示例项目 @ code.qt.io

© 2024 Qt公司有限公司。本文件中包含的文档贡献均为各自所有者的版权。所提供的文档根据自由软件基金会发布的《GNU自由文档许可证版本1.3》的条款进行许可。Qt及其相应的标志在芬兰和/或全球其他国家的The Qt Company有限公司注册为商标。所有其他商标均为各自拥有者的财产。