OpenGL 窗口示例
本示例演示了如何创建一个基于QWindow 的简易应用程序,用于调用OpenGL。

注意:这是一个 关于如何在QWindow 中使用OpenGL的低级示例。实际应用中,建议考虑使用更高层次的QOpenGLWindow 类。有关QOpenGLWindow 便捷类的演示,请参阅“Hello GLES3”示例。
OpenGLWindow 超类
我们的 OpenGLWindow 类充当 API 接口,随后通过子类化来执行实际的渲染。它提供了用于请求调用 render() 的函数:既可以通过 renderNow() 立即执行,也可以通过 renderLater() 在事件循环处理完当前批次事件后立即执行。 OpenGLWindow 的子类可以重写 render() 方法以进行基于 OpenGL 的渲染,或者重写 render(QPainter *) 方法以使用QPainter 进行渲染。若底层 OpenGL 驱动程序启用了垂直同步,请使用 OpenGLWindow::setAnimating(true) 使 render() 方法按垂直刷新率调用。
在执行 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 渲染,而不是使用QPainter 通过QBackingStore 渲染光栅内容。
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(),以在系统准备好重绘时安排更新。
当收到暴露事件时,我们也会调用 renderNow()。exposeEvent() 是通知窗口其在屏幕上的暴露状态(即可见性)已发生变化的事件。收到暴露事件后,你可以查询QWindow::isExposed() 来确定窗口当前是否处于暴露状态。 在窗口收到首个 expose 事件之前,请勿对其进行渲染或调用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()` 中,如果当前未处于可见状态,则立即返回;此时渲染操作将被延迟,直到实际收到 `expose` 事件为止。 如果尚未创建,则使用与 OpenGLWindow 上设置的相同的QSurfaceFormat 创建QOpenGLContext ,并调用 initialize() 以满足子类的需要,同时调用 initializeOpenGLFunctions(),以便将QOpenGLFunctions 超类与正确的QOpenGLContext 关联起来。 无论如何,我们通过调用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() 来安排另一个更新请求。
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 ,指定需要四次多采样抗锯齿,以及默认的几何体。由于需要实现动画效果,我们调用前述的setAnimating()函数,并将参数设为true。
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修饰符的便利——这些修饰符在桌面版OpenGL中并不属于标准规范。 我们将属性(attribute)和统一变量(uniform)的位置存储在成员变量中,以避免每帧都需要进行位置查找。
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;
}© 2026 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.