OpenGL 中的 VBO 究竟是什么?

What exactly is a VBO in OpenGL?

我正在尝试了解 OpenGL 背后的理论,目前正在研究 VBO。

到目前为止我是这样理解的:当我们声明一系列顶点时,比方说 3 个顶点形成一个三角形图元,我们基本上无处存储它们,它们只是在代码中声明。

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

所以,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 的方式是否正确,否则我会感到困惑)

这一切都归结为:当你在“普通”程序中工作时,你所拥有的只是CPU、缓存、寄存器、主内存等

但是,当您处理计算机图形学(和其他领域)时,您希望使用 GPU,因为它对于特定任务来说速度更快。 GPU本身就是一台独立的计算机,有自己的处理器、管线和主存。

这意味着您的程序需要以某种方式将所有数据传输到另一台计算机并告诉另一台计算机如何处理它。这不是一件容易的事,所以 OpenGL 为您简化了事情。因此,它们为您提供了一个抽象 (VBO),表示 GPU 中的顶点缓冲区,以及其他任务的许多其他抽象。然后他们为您提供创建该资源 (glGenBuffers)、用数据填充它 (glBufferData)、“绑定它”以使用它 (glBindBuffer) 等的功能

请记住,这都是为了您的利益而进行的简化。事实上,在下面如何执行所有操作的细节要复杂得多。具有顶点的 VBO 或索引的 IBO 等抽象使得使用它们更容易。

我认为你混淆了很多不同的东西并且有一些困惑,所以我试着按照你提出的顺序来解决其中的大部分问题:

when we declare a series of vertices, let's say 3 vertices that form a triangle primitive, we basically store those nowhere, they're simply declared in code.

没有。如果您“无处”存储数据,那么您就没有数据。另外,您在这里混淆了变量的 declarationdefinitoninitialization。对于顶点数据(与所有其他形式的数据一样),有两种基本策略:

  1. 您将数据存储在某个地方,通常是在一个文件中。直接在源代码中指定它只是意味着它存储在一些二进制文件中,可能是可执行文件本身(或它使用的一些共享库)

  2. 程序通过一些数学公式或更一般的algortihm

    生成数据

方法一和方法二当然可以混用,通常方法二会需要一些参数(它本身需要存储在某处,所以参数只是case 1 次)。

And, through the same VBO we send all that vertex info to the Vertex Shader (which is a bunch of code). Now, the VBO is located in the GPU, so we are basically storing all that info on the GPU's memory when we call the VBO.

OpenGL实际上只是一个规范,完全不知道GPU的存在和VRAM的存在。因此,OpenGL 使用 缓冲对象 (BO) 的 概念 作为一定大小的连续内存块 完全由 GL 实现管理。作为用户,您可以要求 GL 创建或销毁此类 BO,指定它们的大小,并完全控制内容 - 如果您愿意,您可以将 MP3 文件放入 BO(并不是说会有一个很好的用例这个)。

另一方面,GL 实现控制着内存的实际分配位置,以及 GPU 的 GL 实现 实际上具有专用视频内存的设备可以选择将 BO 直接存储在 VRAM 中。 hints like GL_STATIC_DRAW 可以帮助 GL 实现决定在哪里最好放置这样的缓冲区(但该提示系统有些缺陷,现代 GL 中存在更好的替代方案,但我不会在这里讨论)。 GL_STATIC_DRAW 表示您打算指定一次内容并使用可能的时间作为绘图选项的 source - 因此数据不会经常更改(当然不会在每帧甚至更频繁),如果存在这种情况,将其存储在 VRAM 中可能是一个非常好的主意。

Then the Vertex Shader, which is part of the Pipeline Rendering process, "comes" to the GPU's memory, "looks" into the VBO and retrieves all that info.

我认为可以这样说,尽管一些 GPU 有一个专用的“顶点获取”硬件阶段,它实际上读取顶点数据,然后将其馈送到顶点着色器。但这并不是真正重要的一点——顶点着色器需要访问每个顶点的数据,这意味着 GPU 将在顶点着色器执行之前或期间的某个时刻读取该内存(VRAM 或系统内存或其他)。

In other words, the VBO stores the vertex data (triangle vertices)

是的。用作顶点着色器的每个顶点输入(“顶点属性”)源的缓冲区对象称为 顶点缓冲区对象(“VBO”),因此直接从术语的定义。

and sends it to the Vertex Shader.

我不会那样说。一个 BO 只是一块内存,它不会主动 任何事情。它只是一个被动元素:它正在被写入或被读取。就这些了。

// here I declare the VBO 
unsigned int VBO;

不,您是在编程语言的上下文中声明(和定义)一个变量,此变量稍后用于保存缓冲区对象的 名称。在 GL 中,对象 names 只是正整数(因此 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 新缓冲区对象生成 names,因此它将生成 n 以前未使用的缓冲区名称(并将它们标记为已使用)和 returns 通过将它们写入 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. bind-to-use:每当您发出依赖于某些 GL 对象的 GL 调用时,您要使用的对象当前必须绑定到某些(特定,取决于用例)绑定目标(不仅是缓冲对象,还有纹理和其他)。
  2. bind-to_modify:每当你作为用户想要修改某个对象的状态时,你必须先将它绑定到某个绑定目标,并且所有对象状态修改函数不直接将要处理的 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 只是为 GL 缓冲区对象(即实际内存)创建实际的 数据存储,这意味着您指定缓冲区的大小(以及我之前提到的使用提示,你告诉 GL 你打算如何使用内存),它 可选地 允许你 初始化 通过将数据从应用程序的内存复制到缓冲区对象来缓冲。它不关心实际数据和您使用的类型)。

由于你这里使用的是GL_ARRAY_BUFFER作为目标参数,所以这个操作会影响当前绑定为GL_ARRAY_BUFFER的BO。

After doing all that, the VBO now contains all the vertex data within.

基本上是的。

So we tell the VBO, ok now send it to the Vertex Shader.

没有。 GL 使用 Vertex Array Objects (VAOs) 为每个顶点着色器输入属性存储在何处查找数据(在哪个缓冲区对象中,在缓冲区对象内的哪个偏移处)以及如何解释此数据(通过指定数据类型)。

稍后在绘制调用期间,GL 将从缓冲区对象内的相关位置获取数据,正如您在 VAO 中指定的那样。如果这个内存访问是由顶点着色器本身触发的,或者如果有一个专用的顶点获取阶段读取之前的数据并将其转发给顶点着色器 - 或者如果根本没有 GPU - 完全是特定于实现的,none 您的关注点。

And that's the start of the Pipeline, just the beginning.

好吧,取决于你如何看待事物。在传统的基于光栅器的渲染管道中,“顶点获取”或多或少是第一阶段,顶点缓冲区对象将只保存从中获取顶点数据的内存(并且 VAO 告诉它要使用哪个缓冲区对象,以及哪些实际位置,以及如何解释它们)。