Qt中使用OpenGL(一)

Qt 中的 OpenGL

Qt 中对 OpenGL 进行了封装,一般来说,是需要继承 QOpenGLWidgetQOpenGLFunctions。大致上,用于显示 OpenGL 的窗口应该是一个类,这个类包括以下特性:

  • 继承 QOpenGLWidgetQOpenGLFunctions
  • 重载 void initializeGL(), void paintGL()void resizeGL(int w, int h)这三个函数
  • 提供函数用于设置相机的位置与选择
  • 提供函数用于设置需要显示内容的具体数据
  • 提供函数用于设置显示内容的缩放、旋转、位移等

当显示复杂的内容时,则需要编写额外的逻辑,如模型管理等。

initializeGL

initializeGL 函数负责对 OpenGL 进行一定的初始化:

  1. 初始化函数指针

    initializeOpenGLFunctions()

  2. 启用一些 flag

    例如 glEnable(GL_DEPTH_TEST),开启深度测试。深度测试可以在绘制三维图形的时候,产生近处物体遮挡远处物体的效果

  3. 创建缓存

    OpenGL 使用的时候需要一些缓存对象,如顶点数组对象(VAO),顶点缓存对象(VBO),在进行绘制的时候,数据来源于这些缓存对象

  4. 初始化 shader 程序并链接

    Qt 提供了一个类 QOpenGLShaderProgram,通过这个类的 addShaderFromSourceCode 或者 addShaderFromSourceFile 方法加载 shader,然后使用 link 函数链接

  5. 加载数据

    如果要显示的数据是静态数据,如静态模型,就可以在初始化的时候加载数据。如果是动态数据,就要实时加载

paintGL

在 paintGL 函数中进行主要的三维绘制逻辑,主要进行如下操作:

  1. 清除之前绘制的内容,包括颜色与深度信息

    1
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  2. 启用 shader

    1
    m_shaderProgram->bind();
  3. 设置 shader 中的全局数据,例如模型矩阵、视图矩阵、投影矩阵等

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 设置视图矩阵
    m_viewMatrix.setToIdentity();
    QVector3D _lookDir{0, 0, 1};
    QVector3D _up{0, -1, 0};
    _lookDir = _lookDir * m_cameraRotate;
    _up = _up * m_cameraRotate;
    m_viewMatrix.lookAt(m_cameraPos, m_cameraPos + _lookDIr, _up);
    m_shaderProgram->setUniformValue("view", m_viewMatrix);
    // 设置投影矩阵
    m_shaderProgram->setUniformValue("projection", m_projectionMatrix);
    // 设置模型矩阵
    m_modelMatrix.setToIdentity();
    m_modelMatrix *= m_scale;
    m_modelMatrix *= m_rotate;
    m_modelMatrix *= m_translate;
    m_shaderProgram->setUniformValu("model", m_modelMatrix);
  4. 启用顶点数组对象(VAO)

    1
    m_VAO.bind();
  5. 启用并设置顶点缓存对象(VBO)

    1
    2
    3
    4
    // 绑定 VBO
    m_VBO.bind();
    m_VBO.setUsagePattern(QOpenGLBuffer::StaticDraw);
    m_VBO.allocate(m_vertices, m_verticesCount * 3 * sizeof(float));
  6. 将 shader 与顶点缓存进行对应,告诉 OpenGL 如何将顶点缓存中的数据转化为 shader 中的顶点数据

    1
    2
    m_shaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 3, 0);
    m_shaderProgram->enableAttributeArray(0);
  7. 调用绘制函数,绘制顶点缓存中的内容

    1
    2
    // 绘制
    glDrawArrays(GL_POINTS, 0, m_verticesCount);
  8. 释放顶点数组对象

    1
    m_VAO.release();
  9. 释放 shader

    1
    m_shaderProgram.release();

其中,第五、六步是动态加载数据的步骤,如果数据是静态的,则应该在初始化执行操作。初始化的时候也应该先启用 shader 和 VAO ,并在最后释放 shader 和 VAO。

总结

初始阶段

  • 初始化 OpenGL 函数
  • 初始化各种 flag
  • 创建各种缓存对象
  • 创建并链接 shader
  • 启用 shader 和 缓存
  • 绑定缓存 (静态数据)
  • 绑定 shader 的缓存数据 (告诉 OpenGL 如何从缓存中读取到 shader 中)
  • 释放缓存
  • 释放shader

绘制阶段

  • 清空屏幕
  • 启用 shader
  • 绑定 shader 的全局数据(各种矩阵)
  • 启用缓存
  • 绑定缓存(加载动态数据)
  • 绑定 shader 的缓存数据(告诉 OpenGL 如何从缓存中读取到 shader)
  • 绘制(使用 shader 读取缓存中的数据,转换为顶点数据,按照命令基于顶点绘制单位数据)
  • 释放缓存
  • 释放 shader

可以存在多个缓存,每个缓存可以保存不同的内容,根据需要,可以在绘制阶段重复 启用缓存到释放缓存 之间的步骤以达到绘制不同模型的目的。