OpenGL中的VBO是什么?

4

我正在尝试理解OpenGL背后的理论,目前正在学习VBO。

目前我理解的是:当我们声明一系列顶点时,比如说三个形成三角形图元的顶点,它们实际上并没有被存储在某个地方,只是在代码中被声明。

但是,如果我们想要将它们存储在某个地方,我们可以使用一个VBO来存储这些顶点的定义。通过同一个VBO,我们将所有顶点信息发送到顶点着色器(它是一堆代码)。现在,VBO位于GPU中,因此当我们调用VBO时,我们实际上是将所有信息存储在GPU的内存中。然后,顶点着色器作为渲染管道过程的一部分,“访问”GPU的内存,查找并检索所有这些信息。换句话说,VBO存储顶点数据(三角形顶点),并将其发送到顶点着色器。

因此,VBO -> 发送信息到 -> 顶点着色器。

我的理解正确吗?我问这个问题是为了确保这是正确的解释,因为我发现自己在屏幕上绘制三角形,有时由许多三角形组成的字母,还有一堆代码和函数,我只是靠记忆学习,不太了解它们的作用。

简而言之:

// here I declare the VBO
unsigned int VBO;

// we have 1 VBO, so we generate an ID for it, and that ID is: GL_ARRAY_BUFFER
glGenBuffers(1, &VBO) 

// GL_ARRAY_BUFFER is the VBO's ID, which we are going to bind to the VBO itself
glBindBuffer(GL_ARRAY_BUFFER, VBO)

// bunch of info in here that basically says: I want to take the vertex data (the
// triangle that I declared as a float) and send it to the VBO's ID, which is
// GL_ARRAY_BUFFER, then I want to specify the size of the vertex
// data, the vertex data itself and the 'static draw' thingy
glBufferData(...).

在完成这一切后,VBO现在包含了所有顶点数据。因此,我们告诉VBO:好的,现在将其发送到顶点着色器。
这是管线的开始,只是个开始。
这样正确吗? (我还没有读VAO的功能,在我对此有所了解之前,我想知道我脑海中对VBO进行解构的方式是否正确,否则我会感到困惑)

3
@LuigiIstratescu 一个 VBO(顶点缓冲对象)和其他缓冲对象之间没有区别。但是,当调用 glVertexAttribPointer 时,绑定到目标 GL_ARRAY_BUFFER 的缓冲与指定的顶点属性相关联。请参阅顶点数组对象 - Rabbid76
嘿,rabbid,我认为缓冲对象是GPU上的内存位置,而VBO是具有数据的相同内存位置。在我的帖子中,我想知道的是,如果我理解了实际代码中的VBO工作原理,你认为我是否理解了它?或者还有什么我不明白的地方吗? - Luigi Istratescu
2
@LuigiIstratescu 是的,缓冲对象数据存储在GPU上的内存中。不,"VBO"就是一个缓冲对象。"顶点缓冲对象"只是意味着该缓冲区用于顶点属性。但它并没有什么特别之处。 - Rabbid76
1
@LuigiIstratescu:“我们基本上不会将它们存储在任何地方,它们只是在代码中声明而已。”你认为在代码中声明的变量不会被存储在某个地方吗? - Nicol Bolas
嘿,Nicol Bolas,也许它们会被存储在某个地方,但那并不关我的事。我想知道的是,我是否理解了VBO的工作原理,或者还有什么是我理解错误的。 - Luigi Istratescu
显示剩余3条评论
2个回答

13
我认为你把很多不同的东西混在一起并有几个混淆点,所以我将按照你提出的顺序尝试解决大部分问题:
当我们声明一系列顶点时,假设是形成三角形图元的3个顶点,我们基本上没有存储它们在任何地方,它们只是在代码中声明。
不是这样的。如果你把数据“存储在无处”,那么你就没有它。此外,你在这里混淆了变量的声明、定义和初始化。对于顶点数据(像所有其他形式的数据一样),有两种基本策略:
1.你将数据存储在某个地方,通常是在一个文件中。直接在源代码中指定它只意味着它存储在某个二进制文件中,可能是可执行文件本身(或者某个由其使用的共享库)。
2.通过某些数学公式或更一般的算法来生成数据。
当然,方法1和2可以混合使用,通常,方法2需要一些参数(它们本身需要存储在某个地方,因此参数只是情况1)。
同时,我们通过同一个VBO将所有顶点信息发送到顶点着色器(它是一堆代码)。现在,VBO位于GPU上,因此当我们调用VBO时,我们基本上是将所有信息存储在GPU的内存中。
实际上OpenGL只是一个规范,完全不考虑GPU或VRAM的存在。因此,OpenGL使用缓冲对象(BO)的概念,作为某些大小确定的连续内存块,在GL实现中完全由其管理。你作为用户可以要求GL创建或销毁这种BO,指定它们的大小,并完全控制它们的内容-如果愿意,你可以把MP3文件放入BO中(尽管没有很好的用例)。
另一方面,GL实现控制实际分配这个内存的位置,对于有专门的显存的GPU的GL实现,可以直接将BO存储在VRAM中。像GL_STATIC_DRAW这样的提示有助于GL实现决定最佳放置这样的缓冲区的位置(但该提示系统存在某些缺陷,在现代GL中存在更好的替代方法,但我不会在这里讨论)。GL_STATIC_DRAW意味着你打算指定内容一次并多次使用它作为绘图选项的源-因此数据不会经常更改(甚至不会每帧或更频繁地更改),如果这样的东西存在,则在VRAM中存储它可能是一个非常好的主意。
然后在渲染流程中的顶点着色器阶段,“来到”GPU的内存,查看VBO并检索所有信息。有些GPU有专门的“顶点获取”硬件阶段,实际上读取顶点数据,然后将其提供给顶点着色器。但这不是一个非常重要的点-顶点着色器需要访问每个顶点的数据,这意味着GPU会在顶点着色器执行之前或之时读取该内存(VRAM或系统内存或其他)。
换句话说,VBO存储顶点数据(三角形顶点)。
是的,用作顶点着色器每个顶点输入(“顶点属性”)源的缓冲对象称为“顶点缓冲对象”(VBO),因此这直接遵循术语的定义。
我不会这么说。BO只是一块内存,它没有积极地“做”任何事情。它只是一个被动元素:正在被写入或正在被读取。这是全部内容。
// here I declare the VBO 
unsigned int VBO;
不,你是在程序语言的上下文中声明(并定义)一个变量,这个变量稍后用于保存缓冲对象的名称。在OpenGL中,对象名称只是正整数(因此0被保留为GL的“无对象”或“默认对象”,具体取决于对象类型)。
// we have 1 VBO, so we generate an ID for it, and that ID is: GL_ARRAY_BUFFER 
glGenBuffers(1, &VBO) 
glGenBuffers(n,ptr)只是为n个新的缓冲对象生成名称,它会生成n个之前未使用的缓冲名称(并标记它们已被使用),并通过将它们写入指向ptr的数组来返回它们。因此,在这种情况下,它只创建一个新的缓冲对象名称,并将其存储在您的VBO变量中。 GL_ARRAY_BUFFER与此无关。
// GL_ARRAY_BUFFER is the VBO's ID, which we are going to bind to the VBO itself 
glBindBuffer(GL_ARRAY_BUFFER, VBO)
不,GL_ARRAY_BUFFER 不是 VBO 的 ID,你的 VBO 变量的值才是 VBO 的 ID(名称!)。 GL_ARRAY_BUFFER 是一个“绑定目标”。OpenGL 缓冲区对象可以用于不同的用途,将它们用作顶点数据源只是其中之一,而GL_ARRAY_BUFFER 就是指该用途。
请注意,经典 OpenGL 在两个方面使用了“绑定”的概念:
1. “绑定以使用”:每当您发出依赖于某些 GL 对象的 GL 调用时,您要使用的对象必须当前绑定到某个特定的绑定目标上(不仅限于缓冲区对象,也包括纹理和其他对象)。 2. “绑定以修改”:每当您作为用户想要修改某个对象的状态时,您必须首先将其绑定到某个绑定目标上,并且所有对象状态修改函数不直接以 GL 对象的名称作为参数,而是以绑定目标作为参数,并且将影响当前绑定在该目标上的对象。(现代 GL 还有“直接状态访问”,允许您在不必先绑定对象的情况下修改它们的状态,但我在这里也不详细介绍。)
将缓冲区对象绑定到某些缓冲区对象绑定目标意味着可以将该对象用于目标定义的用途。但请注意,缓冲区对象不会因为绑定到目标而发生变化。您可以同时将缓冲区对象绑定到不同的目标上。GL 缓冲区对象没有类型。将缓冲区称为“VBO”通常只意味着您打算将其用作GL_ARRAY_BUFFER,但 GL 并不关心这一点。它确实关心在调用glVertexAttribPointer() 时缓冲区绑定为GL_ARRAY_BUFFER 的内容。
// bunch of info in here that basically says: I want to take the vertex data (the 
// triangle that I declared as a float) and send it to the VBO's ID, which is 
// GL_ARRAY_BUFFER, then I want to specify the size of the vertex 
// data, the vertex data itself and the 'static draw' thingy
glBufferData(...).

glBufferData函数是用来创建OpenGL缓冲区对象的实际数据存储(即真正的内存),你需要指定缓冲区的大小和用途,还可以选择将应用程序内存中的数据复制到缓冲区对象中进行初始化。它不关心数据本身或使用的类型。

使用GL_ARRAY_BUFFER作为目标参数,此操作将影响当前绑定为GL_ARRAY_BUFFER的缓冲区对象。

完成上述操作后,VBO现在包含了所有的顶点数据。

基本上是这样。

然后我们告诉VBO将其发送到顶点着色器。

不对。OpenGL使用顶点数组对象(VAO)存储每个顶点着色器输入属性的数据来源(在哪个缓冲区对象中,以及在缓冲区对象内的偏移量)和如何解释这些数据(通过指定数据类型)。在后续的绘制调用中,OpenGL会根据VAO指定的位置从相应的缓冲区对象中获取数据。这个内存访问是由顶点着色器自己触发的,还是由专门的顶点提取阶段读取数据并将其转发给顶点着色器,或者是否完全由GPU处理,这完全取决于实现方式,与你无关。

这就是管线的开始,仅仅是个开端。

这要看你怎么看。在传统的光栅化渲染管线中,“顶点提取”更或多或少地是第一个阶段,而顶点缓冲区对象只会保存从哪里获取顶点数据(VAO指定使用的缓冲区对象、位置和解析方式)的内存。


所以,基本上我现在理解的是GL_ARRAY_BUFFER只是VBO的绑定目标,而不是ID。而VBO的ID是变量的值,它是无符号整数,也就是VBO本身。抱歉,我知道这听起来很混乱...但我这样做是为了让自己理解它。 - Luigi Istratescu
这里有很多需要理解的内容,所以我会将它复制粘贴到一个Word文档中,以便在有疑问时重新阅读。真希望我能像你一样理解这些东西! - Luigi Istratescu

0
一切归结于这样的事实:当您在“正常”程序中工作时,所拥有的只有CPU、缓存、寄存器、主内存等。然而,在处理计算机图形(以及其他领域)时,您需要使用GPU,因为它对于这个特定的任务来说更快。GPU是一个独立的计算机,具有自己的处理器、管道和主要甚至是内存。
这意味着您的程序需要将所有数据传输到另一台计算机,并告诉另一台计算机如何处理它。这并不是一件容易的事情,因此OpenGL为您简化了这些问题。因此,他们给您提供了一个抽象(VBO),它代表着GPU中的顶点缓冲区,以及许多其他任务的抽象。然后,他们给您提供函数来创建该资源(glGenBuffers)、用数据填充它(glBufferData)、“绑定”它以便使用它(glBindBuffer)等。
请记住,这一切都是为了简化您的使用。事实上,所有东西在底层执行的细节远远比这复杂得多。使用诸如VBO(顶点缓冲对象)或IBO(索引缓冲对象)之类的抽象使得与它们一起工作更加容易。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接