六、OpenGL中EBO的使用及本质
文章目录
- 一、什么是顶点索引
- 二、什么是EBO
- 三、EBO使用的完整代码
 
一、什么是顶点索引
OpenGL 中,顶点索引(Vertex Index)用于减少重复的顶点数据,提高绘制效率。其核心概念涉及索引缓冲对象(Index Buffer Object,IBO 或 EBO),用于存储顶点的索引信息。
为什么需要顶点索引?
 在 3D 渲染中,一个物体的多个三角形可能会共享相同的顶点。例如,正方形由两个三角形构成;如果使用 GL_TRIANGLES 绘制,需要 6 个顶点:
float vertices[] = {
    // 位置
    -0.5f,  0.5f, 0.0f,  // 顶点 0
     0.5f,  0.5f, 0.0f,  // 顶点 1
    -0.5f, -0.5f, 0.0f,  // 顶点 2
     0.5f, -0.5f, 0.0f   // 顶点 3
};
unsigned int indices[] = {
    0, 1, 2,  // 第一个三角形
    1, 3, 2   // 第二个三角形
};
这里,顶点 1 和 2 在两个三角形中都出现了,但实际位置相同。如果不使用索引,每个三角形必须重新定义所有顶点,会导致数据冗余。
二、什么是EBO
在OpenGL中,EBO(Element Buffer Object,元素缓冲对象)是一种用于存储顶点索引数据的缓冲对象。它允许你通过索引来引用顶点数组中的顶点,从而避免重复存储相同的顶点数据,提高渲染效率。
1. EBO的作用的作用
 EBO存储的是顶点的索引(Indices),而不是顶点数据本身。通过索引,你可以指定绘制时顶点的使用顺序。例如,绘制一个矩形时,只需要4个顶点,但需要2个三角形(6个顶点索引)来描述。使用EBO可以避免重复存储顶点数据。
示例:绘制一个矩形
- 矩形由两个三角形组成。
- 需要 6 个顶点来绘制 2 个三角形。
如果使用 GL_TRIANGLES 直接绘制:
float vertices[] = {
    -0.5f,  0.5f, 0.0f,  // 顶点 0
     0.5f,  0.5f, 0.0f,  // 顶点 1
    -0.5f, -0.5f, 0.0f,  // 顶点 2
     0.5f, -0.5f, 0.0f   // 顶点 3
};
// 每个三角形需要 3 个顶点,总共 6 个顶点:
float noEBO_vertices[] = {
    -0.5f,  0.5f, 0.0f,  // 三角形 1,顶点 0
     0.5f,  0.5f, 0.0f,  // 三角形 1,顶点 1
    -0.5f, -0.5f, 0.0f,  // 三角形 1,顶点 2
     0.5f,  0.5f, 0.0f,  // 三角形 2,顶点 1
     0.5f, -0.5f, 0.0f,  // 三角形 2,顶点 3
    -0.5f, -0.5f, 0.0f   // 三角形 2,顶点 2
};
可以看到 顶点 1 和 2 被重复存储,造成数据冗余。
如何使用 EBO可以只存储 4 个顶点,并使用索引数组来定义三角形:
unsigned int indices[] = {
    0, 1, 2,  // 第一个三角形
    1, 3, 2   // 第二个三角形
};
2. EBO的工作原理
- 顶点数据存储在VBO(Vertex Buffer Object,顶点缓冲对象)中。
- 索引数据存储在EBO中。
- 绘制时,OpenGL根据EBO中的索引从VBO中获取顶点数据。
3.EBO的使用步骤
- 生成EBO:使用glGenBuffers生成一个缓冲对象。
- 绑定EBO:使用glBindBuffer将EBO绑定到GL_ELEMENT_ARRAY_BUFFER目标。
- 填充数据:使用glBufferData将索引数据上传到EBO。
- 绘制:使用glDrawElements根据EBO中的索引绘制图形。
以下是一个使用EBO的简单示例:
// 顶点数据
GLfloat vertices[] = {
    // 位置             // 颜色
     0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右上角
     0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 右下角
    -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, // 左下角
    -0.5f,  0.5f, 0.0f, 1.0f, 1.0f, 0.0f  // 左上角
};
// 索引数据
GLuint indices[] = {
    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};
// 生成并绑定VAO
GLuint VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// 生成并绑定VBO
GLuint VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 生成并绑定EBO
GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
glEnableVertexAttribArray(1);
// 解绑VAO
glBindVertexArray(0);
// 绘制图形
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
三、EBO使用的完整代码
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include "glad/glad.h"
#include "GLFW/glfw3.h"
GLuint VBO = 0;
GLuint VAO = 0;
GLuint shaderProgram = 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 rend() {
	glUseProgram(shaderProgram);
	glBindVertexArray(VAO);
	//glDrawArrays(GL_TRIANGLES, 0, 3);
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
	glBindVertexArray(0);
	glUseProgram(0);
}
void initModel() {
	GLfloat vertices[] = {
		// 位置(x, y, z)     颜色(r, g, b)
		0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 
		0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 
		-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 
		-0.5f, 0.5f, 0.0f, 0.0f, 0.1f, 1.0f,
	};
	GLuint indices[] =
	{
		0, 1, 3,
		1, 2, 3
	};
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);
	GLuint EBO = 0;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	glGenBuffers(1, &VBO); //获取vbo的index
	glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定vbo的index,给vbo分配显存空间,传输数据
	//告诉shader数据解析格式
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); //顶点位置信息
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(sizeof(float)*3)); //顶点颜色信息
	glEnableVertexAttribArray(0); //启用顶点位置锚点
	glEnableVertexAttribArray(1); //启用顶点颜色锚点
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}
void initShader(const char* _vertexPath, const char* _fragmPath) {
	std::string _vertexCode("");
	std::string _fragCode("");
	std::ifstream _vShaderFile;
	std::ifstream _fShaderFile;
	_vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	_fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
	try {
		_vShaderFile.open(_vertexPath);
		_fShaderFile.open(_fragmPath);
		std::stringstream _vShaderStream, _fShaderStream;
		_vShaderStream << _vShaderFile.rdbuf();
		_fShaderStream << _fShaderFile.rdbuf();
		_vertexCode = _vShaderStream.str();
		_fragCode = _fShaderStream.str();
	} catch (const std::exception&) {
		std::string errStr = "read shader fail";
		std::cout << errStr << std::endl;
	}
	const char* _vShaderStr = _vertexCode.c_str();
	const char* _fShaderStr = _fragCode.c_str();
	//shader的编译链接
	unsigned int _vertexID = 0, _fragID = 0;
	char _inforLog[512] = { 0 };
	int _successFlag = 0;
	
	//编译
	_vertexID = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(_vertexID, 1, &_vShaderStr, NULL);
	glCompileShader(_vertexID);
	glGetShaderiv(_vertexID, GL_COMPILE_STATUS, &_successFlag);
	if (_successFlag) {
		glGetShaderInfoLog(_vertexID, 512, NULL, _inforLog);
		std::string errstr(_inforLog);
		std::cout << _inforLog << std::endl;
	}
	_fragID = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(_fragID, 1, &_fShaderStr, NULL);
	glCompileShader(_fragID);
	glGetShaderiv(_fragID, GL_COMPILE_STATUS, &_successFlag);
	if (_successFlag) {
		glGetShaderInfoLog(_vertexID, 512, NULL, _inforLog);
		std::string errstr(_inforLog);
		std::cout << _inforLog << std::endl;
	}
	//链接
	shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, _vertexID);
	glAttachShader(shaderProgram, _fragID);
	glLinkProgram(shaderProgram);
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &_successFlag);
	if (!_successFlag) {
		glGetProgramInfoLog(shaderProgram, 512, NULL, _inforLog);
		std::string errStr(_inforLog);
		std::cout << _inforLog << std::endl;
	}
	glDeleteShader(_vertexID);
	glDeleteShader(_fragID);
}
int main() {
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	GLFWwindow* window = glfwCreateWindow(800, 600, "OPenGL Core", NULL, NULL);
	if (window == NULL) {
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
		std::cout << "Failed to initialize GLAD" << std::endl;
		return -1;
	}
	glViewport(0, 0, 800, 600);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
	initModel();
	initShader("vertex_shader.vert", "fragment_shader.frag");
	while (!glfwWindowShouldClose(window)) {
		processInput(window);
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		rend();
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	// 删除 VAO 和 VBO
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	// 清理
	glfwTerminate();
	
	return 0;
}
输出结果:
 
