警告
本节包含自动从C++翻译到Python的代码片段,可能包含错误。
OpenGL窗口示例#
此示例展示了如何创建一个基于QWindow的、用于OpenGL的简洁应用程序。
注意
这是一个如何使用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