5.5 A workflow of generating a graphics
发表于更新于
5.5 A workflow of generating a graphics
Rif1. 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
- 初始化GLEW:
glewInit 初始化GLEW内部状态,为后续操作做准备。
- 探测OpenGL扩展:
glewInit 会查询OpenGL驱动程序支持的扩展,并将这些扩展的相关信息填充到GLEW维护的数据结构中。
- 加载扩展函数指针: 对于每个探测到的扩展,GLEW会尝试加载对应的函数指针。这样,开发者就可以直接调用这些扩展提供的函数,而无需手动加载。
- 设置GLEW错误处理: 如果在初始化过程中发生错误,
glewInit 会设置一个错误码,可以通过 glewGetErrorString 函数获取错误信息。
- 返回状态:
glewInit 返回一个整数值,表示初始化的状态。如果返回值为 GLEW_OK,则表示初始化成功;如果返回其他值,则表示初始化失败。
[!Answer] 为什么要每次都是用GLEW进行加载函数
- 平台无关性: OpenGL被设计为跨平台的图形API,这意味着它需要在不同的操作系统和硬件上运行。不同的平台和显卡驱动程序可能以不同的方式实现OpenGL功能。因此,OpenGL API本身不直接提供函数实现,而是提供了一个机制,允许操作系统和显卡驱动程序在运行时提供这些函数的实现。
- 扩展机制: OpenGL的扩展机制允许显卡制造商添加新的功能,而不需要等待OpenGL标准的更新。这些扩展可能不会被所有显卡支持,因此它们不是OpenGL核心API的一部分。为了使用这些扩展,程序需要查询是否支持这些扩展,并动态加载对应的函数指针。
- 版本兼容性: 随着OpenGL的发展,不同的版本可能包含不同的功能集。为了向后兼容,较新的驱动程序可能仍然支持较旧的OpenGL版本。通过动态加载函数指针,程序可以在运行时确定可用的OpenGL版本和功能,并相应地调整其行为。
以下是一些更为具体的内容
- 动态链接: 在大多数操作系统中,OpenGL函数不是静态链接到应用程序的,而是动态链接的。这意味着在程序运行时,操作系统负责将OpenGL函数的实现加载到程序的地址空间中。这个过程通常是通过一个称为“运行时加载”的过程完成的。
- 函数指针: 由于OpenGL函数是在运行时加载的,因此应用程序不能直接调用这些函数,而是需要通过函数指针来间接调用。这些函数指针在程序启动时通常为NULL,直到通过适当的加载过程(如
glewInit或glXGetProcAddress)被赋值为正确的函数地址。
- 驱动程序差异: 不同的显卡驱动程序可能以不同的方式实现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
| unsigned int buffer; glGenBuffers(1,&buffer);
|
3. Bind buffer
在OpenGL中,设置和渲染顶点数据通常涉及以下步骤和函数调用顺序。以下是一个详细的解释,包括每个函数的作用和调用顺序:
- 生成缓冲区对象:
在使用任何缓冲区之前,先生成一个缓冲区对象。1 2
| GLuint buffer; glGenBuffers(1, &buffer);
|
- 绑定缓冲区对象:
将生成的缓冲区对象绑定到GL_ARRAY_BUFFER目标。1
| glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
这一步告诉OpenGL,接下来所有针对GL_ARRAY_BUFFER的操作都会影响这个缓冲区对象。
- 上传数据到缓冲区:
使用glBufferData将顶点数据上传到当前绑定的缓冲区。1
| glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
|
这里,data是包含顶点数据的数组,sizeof(data)是数据的总大小。
- 启用顶点属性数组:
在使用顶点属性之前,首先需要启用它。1
| glEnableVertexAttribArray(index);
|
index是顶点属性数组的索引,它对应于顶点着色器中的输入变量。
- 设置顶点属性指针:
告诉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: 数据的偏移量或指针。
- 绘制:
在设置完所有顶点属性后,您可以使用glDrawArrays或glDrawElements等函数来绘制几何体。1
| glDrawArrays(mode, first, count);
|
或者1
| glDrawElements(mode, count, type, indices);
|
- 解绑缓冲区 (可选):
在绘制完成后,您可以解绑缓冲区对象,以避免后续操作意外修改它。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;
|