learnOpenGL网站是学习openGL非常有用的网站,作为一个小白,为了方便后续回顾同时给大家提供借鉴,在此记录学习的过程。
本文参考:你好,三角形
请在上篇文章的基础上阅读本篇文章。
1. 索引缓冲对象(EBO/IBO)
1.1 概念
假设我们不再绘制一个三角形而是绘制一个矩形。我们可以绘制两个三角形来组成一个矩形(OpenGL主要处理三角形)。这会生成下面的顶点的集合:
float vertices[] = { // 第一个三角形 0.5f, 0.5f, 0.0f, // 右上角 0.5f, -0.5f, 0.0f, // 右下角 -0.5f, 0.5f, 0.0f, // 左上角 // 第二个三角形 0.5f, -0.5f, 0.0f, // 右下角 -0.5f, -0.5f, 0.0f, // 左下角 -0.5f, 0.5f, 0.0f // 左上角 };
可以看到我们存储了重复的顶点数值,这会造成资源的浪费。索引缓冲对象就是解决此问题的方法。和顶点缓冲对象一样,EBO也是一个缓冲,它专门储存索引,OpenGL调用这些顶点的索引来决定该绘制哪个顶点。
1.2 定义顶点和索引
定义不重复的顶点和绘制出矩形所需的索引:
float vertices[] = { 0.5f, 0.5f, 0.0f, // 右上角 0.5f, -0.5f, 0.0f, // 右下角 -0.5f, -0.5f, 0.0f, // 左下角 -0.5f, 0.5f, 0.0f // 左上角 }; unsigned int indices[] = { // 注意索引从0开始! 0, 1, 3, // 第一个三角形 1, 2, 3 // 第二个三角形 };
1.3 创建及配置
1. 创建索引缓冲对象
unsigned int EBO; glGenBuffers(1, &EBO);
2. 与VBO类似,我们先绑定EBO然后用glBufferData
把索引复制到缓冲里,把缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
1.4 绘制
最后一件要做的事是用glDrawElements
来替换glDrawArrays
函数,来指明我们从索引缓冲渲染。使用glDrawElements
时,我们会使用当前绑定的索引缓冲对象中的索引进行绘制:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
- 第一个参数指定了我们绘制的模式,这个和
glDrawArrays
的一样。 - 第二个参数是我们打算绘制顶点的个数,这里填6,也就是说我们一共需要绘制6个顶点。
- 第三个参数是索引的类型,这里是
GL_UNSIGNED_INT
。 - 最后一个参数里我们可以指定EBO中的偏移量(或者传递一个索引数组,但是这是当你不在使用索引缓冲对象的时候),但是我们会在这里填写0。
当目标是
GL_ELEMENT_ARRAY_BUFFER
的时候,VAO会储存glBindBuffer
的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了。
最终结果:
2. 全部源码
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> const char *vertexShaderSource = "#version 330 core\n" "layout (location = 0) in vec3 aPos;\n" "void main()\n" "{\n" " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" "}\0"; const char *fragmentShaderSource = "#version 330 core\n" "out vec4 FragColor;\n" "void main()\n" "{\n" " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" "}\n\0"; void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window); void draw(GLFWwindow *window); int main() { glfwInit(); //初始化GLFW //配置GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //告知系统我们使用的opengl版本是3.3 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //同样明确告诉GLFW我们使用的是核心模式(Core-profile) glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //MAC环境下需加这一句才能使以上配置生效 GLFWwindow *window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr); if (window == nullptr) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); //初始化glad if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } //设置视口 glViewport(0, 0, 800, 600); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); //----------> 1. 输入顶点 float vertices[] = { 0.5f, 0.5f, 0.0f, // 右上角 0.5f, -0.5f, 0.0f, // 右下角 -0.5f, -0.5f, 0.0f, // 左下角 -0.5f, 0.5f, 0.0f // 左上角 }; unsigned int indices[] = { // 注意索引从0开始! 0, 1, 3, // 第一个三角形 1, 2, 3 // 第二个三角形 }; //---------> 2. 创建VBO并绑定 unsigned int VBO; glGenBuffers(1, &VBO); //绑定一个VBO对象 glBindBuffer(GL_ARRAY_BUFFER, VBO); //将顶点数据复制到缓冲内存中 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //---------> 3. 创建VAO并绑定 unsigned int VAO; glGenVertexArrays(1, &VAO); // bind the Vertex Array Object first glBindVertexArray(VAO); unsigned int EBO; glGenBuffers(1, &EBO); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); //---------> 4. 链接顶点属性 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); //---------> 5. 创建并编译顶点着色器 unsigned int vertexShader; vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); //检测编译是否成功 int success; char infoLog[512]; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if(!success) { glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; } //--------> 6. 创建并编译片段着色器 //创建片段着色器对象 unsigned int fragmentShader; fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader); glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; } //--------> 7. 链接着色器 unsigned int shaderProgram; shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram); glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); if(!success) { glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); std::cout << "ERROR::SHADER::LINK_FAILED\n" << infoLog << std::endl; } glDeleteShader(vertexShader); glDeleteShader(fragmentShader); //循环渲染 while(!glfwWindowShouldClose(window)) { //检测输入事件 processInput(window); //渲染指令 draw(window); //------> 8. 画三角形 glUseProgram(shaderProgram); glBindVertexArray(VAO); // glDrawArrays(GL_TRIANGLES, 0, 3); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glfwSwapBuffers(window); glfwPollEvents(); } //正确释放/删除之前的分配的所有资源 glfwTerminate(); return 0; } void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); } void processInput(GLFWwindow *window) { if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); } void draw(GLFWwindow *window) { glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); }
3. 练习
3.1 创建相同的两个三角形,但对它们的数据使用不同的VAO和VBO
VAO和VBO绑定步骤: 1. 生成VAO和VBO 下面步骤循环: 2. 绑定VAO 3. 接着绑定VBO 4. 设置顶点属性指针
实例代码:
unsigned int VBO[2]; glGenBuffers(2, VBO); unsigned int VAO[2]; glGenVertexArrays(2, VAO); // bind the Vertex Array Object first glBindVertexArray(VAO[0]); //绑定一个VBO对象 glBindBuffer(GL_ARRAY_BUFFER, VBO[0]); //将顶点数据复制到缓冲内存中 glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // bind the Vertex Array Object first glBindVertexArray(VAO[1]); //绑定一个VBO对象 glBindBuffer(GL_ARRAY_BUFFER, VBO[1]); //将顶点数据复制到缓冲内存中 glBufferData(GL_ARRAY_BUFFER, sizeof(secondTriangle), secondTriangle, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0);
效果:
3.2 创建两个着色器程序,第二个程序使用一个不同的片段着色器,输出黄色;再次绘制这两个三角形,让其中一个输出为黄色
结果:
源码:
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> const char *vertexShaderSource = "#version 330 core\n" "layout (location = 0) in vec3 aPos;\n" "void main()\n" "{\n" " gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" "}\0"; const char *fragmentShaderSource1 = "#version 330 core\n" "out vec4 FragColor;\n" "void main()\n" "{\n" " FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" "}\n\0"; const char *fragmentShaderSource2 = "#version 330 core\n" "out vec4 FragColor;\n" "void main()\n" "{\n" " FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);\n" "}\n\0"; void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window); void draw(GLFWwindow *window); int main() { glfwInit(); //初始化GLFW //配置GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //告知系统我们使用的opengl版本是3.3 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //同样明确告诉GLFW我们使用的是核心模式(Core-profile) glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); //MAC环境下需加这一句才能使以上配置生效 GLFWwindow *window = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr); if (window == nullptr) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); //初始化glad if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } //设置视口 glViewport(0, 0, 800, 600); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); float firstTriangle[] = { -0.9f, -0.5f, 0.0f, // left -0.0f, -0.5f, 0.0f, // right -0.45f, 0.5f, 0.0f, // top }; float secondTriangle[] = { 0.0f, -0.5f, 0.0f, // left 0.9f, -0.5f, 0.0f, // right 0.45f, 0.5f, 0.0f // top }; unsigned int VBO[2]; glGenBuffers(2, VBO); unsigned int VAO[2]; glGenVertexArrays(2, VAO); // bind the Vertex Array Object first glBindVertexArray(VAO[0]); //绑定一个VBO对象 glBindBuffer(GL_ARRAY_BUFFER, VBO[0]); //将顶点数据复制到缓冲内存中 glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // bind the Vertex Array Object first glBindVertexArray(VAO[1]); //绑定一个VBO对象 glBindBuffer(GL_ARRAY_BUFFER, VBO[1]); //将顶点数据复制到缓冲内存中 glBufferData(GL_ARRAY_BUFFER, sizeof(secondTriangle), secondTriangle, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); //---------> 5. 创建并编译顶点着色器 unsigned int vertexShader; vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); //检测编译是否成功 int success; char infoLog[512]; glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); if(!success) { glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; } //--------> 6. 创建并编译片段着色器 //创建片段着色器对象 unsigned int fragmentShader1; fragmentShader1 = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader1, 1, &fragmentShaderSource1, NULL); glCompileShader(fragmentShader1); glGetShaderiv(fragmentShader1, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader1, 512, NULL, infoLog); std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; } unsigned int fragmentShader2; fragmentShader2 = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader2, 1, &fragmentShaderSource2, NULL); glCompileShader(fragmentShader2); glGetShaderiv(fragmentShader2, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader2, 512, NULL, infoLog); std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; } //--------> 7. 链接着色器 unsigned int shaderProgram1; shaderProgram1 = glCreateProgram(); glAttachShader(shaderProgram1, vertexShader); glAttachShader(shaderProgram1, fragmentShader1); glLinkProgram(shaderProgram1); glGetProgramiv(shaderProgram1, GL_LINK_STATUS, &success); if(!success) { glGetProgramInfoLog(shaderProgram1, 512, NULL, infoLog); std::cout << "ERROR::SHADER::LINK_FAILED\n" << infoLog << std::endl; } unsigned int shaderProgram2; shaderProgram2 = glCreateProgram(); glAttachShader(shaderProgram2, vertexShader); glAttachShader(shaderProgram2, fragmentShader2); glLinkProgram(shaderProgram2); glGetProgramiv(shaderProgram2, GL_LINK_STATUS, &success); if(!success) { glGetProgramInfoLog(shaderProgram2, 512, NULL, infoLog); std::cout << "ERROR::SHADER::LINK_FAILED\n" << infoLog << std::endl; } glDeleteShader(vertexShader); glDeleteShader(fragmentShader1); glDeleteShader(fragmentShader2); //循环渲染 while(!glfwWindowShouldClose(window)) { //检测输入事件 processInput(window); //渲染指令 draw(window); //------> 8. 画三角形 glUseProgram(shaderProgram1); glBindVertexArray(VAO[0]); glDrawArrays(GL_TRIANGLES, 0, 3); glUseProgram(shaderProgram2); glBindVertexArray(VAO[1]); glDrawArrays(GL_TRIANGLES, 0, 3); glfwSwapBuffers(window); glfwPollEvents(); } //正确释放/删除之前的分配的所有资源 glfwTerminate(); return 0; } void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); } void processInput(GLFWwindow *window) { if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); } void draw(GLFWwindow *window) { glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); }