GPU有成千上万的小核心,每个核心都可以跑专门的小程序,这种程序称为shader。
GPU有固定的shader流水线,我们也可以通过自定义重写shader,精细的控制流水线的每一部分。
蓝色区域是我们可以重写的shader
VBO:顶点数据仓库
- 功能:
VBO是显存中的二进制数据块,负责高效存储顶点数据(如坐标、颜色、法线等),大幅减少CPU-GPU通信开销。 - 特点:
- 纯数据容器:以连续二进制流存储,例如
[x1,y1,z1,r1,g1,b1,x2,y2,z2,...]
。 - 灵活使用模式:支持静态(
GL_STATIC_DRAW
)、动态(GL_DYNAMIC_DRAW
)等存储策略。
- 纯数据容器:以连续二进制流存储,例如
1. 生成缓冲对象
使用 glGenBuffer 函数创建一个新的 VBO,并返回其 ID。
GLuint vbo;
glGenBuffers(1, &vbo); // 1 表示一次性创建几个,后一个参数提供数组指针,将ID回存入数组
2. 绑定缓冲对象
使用 glBindBuffer 函数将创建的 VBO 绑定为当前的顶点缓冲对象。
OPenGL状态机配置阶段只支持激活一个VBO,所以后续数据传入也必须指定这个缓冲区类型。
glBindBuffer(GL_ARRAY_BUFFER, vbo);
3. 上传数据
使用 glBufferData 函数将顶点数据上传到显卡内存。
float vertices[] = {// 顶点数据,例如三角形的三个顶点0.0f, 1.0f, 0.0f,-1.0f, -1.0f, 0.0f,1.0f, -1.0f, 0.0f
};//GL_STATIC_DRAW 表示数据不会频繁更改。
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
传入到之前绑定好类型和对象的缓冲区
数据传到的缓冲区类型要和绑定的一致
// 绑定 VBO_A 并传输数据
glBindBuffer(GL_ARRAY_BUFFER, vboA);
glBufferData(GL_ARRAY_BUFFER, sizeof(dataA), dataA, GL_STATIC_DRAW); // 数据传至 vboA// 绑定 VBO_B 并传输数据
glBindBuffer(GL_ARRAY_BUFFER, vboB);
glBufferData(GL_ARRAY_BUFFER, sizeof(dataB), dataB, GL_STATIC_DRAW); // 数据传至 vboB
VAO:数据解析蓝图
- 功能:
VAO定义VBO中数据的解析规则,通过顶点属性指定数据结构、类型和用途,实现数据与着色器的对接。 - 核心配置:
- 属性指针(
glVertexAttribPointer
):定义数据起始偏移、类型(如GL_FLOAT
)、向量维度(如3D坐标)等。 - 属性启用(
glEnableVertexAttribArray
):激活对应属性通道,与着色器layout(location = N)
绑定。
- 属性指针(
- 优势:
- 状态封装:绘制时只需绑定VAO,自动恢复所有属性配置。
- 多VBO支持:可关联多个VBO(如坐标VBO、颜色VBO),通过不同属性指针区分。
其同一存储在显存中
1. 生成 VAO
使用 glGenVertexArrays 函数创建一个新的 VAO,并返回其 ID。
GLuint VAO;
glGenVertexArrays(1, &VAO);
2. 绑定 VAO
使用 glBindVertexArray 函数将创建的 VAO 绑定为当前的顶点数组对象。绑定 VAO 后,所有后续的顶点属性配置(如 VBO 绑定、顶点属性指针设置)都会存储在这个 VAO 中。
glBindVertexArray(VAO);
后续
glVertexAttribPointer
、glEnableVertexAttribArray
、VBO的绑定状态,会自动存入 VAO
3. 生成并绑定VBO
GLfloat vertices[] = {// 顶点坐标-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f, 0.5f, 0.0f
};GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
4. 设置顶点属性解析方式
// 设置顶点属性解析方式
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0); // 启用顶点解析
glVertexAttribPointer 时,VAO会将当前绑定的VBO的引用和配置记录下,可以记录无限个绑定的。渲染时批量取出
graph TBA[初始化阶段] --> B[创建 VAO 并绑定]B --> C1[绑定 vbo_position]C1 --> D1[配置属性0 位置]B --> C2[绑定 vbo_color]C2 --> D2[配置属性1 颜色]A --> E[解绑 VAO 和 VBO]F[渲染阶段] --> G[绑定 VAO]G --> H[自动恢复属性0→vbo_position]G --> I[自动恢复属性1→vbo_color]G --> J[调用 glDrawArrays]
完整流程示例
#include <iostream>
#include <cstdlib>#include <GL/glew.h>
#include <GLFW/glfw3.h>// shader硬编码可以定义一个宏 #define GET_STR(x) #x 会自动加双引号和换行
#define GET_STR(x) #xfloat vertices[] = {-0.5f, -0.5f, 0.0f, // 左下角0.5f, -0.5f, 0.0f, // 右下角0.0f, 0.5f, 0.0f // 顶部
};const char *vertexShaderSource = R"(#version 330 corelayout (location = 0) in vec3 aPos;void main(){gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);}
)";const char *fragmentShaderSource = R"(#version 330 coreout vec4 FragColor;void main(){FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f); // 绘制一个橙色三角形}
)";void processInput(GLFWwindow *window) {// 检查ESC键状态if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {glfwSetWindowShouldClose(window, true); // 退出程序}
}int main(int, char**) {// 初始化GLFWif (!glfwInit()) {std::cerr << "Failed to initialize GLFW" << std::endl;return EXIT_FAILURE;}// 设置OpenGL版本和配置文件glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 使用核心配置glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);// 创建窗口GLFWwindow* window = glfwCreateWindow(800, 600, "My OpenGL Game", NULL, NULL);if (window == NULL) {std::cerr << "Failed to create GLFW window" << std::endl;glfwTerminate(); // 退出return -1;}// 设置当前上下文glfwMakeContextCurrent(window); // 将这个窗口作为当前glfw上下文// 初始化GLEWglewExperimental = GL_TRUE; // 需要在glewInit()之前设置if (glewInit() != GLEW_OK) {std::cerr << "Failed to initialize GLEW" << std::endl;glfwTerminate();return EXIT_FAILURE;}// 设置视口,左下角起始坐标点,可以绘制的宽高glViewport(0, 0, 800, 600);// VAOunsigned int VAO;glGenVertexArrays(1, &VAO);glBindVertexArray(VAO);// VBOunsigned int VBO;glGenBuffers(1, &VBO);glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBOglBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// set vertex attribute pointersglVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// vertex shaderunsigned int vertexShader;vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); // count: 几个字串glCompileShader(vertexShader);// fragment shaderunsigned int fragmentShader;fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);// shader programunsigned int shaderProgram;shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// 主渲染循环// 当前输入作用于当前帧while (!glfwWindowShouldClose(window)) {glfwPollEvents(); // 获取键盘和鼠标事件传递到window上下文processInput(window); // 判断上下文中的事件// 设置背景色glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 设置清空缓冲区后填充的颜色,默认值,背景色glClear(GL_COLOR_BUFFER_BIT); // 清空缓冲区颜色glBindVertexArray(VAO);glUseProgram(shaderProgram);glDrawArrays(GL_TRIANGLES, 0, 3);// 交换缓冲区glfwSwapBuffers(window); // 双缓冲区,一个用于显示,一个用于绘制}glfwTerminate();return 0;
}