OpenGL学习笔记一——绘制两个三角形,理解VAO,VBO和EBO

基础概念

顶点数据

我们在创建一个最基本的几何体时必须要告诉GPU要渲染的几何体的基本信息,包括顶点的坐标,颜色,法线,贴图等等,这些信息全部保存至数组当中,在使用的时候需要将这些数据传入顶点着色器。示例如下:

	//顶点信息
	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   // 左上角
	};

两个三角形,有两个公共顶点,这时用到EBO

	float vertices[] = {
		// positions          // texture coords
		 0.5f,  0.5f, 0.0f,   1.0f, 1.0f, // top right
		 0.5f, -0.5f, 0.0f,   1.0f, 0.0f, // bottom right
		-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, // bottom left
		-0.5f,  0.5f, 0.0f,   0.0f, 1.0f  // top left 
	};

带有颜色信息

float 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.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f    // 顶部
};

VAO,VBO及EBO

  • 顶点数组对象:Vertex Array Object,VAO

  • 顶点缓冲对象:Vertex Buffer Object,VBO

  • 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO

使用说明
顶点数组对象即为顶点数据,顶点缓冲对象用来管理顶点数据的,需要VBO存在的原因如下:
我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。简而言之就是为了通过它加快数据传输速度。
索引缓冲对象存在的原因是当顶点数据发生重合时会造成不必要的内存浪费,而当引入索引缓冲对象时就可以大量减少这类空间的浪费,节约内存。
例如,当我们要绘制两个三角形,但是这两个三角形有公共顶点时:

	//顶点信息
	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,4行
		1, 2, 3  // 第二个三角形,对应数组的第2,3,4行
	};

使用indices数组就是索引缓冲对象,减少了两组顶点数据的空间,当有大量三角形时,这个节省的空间将非常巨大,占用更少的显存。

顶点着色器及片元着色器

要想理解着色器是什么,就必须理解渲染管线,之前我的博文有相关介绍:
渲染管线

  • 顶点着色器

顶点着色器接收从CPU传入的数据,就是上面创建的顶点数组数据,经过顶点着色器处理计算之后,会有对应的输出数据作为片元着色器的输入数据,当然在这个传值过程中需要经过图元装配和插值光栅化计算,将顶点坐标转化成对应的像素坐标信息。
在OpenGL中的创建过程如下:

	//VertexShader创建顶点着色器
	int vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);
  • 片元着色器

像素信息作为片元着色器的传入数据做对应的计算后,经过几次测试(模板测试和深度测试等)进入frontBuffer,利用双缓冲技术从frontBuffer传入frameBuffer中,显示到屏幕上。
在OpenGL中的创建过程如下:

	//片元着色器
	int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);

图片显示全部过程:
在这里插入图片描述

创建流程

创建窗口

在创建窗口之前需要配置开发环境,最新的API配置之前有介绍,链接如下:
环境配置
代码及API不用死记硬背,只要理解其作业就行了

glfwInit();//初始化glfw
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//配置glfw,glfwWindowHint函数的第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;第二个参数接受一个整型,用来设置这个选项的值。

创建窗口的核心函数:

GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
    std::cout << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);

设置窗口大小

(OpenGL幕后使用glViewport中定义的位置和宽高进行2D坐标的转换,将OpenGL中的位置坐标转换为你的屏幕坐标。例如,OpenGL中的坐标(-0.5, 0.5)有可能(最终)被映射为屏幕中的坐标(200,450)。注意,处理过的OpenGL坐标范围只为-1到1,因此我们事实上将(-1到1)范围内的坐标映射到(0, 800)和(0, 600)。):

glViewport(0, 0, 800, 600);

分配/释放资源:

glfwTerminate();

创建顶点/片元着色器

顶点着色器

	//VertexShader创建顶点着色器
	int vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);

片元着色器

	//片元着色器
	int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);

创建着色器程序

	int shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);

销毁着色器程序,创建完着色器程序之后便可销毁了

	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

将数据传给顶点着色器

	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

完整代码

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);

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(0.0f, 1f, 0f, 1.0f);\n"
 "}\n\0";
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, "LearnOpenGL", NULL, NULL);
	if (window == NULL) {
		cout << "Failed to create GLFW window" << 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);

	//VertexShader创建顶点着色器
	int vertexShader = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
	glCompileShader(vertexShader);

	//Info获取编译出错信息
	int success;
	char info[512];
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(vertexShader, 512, NULL, info);
		std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << info << std::endl;
	}
	//片元着色器
	int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
	glCompileShader(fragmentShader);
	//编译信息
	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
	if (!success)
	{
		glGetShaderInfoLog(fragmentShader, 512, NULL, info);
		std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << info << std::endl;
	}

	//shaderProgramshader程序
	int shaderProgram = glCreateProgram();
	glAttachShader(shaderProgram, vertexShader);
	glAttachShader(shaderProgram, fragmentShader);
	glLinkProgram(shaderProgram);
	glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
	if (!success)
	{
		glGetProgramInfoLog(shaderProgram, 512, NULL, info);
		std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << info << std::endl;
	}
	//没用了,直接删除
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	//顶点信息
	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  // 第二个三角形
	};
	unsigned int VAO, VBO;
	glGenVertexArrays(1, &VAO);//1代表数组长度为1
	glGenBuffers(1, &VBO);//1代表数组长度为1
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER,VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//索引,管理重复数据
	unsigned int EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
	//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

	//赋值数据到顶点着色器
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

	glEnableVertexAttribArray(0);//传入location值
	//glBindBuffer(GL_ARRAY_BUFFER, 0);    //取消绑定
	//glBindVertexArray(0);                         //取消绑定
// 	int nrAttributes;
// 	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
// 	std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
	while (!glfwWindowShouldClose(window)) {
		glClearColor(1, 0, 0, 1);//设置背景颜色
		glClear(GL_COLOR_BUFFER_BIT);//清除颜色缓存

		//glBindVertexArray(VAO);
		//glDrawArrays(GL_TRIANGLES, 0, 3);

		glUseProgram(shaderProgram);//使用着色器程序
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//当使用EBO索引时使用该函数,当不用索引时使用glDrawArrays
		glfwSwapBuffers(window);
		glfwPollEvents();
	}
	//没用了,销毁
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);
	glfwTerminate();
	return 0;
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
	glViewport(0, 0, width, height);
}

运行截图:
在这里插入图片描述

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: OpenGL中的VAOVBOEBO是三种重要的缓存对象,用于存储和管理顶点数据和索引数据。 VAO(Vertex Array Object)是顶点数组对象,用于存储顶点数据的属性信息,如顶点坐标、颜色、法向量等。VAO可以将多个VBO绑定到同一个VAO中,方便管理和使用。 VBO(Vertex Buffer Object)是顶点缓存对象,用于存储顶点数据的实际数值,如顶点坐标、颜色、法向量等。使用VBO可以提高渲染效率,减少数据传输次数。 EBO(Element Buffer Object)是索引缓存对象,用于存储顶点索引数据,可以减少重复顶点的存储,提高渲染效率。 总之,VAOVBOEBOOpenGL中非常重要的缓存对象,可以提高渲染效率,减少数据传输次数,方便管理和使用。 ### 回答2: OpenGL是一款广泛应用于图形渲染的开源3D图形库。VAOVBOEBO这三个单词是OpenGL中的三种不同类型的缓冲区对象。 VAO,即顶点属性对象,是一个OpenGL缓冲区对象,用于存储和管理顶点属性,即描述一个图元的顶点的各种属性,如顶点坐标、颜色、法线等。VAO可以容纳多个VBO,而且可以把VBO的数据分组合起来,VBO的顶点属性索引可以绑定到VAO中,这样一来,当我们要渲染一个物体时,只需绑定一个VAO对象,就可以一次性调用所有需要的VBOVBO,即顶点缓冲区对象,是一个OpenGL缓冲区对象,用于高效存储顶点数据,如顶点坐标、顶点颜色等。当需要渲染一个物体时,我们可以从VBO中读取需要的顶点信息,然后传递给渲染管线进行处理和渲染。使用VBO有一些显著的优势,比如,VBO能够提升渲染性能,减少CPU和GPU之间的数据传输,同时还可以减少内存占用。 EBO,即索引缓冲区对象,是一种存储一系列索引的OpenGL缓冲区对象。EBO中的索引是用于指定顶点顺序的,即用于描述三角形顶点之间的关系。使用EBO可以大大减少需要传输给GPU的数据量,并且可以避免重复的顶点数据,提升渲染性能。 一般情况下,我们通过VAOVBOEBO来实现复杂图形的绘制。在渲染一个物体时,首先需要创建一个VAO对象,并绑定所有需要使用的VBOEBO。然后将这些缓冲区对象中的数据存储到GPU中,并在渲染时调用这些数据。最后,将VAO对象绑定到渲染管道中,执行图形渲染操作。 ### 回答3: OpenGL是一种图形库,它可以用来渲染三维场景或者二维绘图。VAOVBOEBO都是OpenGL中的重要概念,用于优化渲染性能和提高代码可读性。 VAO(Vertex Array Object)是一个顶点数组对象,它保存了一组顶点数据的状态。每个顶点包含诸如位置、法向量、纹理坐标等属性。VAO可以将这些属性打包成一个容器,使得OpenGL可以更快地访问和处理数据。VAO通常包括各种属性缓冲对象(VBO),它们保存了不同类型的属性数据。 VBO(Vertex Buffer Object)是OpenGL中的缓冲对象,用于存储顶点数据。VBO可以存储各种顶点属性,如位置、法向量、颜色等。VBO可以在GPU内存中创建,并且可以被透明地映射到CPU内存。这意味着我们可以直接在CPU内存中修改VBO的数据,而不必将其复制到GPU中。 EBO(Element Buffer Object)是一个索引缓冲对象,它充当索引数组的角色。EBO中存储的是顶点的索引,这些索引对应于VBO中存储的实际顶点数据。使用EBO可以减少顶点数据的冗余性。例如,在多个三角形之间共享相同的顶点数据时,可以使用EBO来避免重复数据的存储。 总之,VAOVBOEBOOpenGL中的重要工具,它们可以大大提高渲染性能和代码可读性。VAO提供了一种整理顶点属性的方法,VBO可以高效地管理顶点数据,而EBO可以减少数据冗余。对于OpenGL编程来说,深入了解VAOVBOEBO是非常重要的。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值