OpenGL 中的 VBO 究竟是什么?

问题描述

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

这就是我目前的理解:当我们声明一系列顶点时,假设有 3 个顶点形成一个三角形基元,我们基本上没有存储这些顶点,它们只是在代码中声明。

但是,如果我们想将它们存储在某个地方,我们可以使用一个 VBO 来存储这些顶点的定义。而且,通过同一个 VBO,我们将所有顶点信息发送到顶点着色器(这是一堆代码)。现在,VBO 位于 GPU 中,因此当我们调用 VBO 时,我们基本上将所有这些信息存储在 GPU 的内存中。然后作为流水线渲染过程的一部分的顶点着色器“来到”GPU 的内存,“查看”VBO 并检索所有信息。换句话说,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 个顶点形成一个三角形图元,我们基本上不会将它们存储在任何地方,它们只是在代码中声明。

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

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

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

    生成数据

方法1.和2当然可以混合使用,通常方法2需要一些参数(这些参数本身需要存储在某个地方,所以参数又只是case 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;

不,您是在编程语言的上下文中声明(并定义)一个变量,并且此变量稍后用于保存缓冲区对象的名称。在 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 以前未使用的缓冲区名称(并将它们标记为已使用)并通过写入返回它们到 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 调用时,您要使用的对象当前必须绑定到某些(具体取决于使用情况) case) 绑定目标(不仅是缓冲区对象,还有纹理等)。
  2. bind-to_modify:当你作为用户想要修改某个对象的状态时,你必须先将它绑定到某个绑定目标,并且所有的对象状态修改函数都不会直接将要处理的 GL 对象的名称作为参数,但绑定目标将影响当前绑定在该目标上的对象。 (现代 GL 还具有直接状态访问,它允许您修改对象而无需先绑定它们,但我也不会在此处详细介绍)。

将缓冲区对象绑定到某些缓冲区对象绑定目标意味着您可以将该对象用于目标定义的目的。但请注意,缓冲区对象不会更改,因为它已绑定到目标。您可以甚至同时将一个缓冲区对象绑定到不同的目标。 GL 缓冲区对象没有类型。将缓冲区称为“VBO”通常仅意味着您打算将其用作 GL_ARRAY_BUFFER,但 GL 并不关心。它确实关心在调用 GL_ARRAY_BUFFER 时缓冲区绑定为 glVertexAttribPointer()

// 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。

完成所有这些之后,VBO 现在包含了其中的所有顶点数据。

基本上是的。

所以我们告诉 VBO,现在将它发送到顶点着色器。

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

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

这是流水线的开始,只是开始。

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

,

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

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

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

请记住,这一切都是为了您的利益而进行的简化。事实上,在底层如何执行一切的细节要复杂得多。使用 VBO 表示顶点或 IBO 表示索引等抽象概念,可以更轻松地使用它们。