5.5 A workflow of generating a graphics

1. Initialize

1. GLFW-try to make a window

1
2
3
4
5
6
GLFWwindow* window;

if(!glfwInit())
return -1;

window = glfwCreateWindow(640,480,"Hello,World",NULL,NULL);

The params of glfwCreateWindow
![[image-1851-5.5 A workflow of generating a graphics.png]]

2. GLFW-make context

1
2
/* Make the window's context current */
glfwMakeContextCurrent(window);

3. GLEW-Init

1
2
3
/* Use glew to get the function pointers in the local driver,so that we can use them*/
if (glewInit() != GLEW_OK)
std::cerr << "Error" << std::endl;

![[image-1857-5.5 A workflow of generating a graphics.png]]

Function

  1. 初始化GLEW: glewInit 初始化GLEW内部状态,为后续操作做准备。
  2. 探测OpenGL扩展: glewInit 会查询OpenGL驱动程序支持的扩展,并将这些扩展的相关信息填充到GLEW维护的数据结构中。
  3. 加载扩展函数指针: 对于每个探测到的扩展,GLEW会尝试加载对应的函数指针。这样,开发者就可以直接调用这些扩展提供的函数,而无需手动加载。
  4. 设置GLEW错误处理: 如果在初始化过程中发生错误,glewInit 会设置一个错误码,可以通过 glewGetErrorString 函数获取错误信息。
  5. 返回状态: glewInit 返回一个整数值,表示初始化的状态。如果返回值为 GLEW_OK,则表示初始化成功;如果返回其他值,则表示初始化失败。

[!Answer] 为什么要每次都是用GLEW进行加载函数

  1. 平台无关性: OpenGL被设计为跨平台的图形API,这意味着它需要在不同的操作系统和硬件上运行。不同的平台和显卡驱动程序可能以不同的方式实现OpenGL功能。因此,OpenGL API本身不直接提供函数实现,而是提供了一个机制,允许操作系统和显卡驱动程序在运行时提供这些函数的实现。
  2. 扩展机制: OpenGL的扩展机制允许显卡制造商添加新的功能,而不需要等待OpenGL标准的更新。这些扩展可能不会被所有显卡支持,因此它们不是OpenGL核心API的一部分。为了使用这些扩展,程序需要查询是否支持这些扩展,并动态加载对应的函数指针。
  3. 版本兼容性: 随着OpenGL的发展,不同的版本可能包含不同的功能集。为了向后兼容,较新的驱动程序可能仍然支持较旧的OpenGL版本。通过动态加载函数指针,程序可以在运行时确定可用的OpenGL版本和功能,并相应地调整其行为。
    以下是一些更为具体的内容
  • 动态链接: 在大多数操作系统中,OpenGL函数不是静态链接到应用程序的,而是动态链接的。这意味着在程序运行时,操作系统负责将OpenGL函数的实现加载到程序的地址空间中。这个过程通常是通过一个称为“运行时加载”的过程完成的。
  • 函数指针: 由于OpenGL函数是在运行时加载的,因此应用程序不能直接调用这些函数,而是需要通过函数指针来间接调用。这些函数指针在程序启动时通常为NULL,直到通过适当的加载过程(如glewInitglXGetProcAddress)被赋值为正确的函数地址。
  • 驱动程序差异: 不同的显卡驱动程序可能以不同的方式实现OpenGL函数,甚至可能有不同的函数名。动态加载函数指针允许应用程序在运行时适应这些差异。
  • 错误处理: 如果直接调用未定义的函数,程序可能会崩溃。通过函数指针调用,程序可以在调用前检查指针是否为NULL,从而更优雅地处理不支持的功能。

2. Preparation for drawing

1. Prepare data array

1
2
3
4
5
6
float positions[6] =
{
-0.5f, -0.5f,
0.0f, 0.0f,
0.5f, -0.5f,
};

2. Create a buffer

1
2
3
4
// The idea of generating a buffer
unsigned int buffer;
glGenBuffers(1,&buffer);
// 1 represents that you want exactly one element to generate

3. Bind buffer

在OpenGL中,设置和渲染顶点数据通常涉及以下步骤和函数调用顺序。以下是一个详细的解释,包括每个函数的作用和调用顺序:

  1. 生成缓冲区对象:
    在使用任何缓冲区之前,先生成一个缓冲区对象。
    1
    2
    GLuint buffer;
    glGenBuffers(1, &buffer);
  2. 绑定缓冲区对象:
    将生成的缓冲区对象绑定到GL_ARRAY_BUFFER目标。
    1
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    这一步告诉OpenGL,接下来所有针对GL_ARRAY_BUFFER的操作都会影响这个缓冲区对象。
  3. 上传数据到缓冲区:
    使用glBufferData将顶点数据上传到当前绑定的缓冲区。
    1
    glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
    这里,data是包含顶点数据的数组,sizeof(data)是数据的总大小。
  4. 启用顶点属性数组:
    在使用顶点属性之前,首先需要启用它。
    1
    glEnableVertexAttribArray(index);
    index是顶点属性数组的索引,它对应于顶点着色器中的输入变量。
  5. 设置顶点属性指针:
    告诉OpenGL如何解释顶点数据。
    1
    glVertexAttribPointer(index, size, type, normalized, stride, pointer);
    • index: 顶点属性数组的索引。
    • size: 每个顶点属性的组件数量(例如,对于位置数据通常是3,表示(x, y, z))。
    • type: 数据的类型(例如,GL_FLOAT)。
    • normalized: 指示是否需要将非浮点数据归一化到[0, 1]或[-1, 1]。
    • stride: 连续顶点属性之间的字节偏移量。
    • pointer: 数据的偏移量或指针。
  6. 绘制:
    在设置完所有顶点属性后,您可以使用glDrawArraysglDrawElements等函数来绘制几何体。
    1
    glDrawArrays(mode, first, count);
    或者
    1
    glDrawElements(mode, count, type, indices);
  7. 解绑缓冲区 (可选):
    在绘制完成后,您可以解绑缓冲区对象,以避免后续操作意外修改它。
    1
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    总结一下,函数调用的顺序通常是:
    1
    2
    3
    4
    5
    6
    7
    glGenBuffers(1, &buffer);             // 生成缓冲区对象
    glBindBuffer(GL_ARRAY_BUFFER, buffer); // 绑定缓冲区对象
    glBufferData(GL_ARRAY_BUFFER, ...); // 上传数据到缓冲区
    glEnableVertexAttribArray(index); // 启用顶点属性数组
    glVertexAttribPointer(index, ...); // 设置顶点属性指针
    glDrawArrays(...); // 绘制
    glBindBuffer(GL_ARRAY_BUFFER, 0); // 解绑缓冲区 (可选)
    请注意,glVertexAttribPointer必须在glBufferData之后调用,因为它指定了如何解释已经上传到缓冲区的数据。同样,glEnableVertexAttribArray应该在这些设置之后调用,以确保顶点属性数组在绘制时是启用的。

Addition: print out the OpenGL version

1
std::cout << glGetString(GL_VERSION) << std::endl;