我正在努力理解OpenGL背后的理论,目前我正在研究VBO。
这就是我到目前为止所理解的:当我们声明一系列顶点时,比如说形成三角形基元的3个顶点,我们基本上把它们存储在任何地方,它们只是在代码中声明的。
但是,如果我们想把它们存储在某个地方,我们可以使用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,好的,现在把它发送到顶点着色器。
这就是Pipeline的开始,而不是开始。
这是正确的吗?(我还没有读过VAO的作用,在我读到之前,我想知道我在脑海中解构VBO的方式是否正确,否则我会感到困惑)
我认为你混淆了很多不同的东西,有几个困惑,所以我试着按照你提出的顺序来处理其中的大部分:
当我们声明一系列顶点时,比如说形成三角形基元的3个顶点,我们基本上把它们存储在任何地方,它们只是在代码中声明的。
否。如果存储数据";无处";,那么你就没有了。此外,你在这里混淆了变量的声明、定义和初始化。对于顶点数据(像所有其他形式的数据一样),有两种基本策略:
-
您将数据存储在某个位置,通常存储在文件中。直接在源代码中指定它意味着它存储在某个二进制文件中,可能是可执行文件本身(或它使用的某个共享库)
-
您程序性地通过一些数学公式或更通用的算法生成数据
方法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
意味着您打算指定一次内容,并使用may times作为绘图选项的源,这样数据就不会经常更改(当然也不会以每帧为单位,甚至更频繁),如果存在这种情况,将其存储在VRAM中可能是一个非常好的主意。
然后,作为管道渲染过程一部分的顶点着色器;来了";到GPU的存储器;外观;并检索所有这些信息。
我认为可以这样说,尽管有些GPU有一个专用的";顶点提取";硬件阶段,它实际读取顶点数据,然后将其提供给顶点着色器。但这并不是一个真正重要的点——顶点着色器需要访问每个顶点的数据,这意味着GPU将在顶点着色器执行之前或执行期间的某个点读取该内存(VRAM或系统内存或其他)。
换句话说,VBO存储顶点数据(三角形顶点)
是。用作顶点着色器的逐顶点输入("顶点属性")的源的缓冲区对象被称为顶点缓冲区对象("VBO"),因此直接遵循术语的定义。
并将其发送到顶点着色器。
我不会这么说的。BO只是一块内存,它不会主动做任何事情。它只是一个被动的元素:它被写入或读取。仅此而已。
// here I declare the VBO unsigned int VBO;
不,您在编程语言的上下文中声明(和定义)一个变量,该变量稍后用于保存缓冲区对象的名称。在GL中,对象名称只是正整数(因此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的标识(名称!)。GL_ARRAY_BUFFER
是的结合靶标。OpenGL缓冲区对象可以用于不同的目的,使用它们作为顶点数据的源只是其中之一,GL_ARRAY_BUFFER
指的是这种用例。
请注意,经典OpenGL使用绑定的概念有两个目的:
- 绑定使用:每当您发出依赖于某些GL对象的GL调用时,您要处理的对象必须当前绑定到某些(特定的,取决于用例)绑定目标(不仅是缓冲区对象,还有纹理和其他)
- bind-to_modify:当用户想要修改某个对象的状态时,必须首先将其绑定到某个绑定目标,并且所有的对象状态修改函数都不会直接将要处理的GL对象的名称作为参数,而是将绑定目标作为参数,并且会影响当前绑定在该目标的对象。(现代GL也有直接状态访问,这允许您修改对象,而不必首先绑定它们,但我在这里也不详细介绍)
将缓冲区对象绑定到某些缓冲区对象的绑定目标意味着您可以将该对象用于目标定义的目的。但请注意,缓冲区对象不会更改,因为它绑定到了目标。您可以将缓冲区对象绑定到不同的目标,甚至可以同时绑定。GL缓冲区对象没有类型。调用缓冲区a";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。
完成所有这些之后,VBO现在包含其中的所有顶点数据。
基本上是的。
所以我们告诉VBO,现在可以将其发送到顶点着色器。
否。GL使用顶点阵列对象(VAO),它为每个顶点着色器输入属性存储在哪里查找数据(在哪个缓冲区对象中,缓冲区对象内部的偏移量)以及如何解释此数据(通过指定数据类型)。
稍后在绘制调用期间,GL将从缓冲区对象内的相关位置获取数据,正如您在VAO中指定的那样。如果此内存访问是由顶点着色器本身触发的,或者如果有一个专用的顶点提取阶段在读取数据之前将其转发到顶点着色器-,或者如果根本有GPU-这完全是特定于实现的,不必担心。
这是管道的开始,只是开始。
这取决于你如何看待事物。在传统的基于光栅化器的渲染流水线中;顶点提取";或多或少是第一阶段,顶点缓冲区对象将只保存从何处获取顶点数据的内存(以及VAO告诉它要使用哪些缓冲区对象、哪些实际位置以及如何解释它们)。
这一切都归结为:当您在"正常的";程序,你所拥有的只是CPU、缓存、寄存器、主存储器等。
然而,当您使用计算机图形(和其他领域)时,您希望使用GPU,因为它对特定任务更快。GPU是一台独立的计算机,有自己的处理器、管道和主存储器。
这意味着你的程序需要以某种方式将所有数据传输到另一台计算机,并告诉另一台电脑该怎么处理。这不是一项容易的任务,所以OpenGL为你简化了事情。因此,它们为您提供了一个抽象(VBO),表示GPU中的顶点缓冲区,以及其他任务的许多其他抽象。然后,它们为您提供创建该资源的函数(glGenBuffers
),用数据填充它(glBufferData
),";绑定它";使用它(glBindBuffer
)等
记住,这一切都是为了你的利益而简化的。事实上,每件事在背后是如何表现的细节要复杂得多。使用诸如顶点的VBO或索引的IBO之类的抽象可以更容易地使用它们。