警告

本节包含自动从C++翻译到Python的代码片段,可能包含错误。

OpenGL窗口示例#

此示例展示了如何创建一个基于QWindow的、用于OpenGL的简洁应用程序。

../_images/openglwindow-example.png

注意

这是一个如何使用QWindow结合OpenGL的低级别示例。在实际中,你应该考虑使用较高级别的QOpenGLWindow类。请参见Hello GLES3 示例,以演示QOpenGLWindow便利类的用法。

OpenGLWindow超类#

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

在执行OpenGL渲染的类中,你通常想要继承自QOpenGLFunctions,就像我们的OpenGLWindow所做的那样,以便得到对OpenGL ES 2.0函数的平台无关访问。通过继承QOpenGLFunctions,包含的OpenGL函数将获得优先级,你无需担心这些函数的解析问题,如果你的应用程序需要同时与OpenGL和OpenGL ES 2.0一起工作。

class OpenGLWindow(QWindow, QOpenGLFunctions):

    Q_OBJECT
# public
    OpenGLWindow = explicit(QWindow parent = None)
    ~OpenGLWindow()
    virtual void render(QPainter painter)
    virtual void render()
    virtual void initialize()
    def setAnimating(animating):
# public slots
    def renderLater():
    def renderNow():
# protected
    bool event(QEvent event) override
    def exposeEvent(event):
# private
    m_animating = False
    m_context = None
    m_device = None

窗口的表面类型必须设置为QSurface::OpenGLSurface,以指示窗口将用于OpenGL渲染,而不是使用QBackingStore通过QPainter进行光栅内容的渲染。

def __init__(self, parent):
    super().__init__(parent)

    setSurfaceType(QWindow.OpenGLSurface)

任何需要的OpenGL初始化都可以通过覆盖initialize()函数完成,这个函数在第一次调用render()之前被调用一次,并且传入有效的当前QOpenGLContext。如下代码片段所示,默认的render(QPainter *)和initialize()实现都是空的,而默认的render()实现初始化了一个QOpenGLPaintDevice并随后调用render(QPainter *)。

def render(self, painter):

    Q_UNUSED(painter)

def initialize(self):


def render(self):

    if not m_device:
        m_device = QOpenGLPaintDevice()
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
    m_device.setSize(size() * devicePixelRatio())
    m_device.setDevicePixelRatio(devicePixelRatio())
    painter = QPainter(m_device)
    render(painter)

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

我们还在获取到暴露事件时调用renderNow()。暴露事件是对窗口的通知,表明窗口在屏幕上的可见性已经更改。当接收到暴露事件时,你可以查询QWindow::isExposed()以确定窗口当前是否被暴露。在窗口接收到其第一个暴露事件之前,不要向其渲染或者调用QOpenGLContext::swapBuffers(),因为在那时,其最终大小可能未知,并且渲染的内容甚至可能不会出现在屏幕上。

def renderLater(self):

    requestUpdate()

def event(self, QEvent event):

    switch (event.type()) {
    elif shape == QEvent.UpdateRequest:
        renderNow()
        return True
    else:
        return QWindow.event(event)


def exposeEvent(self, event):

    Q_UNUSED(event)
    if isExposed():
        renderNow()

在 renderNow() 函数中,如果我们目前没有被暴露,则返回,在这种情况下,渲染将延迟直到我们真正收到一个暴露事件。如果我们还没有这样做,我们将使用与 OpenGLWindow 上设置的相同的 QSurfaceFormat 创建 QOpenGLContext,并为子类调用 initialize(),以使 QOpenGLFunctions 生存类与正确的 QOpenGLContext 关联并调用 initializeOpenGLFunctions()。在任何情况下,我们通过调用 QOpenGLContext::makeCurrent() 使上下文当前化,调用 render() 进行实际的渲染,最后通过调用带有 OpenGLWindow 参数的 QOpenGLContext::swapBuffers() 来安排使渲染内容可见。

一旦通过调用 QOpenGLContext::makeCurrent() 初始化了使用 OpenGL 上下文的帧渲染,并传递了渲染表面作为参数,就可以发出 OpenGL 命令。命令可以直接通过包含 <qopengl.h> 发出,这也包含了系统的 OpenGL 标头,或者通过使用 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() 来安排另一个更新请求。

def renderNow(self):

    if not isExposed():
        return
    needsInitialize = False
    if not m_context:
        m_context = QOpenGLContext(self)
        m_context.setFormat(requestedFormat())
        m_context.create()
        needsInitialize = True

    m_context.makeCurrent(self)
    if needsInitialize:
        initializeOpenGLFunctions()
        initialize()

    render()
    m_context.swapBuffers(self)
    if m_animating:
        renderLater()

启用动画也会像下面的代码片段所示安排一个更新请求。

def setAnimating(self, animating):

    m_animating = animating
    if animating:
        renderLater()

示例 OpenGL 渲染子类#

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

class TriangleWindow(OpenGLWindow):

# public
    OpenGLWindow::OpenGLWindow = using()
    def initialize():
    def render():
# private
    m_posAttr = 0
    m_colAttr = 0
    m_matrixUniform = 0
    m_program = None
    m_frame = 0

在我们的 main 函数中,我们初始化 QGuiApplication 并实例化我们的 TriangleOpenGLWindow。我们给它一个 QSurfaceFormat,指定我们想要多样本反锯齿的四样本,以及默认的几何形状。由于我们想要有动画,我们通过传递 true 参数调用上面提到的 setAnimating() 函数。

if __name__ == "__main__":

    app = QGuiApplication(argc, argv)
    format = QSurfaceFormat()
    format.setSamples(16)
    window = TriangleWindow()
    window.setFormat(format)
    window.resize(640, 480)
    window.show()
    window.setAnimating(True)
    sys.exit(app.exec())

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

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"
fragmentShaderSource =
    "varying lowp vec4 col;\n"
    "void main() {\n"
    " gl_FragColor = col;\n"
    "}\n"

以下是加载着色器和初始化着色器程序的代码。通过使用 QOpenGLShaderProgram 而不是原始 OpenGL,我们得到了便利,它会从桌面 OpenGL 中删除 highp、mediump 和 lowp 副词,在这些副词 不是标准的一部分。我们将属性和统一的位置存储在成员变量中,以避免在每一帧中执行查找。

def initialize(self):

    m_program = QOpenGLShaderProgram(self)
    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 设置视口、清除背景并渲染一个旋转的三角形。

def render(self):

    retinaScale = devicePixelRatio()
    glViewport(0, 0, width() * retinaScale, height() * retinaScale)
    glClear(GL_COLOR_BUFFER_BIT)
    m_program.bind()
    matrix = QMatrix4x4()
    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)
    vertices = {
         0.0f, 0.707f,
        -0.5f, -0.5f,
         0.5f, -0.5f

    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 = m_frame + 1

@ code.qt.io 示例项目