关于opengl中VAO、VBO和EBO的概念整理
最近在学习下opengl相关知识,主要通过这个地方学习:https://www.jianshu.com/p/6bda18e953f6
第二章和第三章中提到这么几个概念:顶点缓冲对象(VBO Vertex Buffer Object)、顶点数组对象(VAO Vertex Array Object)和元素缓存对象(EBO Element Buffer Object)
由于对于OpenGL图形渲染管线相关不是很熟,所以查阅相关资料,整理如下
上图是一张经典的OpenGL图形渲染管线概念图,
首先,我们以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做顶点数据(Vertex Data);顶点数据是一系列顶点的集合。一个顶点(Vertex)是一个3D坐标的数据的集合。而顶点数据是用顶点属性(Vertex Attribute)表示的,它可以包含任何我们想用的数据。
为了让OpenGL知道我们的坐标和颜色值构成的到底是什么,OpenGL需要你去指定这些数据所表示的渲染类型。我们是希望把这些数据渲染成一系列的点?一系列的三角形?还是仅仅是一个长长的线?做出的这些提示叫做图元(Primitive),任何一个绘制指令的调用都将把图元传递给OpenGL。这是其中的几个:GL_POINTS、GL_TRIANGLES、GL_LINE_STRIP。
图形渲染管线的第一个部分是顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标,同时顶点着色器允许我们对顶点属性进行一些基本处理。
图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状;。
图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。例子中,它生成了另一个三角形。
几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。
可以看到,图形渲染管线非常复杂,它包含很多可配置的部分。然而,对于大多数场合,我们只需要配置顶点和片段着色器就行了。几何着色器是可选的,通常使用它默认的着色器就行了。
顶点缓冲对象(Vertex Buffer Objects,VBO)
顶点缓冲对象VBO是在显卡存储空间中开辟出的一块内存缓存区,用于存储顶点的各类属性信息,如顶点坐标,顶点法向量,顶点颜色数据等。在渲染时,可以直接从VBO中取出顶点的各类属性数据,由于VBO在显存而不是在内存中,不需要从CPU传输数据,处理效率更高。
所以可以理解为VBO就是显存中的一个存储区域,可以保持大量的顶点属性信息。并且可以开辟很多个VBO,每个VBO在OpenGL中有它的唯一标识ID,这个ID对应着具体的VBO的显存地址,通过这个ID可以对特定的VBO内的数据进行存取操作。
顶点数组对象(Vertex Arrary Object,VAO)
VBO保存了一个模型的顶点属性信息,每次绘制模型之前需要绑定顶点的所有信息,当数据量很大时,重复这样的动作变得非常麻烦。VAO可以把这些所有的配置都存储在一个对象中,每次绘制模型时,只需要绑定这个VAO对象就可以了。
VAO是一个保存了所有顶点数据属性的状态结合,它存储了顶点数据的格式以及顶点数据所需的VBO对象的引用。
VAO本身并没有存储顶点的相关属性数据,这些信息是存储在VBO中的,VAO相当于是对很多个VBO的引用,把一些VBO组合在一起作为一个对象统一管理。
执行VAO绑定之后其后的所有VBO配置都是这个VAO对象的一部分,可以说VBO是对顶点属性信息的绑定,VAO是对很多个VBO的绑定。
索引缓冲对象(Element Buffer Object,EBO)
索引缓冲对象EBO相当于OpenGL中的顶点数组的概念,是为了解决同一个顶点多洗重复调用的问题,可以减少内存空间浪费,提高执行效率。当需要使用重复的顶点时,通过顶点的位置索引来调用顶点,而不是对重复的顶点信息重复记录,重复调用。
EBO中存储的内容就是顶点位置的索引indices,EBO跟VBO类似,也是在显存中的一块内存缓冲器,只不过EBO保存的是顶点的索引。
1 #include <iostream> 2 3 // GLEW 4 #define GLEW_STATIC 5 #include <GL/glew.h> 6 7 // GLFW 8 #include <GLFW/glfw3.h> 9 10 11 // Function prototypes 12 void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode); 13 14 // Window dimensions 15 const GLuint WIDTH = 800, HEIGHT = 600; 16 17 // Shaders 18 const GLchar* vertexShaderSource = "#version 330 core\n" 19 "layout (location = 0) in vec3 position;\n" 20 "void main()\n" 21 "{\n" 22 "gl_Position = vec4(position.x, position.y, position.z, 1.0);\n" 23 "}\0"; 24 const GLchar* fragmentShaderSource = "#version 330 core\n" 25 "out vec4 color;\n" 26 "void main()\n" 27 "{\n" 28 "color = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n" 29 "}\n\0"; 30 31 // The MAIN function, from here we start the application and run the game loop 32 int main() 33 { 34 // Init GLFW 35 glfwInit(); 36 // Set all the required options for GLFW 37 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 38 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 39 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 40 glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); 41 42 // Create a GLFWwindow object that we can use for GLFW's functions 43 GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr); 44 glfwMakeContextCurrent(window); 45 46 // Set the required callback functions 47 glfwSetKeyCallback(window, key_callback); 48 49 // Set this to true so GLEW knows to use a modern approach to retrieving function pointers and extensions 50 glewExperimental = GL_TRUE; 51 // Initialize GLEW to setup the OpenGL Function pointers 52 glewInit(); 53 54 // Define the viewport dimensions 55 int width, height; 56 glfwGetFramebufferSize(window, &width, &height); 57 glViewport(0, 0, width, height); 58 59 60 // Build and compile our shader program 61 // Vertex shader 62 GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); 63 glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); 64 glCompileShader(vertexShader); 65 // Check for compile time errors 66 GLint success; 67 GLchar infoLog[512]; 68 glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); 69 if (!success) 70 { 71 glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); 72 std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; 73 } 74 // Fragment shader 75 GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); 76 glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); 77 glCompileShader(fragmentShader); 78 // Check for compile time errors 79 glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); 80 if (!success) 81 { 82 glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); 83 std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; 84 } 85 // Link shaders 86 GLuint shaderProgram = glCreateProgram(); 87 glAttachShader(shaderProgram, vertexShader); 88 glAttachShader(shaderProgram, fragmentShader); 89 glLinkProgram(shaderProgram); 90 // Check for linking errors 91 glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); 92 if (!success) { 93 glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); 94 std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl; 95 } 96 glDeleteShader(vertexShader); 97 glDeleteShader(fragmentShader); 98 99 100 // Set up vertex data (and buffer(s)) and attribute pointers 101 // We add a new set of vertices to form a second triangle (a total of 6 vertices); the vertex attribute configuration remains the same (still one 3-float position vector per vertex) 102 GLfloat firstTriangle[] = { 103 -0.9f, -0.5f, 0.0f, // Left 104 -0.0f, -0.5f, 0.0f, // Right 105 -0.45f, 0.5f, 0.0f, // Top 106 }; 107 GLfloat secondTriangle[] = { 108 0.0f, -0.5f, 0.0f, // Left 109 0.9f, -0.5f, 0.0f, // Right 110 0.45f, 0.5f, 0.0f // Top 111 }; 112 GLuint VBOs[2], VAOs[2]; 113 glGenVertexArrays(2, VAOs); // We can also generate multiple VAOs or buffers at the same time 114 glGenBuffers(2, VBOs); 115 // ================================ 116 // First Triangle setup 117 // =============================== 118 glBindVertexArray(VAOs[0]); 119 glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]); 120 glBufferData(GL_ARRAY_BUFFER, sizeof(firstTriangle), firstTriangle, GL_STATIC_DRAW); 121 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); // Vertex attributes stay the same 122 glEnableVertexAttribArray(0); 123 glBindVertexArray(0); 124 // ================================ 125 // Second Triangle setup 126 // =============================== 127 glBindVertexArray(VAOs[1]); // Note that we bind to a different VAO now 128 glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]); // And a different VBO 129 glBufferData(GL_ARRAY_BUFFER, sizeof(secondTriangle), secondTriangle, GL_STATIC_DRAW); 130 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0); // Because the vertex data is tightly packed we can also specify 0 as the vertex attribute's stride to let OpenGL figure it out. 131 glEnableVertexAttribArray(0); 132 glBindVertexArray(0); 133 134 // Game loop 135 while (!glfwWindowShouldClose(window)) 136 { 137 // Check if any events have been activiated (key pressed, mouse moved etc.) and call corresponding response functions 138 glfwPollEvents(); 139 140 // Render 141 // Clear the colorbuffer 142 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 143 glClear(GL_COLOR_BUFFER_BIT); 144 145 // Activate shader (same shader for both triangles) 146 glUseProgram(shaderProgram); 147 // Draw first triangle using the data from the first VAO 148 glBindVertexArray(VAOs[0]); 149 glDrawArrays(GL_TRIANGLES, 0, 3); 150 // Then we draw the second triangle using the data from the second VAO 151 glBindVertexArray(VAOs[1]); 152 glDrawArrays(GL_TRIANGLES, 0, 3); 153 glBindVertexArray(0); 154 155 // Swap the screen buffers 156 glfwSwapBuffers(window); 157 } 158 // Properly de-allocate all resources once they've outlived their purpose 159 glDeleteVertexArrays(2, VAOs); 160 glDeleteBuffers(2, VBOs); 161 // Terminate GLFW, clearing any resources allocated by GLFW. 162 glfwTerminate(); 163 return 0; 164 } 165 166 // Is called whenever a key is pressed/released via GLFW 167 void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) 168 { 169 if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) 170 glfwSetWindowShouldClose(window, GL_TRUE); 171 }
如上所示,VAO主要是保存了VBO的初始化过程
EBO则也类似。