OpenGL概念

用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)。Open Graphics Library由Khronos组织制定并维护的规范。OpenGL核心是一个C库,同时也支持多种语言的派生。

框架:OpenGL+Qt

没有Qt需要配置第三方库GLFW和GLAD。

GLFW解决操作系统层面的不同,如果窗口、上下文、用户输入都自己做太麻烦:

  • 创建窗口,GLFW做
  • 定义上下文,GLFW做
  • 处理用户输入,GLFW做

GLAD使得代码可以用于不同的OpenGL驱动

  • OpenGL本身只是标准/规范。对于软件和硬件的适配OpenGL不管,由程序猿来做。

  • 各个厂家具体实现方式可以不同。

  • 没有GLAD,windows下需要通过函数指针调用显卡的函数,但是显卡驱动具体函数的地址只有在运行时才知道。

    1
    2
    3
    4
    5
    6
    7
    //define the function's prototype
    typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*);
    //find the function and assign it to a function pointer
    GL_GENBUFFERS glGenBuffers = (GL_GENBUFFERS)wglGetProcAddress("glGenBuffers");//wglGetProcAddress("glGenBuffers")获取显卡在当前上下文状态下的OpenGL函数glGenBuffers的地址。
    //function can now be called as normal
    unsigned int buffer;
    glGenBuffers(1, &buffer);

    有GLAD,我们只使用GLAD的固定代码,让GLAD去找显卡的驱动函数地址。

**核心模式(Core-profile)**也叫可编程管线、现代模式、可编程模式,提供了更多的灵活性,更高的效率,更重要的是可以更深入的理解图形编程。

image-20251106232849296

GPU渲染流程:

  1. 输入给GPU顶点数据。(可编程)
  2. GPU将顶点装配成图元,即建立大框架。(不可编程)
  3. GPU在大框架中插入部分点。(可编程)
  4. GPU在几何润色的框架基础上生成像素栅格。(不可编程)
  5. GPU把栅格化的框架着色。(可编程)
  6. GPU将多个着色的栅格(位置有前有后)框架融合和混合。(不可编程)

非可编程管线即固定管线,2、4、6的步骤的输入输出都以写死;1、3、5的步骤是可编程的管线,其中1、5是必须要写代码的模块,3可以写代码也可以使用默认处理流程。

立即渲染

  • 这是早期OpenGL使用的模式(固定渲染管线)。
  • OpenGL的大多数功能都被库隐藏起来,容易使用和理解但是效率低。
  • 开发者很少能控制OpenGL如何进行计算。
  • 从OpenGL3.2以后,推出核心模式。

状态机

  • OpenGL自身是一个巨大的状态机(上下文即当前状态)
    • 描述该如何操作的所有变量的大集合。
  • OpenGL的状态通常被称为上下文(Context)
  • 状态机函数分两种:
    • 状态设置函数(State-changing Function)
    • 状态应用函数(State-using Function)
image-20251110213713573

我们通过改变一些上下文变量来改变OpenGL状态,从而告诉OpenGL如何去绘图。

对象

一个对象是指一些选项的集合,代表OpenGL状态的一个子集。例如一个对象可以是绘图窗口,其包含若干OpenGL状态,如窗口大小、颜色位数等。可以将一个C风格的结构体视作一个对像。

1
2
3
4
5
struct object_name {
GLfloat option1;
GLint option2;
GLchar[] name;
};

OpenGL上下文是一个超大的状态集合(状态机、结构体)可以包含若干子集(对象、结构体)。

1
2
3
4
5
struct OpenGL_Context{
...
object* object_Window_Target;
...
};

当前状态只有一份,如果每次显示不同的效果都要重新配置会很麻烦。这时我们就需要一些小助理(对象),帮忙记录某些状态信息,以便复用。如果我们有10种子集,每个子集有10种不同的状态,那么我们需要100个小助理(对象)。

1
2
3
4
5
6
7
8
9
10
11
//创建对象,使用OpenGL自己的uint,可以跨平台
GLuint objectId = 0;
glGenObject(1,&objectId); //给助理(对象)一个编号
//绑定对象至上下文,给助理分配任务,记录GL_WINDOW_TARGET的状态
glBindObject(GL_WINDOW_TARGET, objectId); //该助理(对象),这次的工作内容(任务)需要绑定了才确定
//设置GL_WINDOW_TARGET对象的一些选项,即状态
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
//将上下文的GL_WINDOW_TARGET对像设回默认
glBindObject(GL_WINDOW_TARGET, 0); //助理(对象)已经记录了上面的内容,可以休息了。需要查看记录的时候喊他过来就好,即再次绑定助理
//一旦我们重新绑定这个对象到GL_WINDOW_TARGET位置,这些选项就会重新生效

Hello World

QOpenGLWidget

QOpenGLWidget提供了三个便捷的虚函数,可以重载,用来重新实现典型的OpenGL任务,不需要GLFW库:

  • paintGL: 渲染OpenGL场景,绘画操作(QOpenGLFunctions_X_X_Core里提供的函数指针)都放到该函数中。widget需要更新时调用。
  • resizeGL: 设置OpenGL视口、投影等。widget调整大小时调用。
  • initializeGL: 设置OpenGL资源和状态。第一次调用resizeGL(),paintGL()之前调用一次。
  • 如果需要从paintGL()以外的位置触发重新绘图(例如使用计时器设置场景动画),则应调用widget的update()函数来安排更新。在paintGL()以外的位置调用绘制函数没有意义,绘制图像最终将被paintGL()覆盖。
  • 调用paintGL(),resizeGL(),initializeGL()时,widget的OpenGL呈现上下文将变为当前。如果需要从其他位置( 例如在widget的构造函数或自己的绘制函数中)调用标准OpenGL API函数,则必须首先调用makeCurrent()获取当前状态然后update。

QOpenGLFunctions_X_X_Core

QOpenGLFunctions_X_X_Core提供OpenGL X_X版本核心模式的所有功能,是对OpenGL函数的封装,不需要GLAD库, _X_X_表示版本号例如3_3。

  • initializeOpenGLFunctions: 初始化OpenGL函数,将QOpenGLFunctions_X_X_Core里的函数指针(例如glClearColor(0.2f, 0.3f, 0.3f, 1.0f);)指向显卡的函数。否则运行空指针报错。

Vulkan概念

GitHub

Vulkan是一种基于命令缓冲区的底层图形API接口,能够更好地利用现代GPU的强大计算能力,从而获得更高的渲染性能和更低的CPU开销。与OpenGL相比,Vulkan提供了更详细的硬件控制更高效的内存管理更灵活的管线状态管理多线程支持等优势。

图形学api是用gpu加速图形学中的计算过程的。shader是用g pu计算的,c++使用cpu计算的,当需要cpu和gpu交互时需要使用vulkan。