关于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_POINTSGL_TRIANGLESGL_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则也类似。

posted on 2018-09-06 22:54  mimido  阅读(3277)  评论(0编辑  收藏  举报

导航