我认为你把很多不同的东西混在一起并有几个混淆点,所以我将按照你提出的顺序尝试解决大部分问题:
当我们声明一系列顶点时,假设是形成三角形图元的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指定使用的缓冲区对象、位置和解析方式)的内存。
glVertexAttribPointer
时,绑定到目标GL_ARRAY_BUFFER
的缓冲与指定的顶点属性相关联。请参阅顶点数组对象。 - Rabbid76