比如我们想要显示一张 QImage
我们可以用 QLabel 直接 setPixmap
也可以用 QGraphicsView 里设置一个 QGraphicsPixmapItem
但假如我们这张图需要不停刷新(30fps)并能随着控件大小缩放时
特别是 4K 分辨率下,性能 就出现了瓶颈
究其原因,还是 CPU 渲染慢,所以我们改用 GPU 来渲染
自然,我们首先想到的是 QOpenGLWidget
下面是个保持图像 400x640 比例的类,m_x、m_y 代表横纵黑边的长度
x1234567
8class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions9{ 10 Q_OBJECT11public: 12 explicit GLWidget(QWidget *parent) : QOpenGLWidget(parent) {}13 void setImage(QImage image)14 {15 currentImage = image;16 update();17 }18 void clearImage()19 {20 QImage image(m_w, m_h, QImage::Format_RGB888);21 image.fill(Qt::black);22 currentImage = image;23 update();24 }25protected:26 void paintGL()27 {28 QPainter painter;29 painter.begin(this);30 if(!currentImage.isNull())31 {32 painter.drawImage(QRectF(m_x, m_y, m_w, m_h), currentImage);33 }34 painter.end();35 }36 void initializeGL()37 {38 initializeOpenGLFunctions();39 glClearColor(0.f, 0.f, 0.f, 1.0f);40 }41 void resizeGL(int w, int h)42 {43 if(h > 640 * w / 400)44 {45 m_w = w;46 m_h = 640 * w / 400;47 m_x = 0;48 m_y = ( h - 640 * w / 400 ) / 2;49 }50 else51 {52 m_w = 400 * h / 640;53 m_h = h;54 m_x = ( w - 400 * h / 640) / 2;55 m_y = 0;56 }57 }58private:59 int m_w{400};60 int m_h{640};61 int m_x{0};62 int m_y{0};63 QImage currentImage;64};65
66我们可以在 Qt Creator 的 UI 设计器里拖出一个 QWidget
然后提升为这个自定义的类 GLWidget 来显示图像
很多算法相关的同学喜欢用 glfw 去做科研
如果用 QOpenGLWidget 重构下的话
就能获得 Qt 事件、Qt 信号槽等特性,更 方便使用
把 GLFW 的渲染代码迁到 Qt 的 QOpenGLWidget,核心思路是:
GLFW 负责的“创建窗口/创建上下文/事件循环/交换缓冲”,
在 Qt 里分别由 QOpenGLWidget + Qt 事件系统接管;
你只保留 OpenGL 初始化与绘制逻辑,
然后搬到 initializeGL() / paintGL() / resizeGL() 里。
GLFW:
31glfwInit();2GLFWwindow* w = glfwCreateWindow(...);3glfwMakeContextCurrent(w);Qt:
不需要 glfwInit/glfwCreateWindow/glfwMakeContextCurrent
用 QOpenGLWidget 就已经创建好 OpenGL 上下文了(在它内部)
如果你需要设置 OpenGL 版本/核心模式/多重采样等,用:
51QSurfaceFormat fmt;2fmt.setVersion(3, 3);3fmt.setProfile(QSurfaceFormat::CoreProfile);4fmt.setSamples(4);5QSurfaceFormat::setDefaultFormat(fmt);(在 QApplication 创建后、创建任何 OpenGL widget 之前设置最好)
GLFW:
51while (!glfwWindowShouldClose(w)) {2 render();3 glfwSwapBuffers(w);4 glfwPollEvents();5}Qt:
没有 while 主循环(Qt 自己跑 event loop)
不用手动 swap buffers(QOpenGLWidget 自己做 FBO + swap)
你只需要在合适时机调用 update() 触发重绘(Qt 会调用 paintGL())
GLFW 的 glfwSetKeyCallback、glfwSetCursorPosCallback 等
→ Qt 重写事件函数:
keyPressEvent / keyReleaseEvent
mousePressEvent / mouseMoveEvent / wheelEvent
或者装 eventFilter
initializeGL():一次性初始化(创建 VAO/VBO、编译 shader、加载纹理等)
resizeGL(w,h):窗口尺寸变化(glViewport、投影矩阵更新)
paintGL():每帧绘制(clear、bind、draw)
假设你原来用的是现代 OpenGL(VAO/VBO/Shader),建议让类继承
QOpenGLFunctions来拿到函数指针。
411234
5class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions {6 Q_OBJECT7public:8 explicit MyGLWidget(QWidget* parent=nullptr) : QOpenGLWidget(parent) {9 // 需要持续刷新就用定时器10 auto* t = new QTimer(this);11 connect(t, &QTimer::timeout, this, QOverload<>::of(&MyGLWidget::update));12 t->start(16); // ~60 FPS13 }14
15protected:16 void initializeGL() override {17 initializeOpenGLFunctions();18
19 // 等价于你 GLFW 初始化后做的 glEnable/glGen*/编译shader...20 glEnable(GL_DEPTH_TEST);21
22 // initResources(); // 你的 VAO/VBO/Shader/Texture 初始化23 }24
25 void resizeGL(int w, int h) override {26 glViewport(0, 0, w, h);27 // updateProjection(w, h);28 }29
30 void paintGL() override {31 glClearColor(0.1f, 0.1f, 0.12f, 1.0f);32 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);33
34 // renderScene(); // 你的 draw call35 }36
37 // 如果需要键鼠:38 void keyPressEvent(QKeyEvent* e) override {39 // 例如:if (e->key() == Qt::Key_W) ...40 }41};main.cpp 里:
20123
4int main(int argc, char** argv) {5 QApplication app(argc, argv);6
7 QSurfaceFormat fmt;8 fmt.setVersion(3, 3);9 fmt.setProfile(QSurfaceFormat::CoreProfile);10 fmt.setDepthBufferSize(24);11 fmt.setStencilBufferSize(8);12 fmt.setSamples(4);13 QSurfaceFormat::setDefaultFormat(fmt);14
15 MyGLWidget w;16 w.resize(800, 600);17 w.show();18
19 return app.exec();20}不要在构造函数里调用 OpenGL API
构造时上下文还没就绪;必须在 initializeGL() 之后才安全。
QOpenGLWidget 默认是“渲染到内部 FBO”再显示
如果你用到 glBindFramebuffer(0) 这类假设“0 就是屏幕”,在 QOpenGLWidget 里可能不成立(因为它在绘制时绑定了自己的 FBO)。
通常你无需自己绑定 0,直接在 paintGL() 里画就好
如果你必须拿默认 framebuffer id:Qt 提供 defaultFramebufferObject()
函数加载器的替换
GLFW 常用 glad/glew。Qt 里推荐用 QOpenGLFunctions(或你继续用 glad 也可以,但要确保在正确时机初始化)
如果你原来用 glad:把 glad 初始化放到 initializeGL() 里(确保 context current)
线程问题
QOpenGLWidget 的 OpenGL 上下文一般只在 GUI 线程使用;别在别的线程直接调用 GL(除非你很清楚 Qt 的共享上下文/离屏渲染方案)。
假设你原来:
61init();2while (...) {3 processInput();4 render();5}6cleanup();迁到 Qt:
init() → 放 initializeGL()
render() → 放 paintGL()
processInput() → 用 Qt 的事件(或定时器里更新状态)
cleanup() → 放析构函数里,但要用 makeCurrent() / doneCurrent() 包住释放 GL 资源(因为析构时可能没 current context)
示例(释放资源):
51MyGLWidget::~MyGLWidget() {2 makeCurrent();3 // glDeleteBuffers / glDeleteVertexArrays / delete shader program ...4 doneCurrent();5}