显卡、GPU、显存及OpenGL VBO、VAO、EBO概念及用例 学习

1 篇文章 0 订阅


学习参考:
https://blog.csdn.net/dcrmg/article/details/53556664
https://www.cnblogs.com/yongfengnice/p/8665426.html
https://www.cnblogs.com/BigFeng/p/5117311.html

1.0 显卡与GPU与显存简要概述

在这里插入图片描述
现在的显卡做的比较有科技感,下图是NVIDIA英伟达售价22999的显卡:
在这里插入图片描述

1.1 显卡

显卡(Video card,Graphics card)全称显示接口卡,又称显示适配器,是计算机最基本配置、最重要的配件之一。就像电脑联网需要网卡,主机里的数据要显示在屏幕上就需要显卡。具体来说,显卡接在电脑主板上,它将电脑的数字信号转换成模拟信号让显示器显示出来。原始的显卡一般都是集成在主板上,称作“集成显卡”,只完成最基本的信号输出工作,并不用来处理数据。随着显卡的迅速发展,就出现了GPU的概念,显卡也分为独立显卡和集成显卡。显卡由GPU、显存、电路板,BIOS固件组成。

1.2 GPU

GPU(Graphic Processing Unit)全称图形处理单元,GPU这个概念是由Nvidia公司于1999年提出的。GPU是显卡上的一块芯片,就像CPU是主板上的一块芯片。如果是独立显卡,一般它就焊在显卡的电路板上,位置在显卡的风扇下面。如果是集成显卡,一般GPU就和CPU集成在一起了,这时候它和CPU共用一个风扇和缓存 。 因此GPU实际上就是显卡的核心部件,显卡主要就是靠它来工作的。现在的GPU开发厂家只有2个,一个是AMD(ATI),一个是N’VIDIA英伟达。GPU运算时没有其他CPU那些指令集之类东西干扰,所以专一运算效率更高。GPU本身并不能单独工作,只有配合上附属电路和接口,才能工作。

1.3 显存

显存(Video Memory),也被叫做帧缓存,它的作用是用来存储显卡芯片处理过或者即将提取的渲染数据。如同计算机的内存一样,显存是用来存储要处理的图形信息的部件。如同计算机的内存一样,显存是用来存储要处理的图形信息的部件。在显示屏上看到的画面是由一个个的像素点构成的,而每个像素点都以4至32甚至64位的数据来控制它的亮度和色彩,这些数据必须通过显存来保存,再交由显示芯片和CPU调配,最后把运算结果转化为图形输出到显示器上。

2.0 过时的缓冲对象

2.1 最原始顶点设置(glVertex)

最原始的设置顶点方法,在glBegin和glEnd之间使用。OpenGL3.0已经废弃此方法。每个glVertex与GPU进行一次通信,十分低效

	glBegin(GL_TRIANGLES);
    glVertex(0, 0);
    glVertex(1, 1);
    glVertex(2, 2);
	glEnd();

2.2 显示列表(glCallList)

每个glVertex调用都与GPU进行一次通信,显示列表是收集好所有的顶点,一次性的发送给GPU。缺点是在绘制之前就要把要传给GPU的顶点准备好,传后就不能修改了。

	GLuint glassList;
	glNewList(glassList, GL_COMPILE);
	DrawGlass();
	glEndList();
	glCallList(glassList); //DrawGlass();

2.3 顶点数组(Vertex Array)

顶点数组也是收集好所有的顶点,一次性发送给GPU。不过数据不是存储于GPU中的,绘制速度上没有显示列表快,优点是可以修改数据。

	#define MEDIUM_STARS   40
	M3DVector2f vMediumStars[MEDIUM_STARS];
	//在这做点vMediumStars的设置
	glVertexPointer(2, GL_FLOAT, 0, vMediumStars);
	glDrawArrays(GL_POINTS, 0, MEDIUM_STARS);

3.0 顶点缓冲对象VBO(Vertex Buffer Object)

3.1 VBO简介

VBO是在显卡存储空间(显存)中开辟出的一块内存缓存区,用于存储顶点的各类属性信息,如顶点坐标,顶点法向量,顶点颜色数据等。在渲染时,可以直接从VBO中取出顶点的各类属性数据,由于VBO在显存而不是在内存中,不需要从CPU传输数据,处理效率更高。它会在显存储存大量顶点。使用VBO的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。

可以开辟很多个VBO,每个VBO在OpenGL中有它的唯一标识ID,这个ID对应着具体的VBO的显存地址,通过这个ID可以对特定的VBO内的数据进行存取操作。

3.2 VBO的使用

创建VBO的第一步需要开辟显存空间并分配VBO的ID:

	//创建vertex buffer object对象
	GLuint vboId;
	glGenBuffers(1, &vboId);

创建的VBO可用来保存不同类型的顶点数据,创建之后需要通过分配的ID绑定VBO,对于同一类型的顶点数据一次只能绑定一个VBO。绑定操作通过glBindBuffer来实现,第一个参数指定绑定的数据类型,如下:

	//GL_ARRAY_BUFFER
	//GL_ELEMENT_ARRAY_BUFFER
	//GL_PIXEL_PACK_BUFFER
	//GL_PIXEL_UNPACK_BUFFER
	glBindBuffer(GL_ARRAY_BUFFER, vboId);

调用glBufferData把用户定义的数据传输到当前绑定的显存缓冲区中:

	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

调用glVertexAttribPointer通知OpenGL如何解释这些顶点数据,这个接口默认是关闭状态,需要使用glEnableVertexAttribArray进行打开:

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);

参数说明:

  1. 顶点属性位置,与顶点着色器中layout(location=0)对应。
  2. 顶点属性大小顶点属性是一个vec4,它由4个值组成,所以大小是4。
  3. 数据类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
  4. 定义是否希望数据被标准化。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
  5. 步长(Stride),指定在连续的顶点属性之间的间隔。它告诉我们在连续的顶点属性组之间的间隔(多少字节数)。例如知道步长间隔12个字节,可以把步长设置为3 * sizeof(float)。要注意的是由于我们知道这个数组是紧密排列的(在两个顶点属性之间没有空隙)我们也可以设置为0来让OpenGL决定具体步长是多少(只有当数值是紧密排列时才可用)。一旦我们有更多的顶点属性,我们就必须更小心地定义每个顶点属性之间的间隔。
  6. 表示我们的位置数据在缓冲区起始位置的偏移量。参数的类型是void*。
    图解:
    在这里插入图片描述

4.0 顶点数组对象VAO(Vertex Array Object)

4.1 VAO简介

VAO是一个保存了所有顶点数据属性的状态结合,它存储了顶点数据的格式以及顶点数据所需的VBO对象的引用。VAO本身并没有存储顶点的相关属性数据,这些信息是存储在VBO中的,VAO相当于是对很多个VBO的引用,把一些VBO组合在一起作为一个对象统一管理。当一个VAO被创建绑定之后,任何随后的顶点属性调用都会储存在这个VAO中

VBO将顶点信息放到显存中,GPU在渲染时去缓存中取数据,二者中间的桥梁是GL-Context。GL-Context整个程序一般只有一个,所以如果一个渲染流程里有两份不同的绘制代码,GL-context就负责在他们之间进行切换。这也是为什么要在渲染过程中,在每份绘制代码之中会有glBindbuffer、glEnableVertexAttribArray、glVertexAttribPointer。那么优化的方法来了,把这些都放到初始化时候完成吧!VAO记录该次绘制所需要的所有VBO所需信息,把它保存到VBO特定位置,绘制的时候直接在这个位置取信息绘制。

VAO缓存glVertexAttribPointer()函数的结果。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。

VAO记录的是一次绘制中所需要的信息,这包括:

  • glBindBuffer:数据位置
  • glVertexAttribPointer:OpenGL怎样解释数据格式
  • glEnableVertexAttribArray:shader-attribute的location的启用
    在这里插入图片描述

4.2 VAO的使用

生成一个VAO对象并绑定:

	GLuint vaoId;
	glGenVertexArrays(1, &vaoId);// 创建
	glBindVertexArray(vaoId);// 绑定激活

OpenGL中所有的图形都是通过分解成三角形的方式进行绘制,glDrawArrays函数负责把模型绘制出来,它使用当前激活的着色器,当前VAO对象中的VBO顶点数据和属性配置来绘制出来基本图形。

	glDrawArrays (GLenum mode, GLint first, GLsizei count);

参数说明:

  • 第一个参数
    • GL_TRIANGLES:每三个顶之间绘制三角形,之间不连接
    • GL_TRIANGLE_FAN:以V0V1V2,V0V2V3,V0V3V4,……的形式绘制三角形
    • GL_TRIANGLE_STRIP:顺序在每三个顶点之间均绘制三角形。这个方法可以保证从相同的方向上所有三角形均被绘制。以V0V1V2,V1V2V3,V2V3V4……的形式绘制三角形
  • 第二个参数
    定义从缓存中的哪一位开始绘制,一般定义为0
  • 第三个参数
    定义绘制的顶点数量

5.0 索引缓冲对象EBO(Element Buffer Object)

5.1 EBO简介

EBO也是一个缓冲,它专门储存索引,索引的意义在于减少重复数据,OpenGL调用这些顶点的索引来决定该绘制哪个顶点。EBO也不是必须的,如果使用EBO,绘制过程将更清晰简单,EBO需配合VBO使用,索引必须指定索引的对象。

使用EBO绘图是使用GLDrawElements()函数,这个函数是要通过索引到相应的顶点缓冲区内去拿数据,如果绑定了VAO就到VAO里拿数据。那EBO要不要每次绘制的时候都重新绑定,这个就要看顶点数据部分如何处理的了。如果你没有使用VAO,那么就要每次都重新绑定相应的EBO,然后先读取索引,再根据索引去到绑定的VBO里寻找数据。如果已经绑定了VAO,要注意一点:VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象,绑定VAO的同时也会自动绑定EBO。看下面这张图,索引缓冲对象会成为VAO的一个元素。
在这里插入图片描述

5.2 EBO的使用

创建EBO并绑定,用glBufferData(以GL_ELEMENT_ARRAY_BUFFER为参数)把索引存储到EBO中:

    GLuint ebo;
	glGenBuffers(1, &ebo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

当用EBO绑定顶点索引的方式绘制模型时,需要使用glDrawElements而不是glDrawArrays:

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

参数说明:

  1. 绘制模式
  2. 指定要绘制的顶点个数
  3. 索引的数据类型
  4. 可选的EBO中偏移量设定

6. VBO,VAO使用案例

//使用VAO VBO绘制矩形
#include <GL/glew.h>  
#include <GL/freeglut.h>  
 
void userInit();  //自定义初始化
void reshape(int w, int h);   //重绘
void display(void);
void keyboardAction(unsigned char key, int x, int y);   //键盘退出事件
 
GLuint vboId;//vertex buffer object句柄    
GLuint vaoId;//vertext array object句柄    
GLuint programId;//shader program 句柄    
 
int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(512, 512);
	glutCreateWindow("Rectangle demo");
 
	//使用glew,需要执行glewInit,不然运行过程会报错
	//glewInit要放在glut完成了基本的初始化之后执行
	glewInit();
 
	//自定义初始化,生成VAO,VBO对象
	userInit();
 
	//重绘函数
	glutReshapeFunc(reshape);
 
	glutDisplayFunc(display);
 
	//注册键盘按键退出事件
	glutKeyboardFunc(keyboardAction);
 
	glutMainLoop();
	return 0;
}
 
//自定义初始化函数    
void userInit()
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	//创建顶点数据    
	const GLfloat vertices[] = {
		-0.5f,-0.5f,0.0f,1.0f,
		0.5f,-0.5f,0.0f,1.0f,
		0.5f,0.5f,0.0f,1.0f,
		-0.5f,0.5f,0.0f,1.0f,
	};
 
	//创建VAO对象
	glGenVertexArrays(1, &vaoId);
	glBindVertexArray(vaoId);
 
	//创建VBO对象	
	glGenBuffers(1, &vboId);
	glBindBuffer(GL_ARRAY_BUFFER, vboId);
	//传入VBO数据
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	//解除VBO绑定
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}
 
//调整窗口大小回调函数    
void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}
 
//绘制回调函数    
void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT);
 
	//绑定VBO
	glBindBuffer(GL_ARRAY_BUFFER, vboId);
	glEnableVertexAttribArray(0);
 
	//解释顶点数据方式
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
 
	//绘制模型
	glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glDisableVertexAttribArray(0);
 
	glutSwapBuffers();
}
 
//键盘按键回调函数    
void keyboardAction(unsigned char key, int x, int y)
{
	switch (key)
	{
	case 033:  // Escape key    
		exit(EXIT_SUCCESS);
		break;
	}
}

执行效果:
在这里插入图片描述

7. EBO使用案例

使用EBO绘制两个三角形,组成同样的矩形图形:


//使用EBO绘制矩形(两个三角形)
#include <GL/glew.h>  
#include <GL/freeglut.h>  
 
void userInit();  //自定义初始化
void reshape(int w, int h);   //重绘
void display(void);
void keyboardAction(unsigned char key, int x, int y);   //键盘退出事件
 
GLuint eboId;//element buffer object句柄    
GLuint vboId;//vertext buffer object句柄    
GLuint vaoId;//vertext array object句柄    
 
int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(512, 512);
	glutCreateWindow("Rectangle demo");
 
	//使用glew,需要执行glewInit,不然运行过程会报错
	//glewInit要放在glut完成了基本的初始化之后执行
	glewInit();
 
	//自定义初始化,生成VAO,VBO,EBO
	userInit();
 
	//重绘函数
	glutReshapeFunc(reshape);
	glutDisplayFunc(display);
	//注册键盘按键退出事件
	glutKeyboardFunc(keyboardAction);
	glutMainLoop();
	return 0;
}
 
//自定义初始化函数    
void userInit()
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
 
	//创建顶点数据    
	const GLfloat vertices[] = {
		-0.5f,-0.5f,0.0f,1.0f,
		0.5f,-0.5f,0.0f,1.0f,
		0.5f,0.5f,0.0f,1.0f,
		-0.5f,0.5f,0.0f,1.0f,
	};
	// 索引数据
	GLshort indices[] = {
		0, 1, 3,  // 第一个三角形
		1, 2, 3   // 第二个三角形
	};
 
	//创建VAO对象
	glGenVertexArrays(1, &vaoId);
	glBindVertexArray(vaoId);
 
	//创建VBO对象,把顶点数组复制到一个顶点缓冲中,供OpenGL使用
	glGenBuffers(1, &vboId);
	glBindBuffer(GL_ARRAY_BUFFER, vboId);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
 
	//创建EBO对象	
	glGenBuffers(1, &eboId);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
	//传入EBO数据
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
 
	//解释顶点数据方式
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(0);
 
	//解绑VAO
	glBindVertexArray(0);
	//解绑EBO
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	//解绑VBO
	glBindBuffer(GL_ARRAY_BUFFER, 0);
}
 
//调整窗口大小回调函数    
void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}
 
//绘制回调函数    
void display(void)
{
	glClear(GL_COLOR_BUFFER_BIT);
	//绑定VAO
	glBindVertexArray(vaoId);
	//绘制模型
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL);
	glutSwapBuffers();
}
 
//键盘按键回调函数    
void keyboardAction(unsigned char key, int x, int y)
{
	switch (key)
	{
	case 033:  // Escape key    
		exit(EXIT_SUCCESS);
		break;
	}
}

效果一样:
在这里插入图片描述

8. 总结

从程序传入的数据都是要放入缓冲对象的,也就是Buffer Object,像VBO和EBO,而VAO则可以看作是把这些数据进行了分类整合,根据缓冲对象里的数据进行自由配置,从而得到自己需要的数据,然后放入一个对象里。比如我从一堆顶点缓冲和索引缓冲里拿出一部分数据组成正方体,然后给它一个VAO的名字,下次用的时候直接取出来就行,我还可以拿出一些数据组成四面体,然后再给它一个名字。这样看来,VBO和EBO里的数据就好像是可以重复使用的原材料,VAO则是使用这些原材料进行加工好了的对象。

VBO:CPU把顶点数据存储到VBO,然后每次调用的时候从VBO集中发送到GPU,能够大大降低cpu到gpu的交互次数。EBO:主要是为了减少VBO中的重复数据,但是它不能减少数据的传输,如果一个正方体有36个顶点,用索引的话就要有36个索引值,但是不同的索引可能索引到同一个顶点。

VAO:VAO则是用来保存顶点数据和配置的,没有VAO的时候,我们需要不停地把配置从CPU传到GPU,以此让GPU来知道怎么取顶点,而有了VAO(VAO是放在显存里的),我们通过绑定的VAO可以很快拿到配置,而不用从CPU程序中传入。

数据传递流程:我们定义了顶点数组以后,需要将数据载入缓存对象也就是VBO中,同时我们必须告诉OpenGL如何配置这些数据,这就需要我们绑定一个VAO,这里面存储着相应的配置,同时VAO也与相应的顶点数组做了间接绑定,所以一般来说;一个顶点数组可以对应着多个VAO,但是一个VAO一般对应一个顶点数组。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我来为你解答。 1. OpenGL图形渲染管线 OpenGL图形渲染管线是OpenGL对图形数据进行处理的流程。它包括两个主要部分:顶点处理阶段和片元处理阶段。其中,顶点处理阶段将顶点数据转换成片元数据,片元处理阶段将片元数据转换成最终的图像颜色值。 2. VBO VBO(Vertex Buffer Object)是OpenGL中的一种缓冲区对象,它用于存储顶点数据。使用VBO可以将顶点数据存储到显存中,从而提高渲染效率。 3. VAO VAO(Vertex Array Object)是OpenGL中的另一种缓冲区对象,它用于存储顶点数组状态。使用VAO可以将多个VBO绑定到一个VAO中,从而方便在不同的渲染环境中切换。 4. EBO EBO(Element Buffer Object)也是OpenGL中的一种缓冲区对象,它用于存储顶点索引数据。使用EBO可以减少顶点数据的冗余,从而节省显存空间。 下面是一个简单的用例,演示如何使用VBOVAOEBO进行三角形的渲染: ```c++ // 顶点数据 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 }; // 索引数据 unsigned int indices[] = { 0, 1, 2 }; // 创建VBOVAOEBO对象 unsigned int VBO, VAO, EBO; glGenBuffers(1, &VBO); glGenVertexArrays(1, &VAO); glGenBuffers(1, &EBO); // 绑定VBOVAOEBO对象 glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 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(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); // 渲染三角形 glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0); glBindVertexArray(0); // 删除VBOVAOEBO对象 glDeleteBuffers(1, &VBO); glDeleteBuffers(1, &EBO); glDeleteVertexArrays(1, &VAO); ``` 上面的代码首先创建了一个顶点数组和一个索引数组,然后创建了VBOVAOEBO对象,并将顶点数据和索引数据存储到对应的缓冲区对象中。接着,设置了顶点属性指针,并在渲染时使用glDrawElements函数渲染了三角形。最后,删除了VBOVAOEBO对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值