glVertexAttribPointer 和 glVertexAttribFormat:有什么区别? [英] glVertexAttribPointer and glVertexAttribFormat: What's the difference?

查看:22
本文介绍了glVertexAttribPointer 和 glVertexAttribFormat:有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

OpenGL 4.3 和 OpenGL ES 3.1 添加了几个用于指定顶点数组的替代函数:glVertexAttribFormatglBindVertexBuffers 等.但是我们已经有了用于指定顶点数组的函数.即 glVertexAttribPointer.

OpenGL 4.3 and OpenGL ES 3.1 added several alternative functions for specifying vertex arrays: glVertexAttribFormat, glBindVertexBuffers, etc. But we already had functions for specifying vertex arrays. Namely glVertexAttribPointer.

  1. 为什么要添加与旧 API 具有相同功能的新 API?

  1. Why add new APIs that do the same thing as the old ones?

新的 API 是如何工作的?

How do the new APIs work?

推荐答案

glVertexAttribPointer 有两个缺陷,一个是半主观的,另一个是客观的.

glVertexAttribPointer has two flaws, one of them semi-subjective, the other objective.

第一个缺陷是它对GL_ARRAY_BUFFER 的依赖.这意味着 glVertexAttribPointer 的行为取决于在调用它时绑定到 GL_ARRAY_BUFFER 的任何内容.但是一旦被调用,绑定到 GL_ARRAY_BUFFER 的内容就不再重要了;缓冲区对象的引用被复制到 VAO 中.所有这些都是非常不直观和令人困惑的,即使对于一些半经验的用户也是如此.

The first flaw is its dependency on GL_ARRAY_BUFFER. This means that the behavior of glVertexAttribPointer is contingent on whatever was bound to GL_ARRAY_BUFFER at the time it was called. But once it is called, what is bound to GL_ARRAY_BUFFER no longer matters; the buffer object's reference is copied into the VAO. All this is very unintuitive and confusing, even to some semi-experienced users.

它还要求您提供缓冲区对象的偏移量作为指针",而不是作为整数字节偏移量.这意味着您执行了从整数到指针的笨拙转换(必须与驱动程序中同样笨拙的转换匹配).

It also requires you to provide an offset into the buffer object as a "pointer", rather than as an integer byte offset. This means that you perform an awkward cast from an integer to a pointer (which must be matched by an equally awkward cast in the driver).

第二个缺陷是它将两个逻辑上完全分开的操作混为一谈.为了定义一个OpenGL可以读取的顶点数组,你必须提供两件事:

The second flaw is that it conflates two operations that, logically, are quite separate. In order to define a vertex array that OpenGL can read, you must provide two things:

  • 如何从内存中获取数据.
  • 这些数据是什么样的.

glVertexAttribPointer 同时提供这两者.GL_ARRAY_BUFFER 缓冲区对象,加上偏移量指针"和步幅定义了数据的存储位置和获取方式.其他参数描述了单个数据单元的样子.我们称之为数组的顶点格式.

glVertexAttribPointer provides both of these simultaneously. The GL_ARRAY_BUFFER buffer object, plus the offset "pointer" and stride define where the data is stored and how to fetch it. The other parameters describes what a single unit of data looks like. Let us call this the vertex format of the array.

实际上,与顶点格式相比,用户更可能改变顶点数据的来源.毕竟,场景中的许多对象都以相同的方式存储它们的顶点.不管这种方式是什么:3 个用于位置的浮点数,4 个用于颜色的无符号字节,2 个用于 tex-coords 的无符号短整数,等等.一般来说,您只有几种顶点格式.

As a practical matter, users are far more likely to change where vertex data comes from than vertex formats. After all, many objects in the scene store their vertices in the same way. Whatever that way may be: 3 floats for position, 4 unsigned bytes for colors, 2 unsigned shorts for tex-coords, etc. Generally speaking, you have only a few vertex formats.

然而,您可以从更多位置提取数据.即使所有对象都来自同一个缓冲区,您可能希望更新该缓冲区的偏移量,以便从一个对象切换到另一个对象.

Whereas you have far more locations where you pull data from. Even if the objects all come from the same buffer, you will likely want to update the offset within that buffer to switch from object to object.

使用 glVertexAttribPointer,您不能只更新偏移量.您必须一次指定整个格式+缓冲区信息.每次.

With glVertexAttribPointer, you can't update just the offset. You have to specify the whole format+buffer information all at once. Every time.

VAO 减轻了对每个对象进行所有这些调用的必要性,但事实证明它们并没有真正解决问题.哦,当然,您不必实际调用 glVertexAttribPointer.但这并没有改变改变顶点格式是昂贵的事实.

VAOs mitigate having to make all those calls per object, but it turns out that they don't really solve the problem. Oh sure, you don't have to actually call glVertexAttribPointer. But that doesn't change the fact that changing vertex formats is expensive.

正如此处所讨论的,更改顶点格式的成本非常高.当你绑定一个新的 VAO(或者更确切地说,当你在绑定一个新的 VAO 之后渲染时),实现要么改变顶点格式,要么必须比较两个 VAO 以查看它们定义的顶点格式是否不同.无论哪种方式,它都在做它不需要做的工作.

As discussed here, changing vertex formats is pretty expensive. When you bind a new VAO (or rather, when you render after binding a new VAO), the implementation either changes the vertex format regardless or has to compare the two VAOs to see if the vertex formats they define are different. Either way, it's doing work that it doesn't need to be doing.

glVertexAttribFormatglBindVertexBuffer 解决了这两个问题.glBindVertexBuffer 直接指定缓冲区对象并将字节偏移量作为实际(64 位)整数.因此,GL_ARRAY_BUFFER 绑定的使用并不尴尬;该绑定仅用于操作缓冲区对象.

glVertexAttribFormat and glBindVertexBuffer fix both of these problems. glBindVertexBuffer directly specifies the buffer object and takes the byte offset as an actual (64-bit) integer. So there's no awkward use of the GL_ARRAY_BUFFER binding; that binding is solely used for manipulating the buffer object.

并且因为这两个独立的概念现在是独立的函数,所以您可以拥有一个 VAO 来存储格式、绑定它,然后为您渲染的每个对象或对象组绑定顶点缓冲区.更改顶点缓冲区绑定状态比顶点格式状态更便宜.

And because the two separate concepts are now separate functions, you can have a VAO that stores a format, bind it, then bind vertex buffers for each object or group of objects that you render with. Changing vertex buffer binding state is cheaper than vertex format state.

请注意,这种分离在 GL 4.5 的直接状态访问 API 中已正式化.也就是说,没有 glVertexAttribPointer 的 DSA 版本;您必须使用 glVertexArrayAttribFormat 和其他单独的格式 API.

Note that this separation is formalized in GL 4.5's direct state access APIs. That is, there is no DSA version of glVertexAttribPointer; you must use glVertexArrayAttribFormat and the other separate format APIs.

单独的属性绑定函数是这样工作的.glVertexAttrib*Format 函数提供属性的所有顶点格式参数.它的每个参数与对 glVertexAttrib*Pointer 的等效调用中的参数具有完全相同的含义.

The separate attribute binding functions work like this. glVertexAttrib*Format functions provides all of the vertex formatting parameters for an attribute. Each of its parameters have the exact same meaning as the parameters from the equivalent call to glVertexAttrib*Pointer.

事情变得有点混乱的地方是 glBindVertexBuffer.

Where things get a bit confusing is with glBindVertexBuffer.

它的第一个参数是一个索引.但这不是属性位置;它只是一个缓冲区绑定点.这是一个独立数组,与属性位置有自己的最大限制.因此,将缓冲区绑定到索引 0 的事实意味着没有关于属性位置 0 从何处获取其数据的信息.

Its first parameter is an index. But this is not an attribute location; it is merely a buffer binding point. This is a separate array from attribute locations with its own maximum limit. So the fact that you bind a buffer to index 0 means nothing about where attribute location 0 gets its data from.

缓冲区绑定和属性位置之间的连接由glVertexAttribBinding 定义.第一个参数是属性位置,第二个参数是用于获取该属性位置的缓冲区绑定索引.由于函数的名称以VertexAttrib"开头,您应该将其视为顶点格式状态的一部分,因此更改成本很高.

The connection between buffer bindings and attribute locations is defined by glVertexAttribBinding. The first parameter is the attribute location, and the second is the buffer binding index to fetch that attribute's location with. Since the function's name starts with "VertexAttrib", you should consider this to be part of the vertex format state and thus is expensive to change.

偏移的性质一开始也可能有点混乱.glVertexAttribFormat 有一个偏移参数.但glBindVertexBuffer 也是如此.但这些抵消意味着不同的东西.理解差异的最简单方法是使用交错数据结构的示例:

The nature of offsets may be a bit confusing at first as well. glVertexAttribFormat has an offset parameter. But so too does glBindVertexBuffer. But these offsets mean different things. The easiest way to understand the difference is by using an example of an interleaved data structure:

struct Vertex
{
    GLfloat pos[3];
    GLubyte color[4];
    GLushort texCoord[2];
};

顶点缓冲区绑定偏移指定从缓冲区对象的开头到第一个顶点索引的字节偏移量.也就是说,当您渲染索引 0 时,GPU 将从缓冲区对象的地址 + 绑定偏移量中获取内存.

The vertex buffer binding offset specifies the byte offset from the start of the buffer object to the first vertex index. That is, when you render index 0, the GPU will fetch memory from the buffer object's address + the binding offset.

顶点格式偏移量指定从每个顶点的开始到该特定属性数据的偏移量.如果缓冲区中的数据由 Vertex 定义,则每个属性的偏移量将是:

The vertex format offset specifies the offset from the start of each vertex to that particular attribute's data. If the data in the buffer is defined by Vertex, then the offset for each attribute would be:

glVertexAttribFormat(0, ..., offsetof(Vertex, pos)); //AKA: 0
glVertexAttribFormat(1, ..., offsetof(Vertex, color)); //Probably 12
glVertexAttribFormat(2, ..., offsetof(Vertex, texCoord)); //Probably 16

因此绑定偏移定义了顶点 0 在内存中的位置,而格式偏移定义了每个属性的数据来​​自顶点中的位置.

So the binding offset defined where vertex 0 is in memory, while the format offsets define where the each attribute's data comes from within a vertex.

最后要理解的是,缓冲区绑定是定义 stride 的地方.这可能看起来很奇怪,但请从硬件的角度考虑.

The last thing to understand is that the buffer binding is where the stride is defined. This may seem odd, but think about it from the hardware perspective.

缓冲区绑定应包含硬件将顶点索引或实例索引转换为内存位置所需的所有信息.完成后,顶点格式将解释如何解释该内存位置中的字节.

The buffer binding should contain all of the information needed by the hardware to turn a vertex index or instance index into a memory location. Once that's done, the vertex format explains how to interpret the bytes in that memory location.

这也是为什么实例除数是缓冲区绑定状态的一部分,通过glVertexBindingDivisor.硬件需要知道除数才能将实例索引转换为内存地址.

This is also why the instance divisor is part of the buffer binding state, via glVertexBindingDivisor. The hardware needs to know the divisor in order to convert an instance index into a memory address.

当然,这也意味着您不能再依赖 OpenGL 来为您计算步幅.在上面的转换中,您只需使用 sizeof(Vertex).

Of course, this also means that you can no longer rely on OpenGL to compute the stride for you. In the above cast, you simply use sizeof(Vertex).

单独的属性格式完全覆盖了旧的 glVertexAttribPointer 模型,以至于旧的函数现在完全根据新的定义:

Separate attribute formats completely covers the old glVertexAttribPointer model so well that the old function is now defined entirely in terms of the new:

void glVertexAttrib*Pointer(GLuint index​, GLint size​, GLenum type​, {GLboolean normalized​,} GLsizei stride​, const GLvoid * pointer​)
{
  glVertexAttrib*Format(index, size, type, {normalized,} 0);
  glVertexAttribBinding(index, index);

  GLuint buffer;
  glGetIntegerv(GL_ARRAY_BUFFER_BINDING, buffer);
  if(buffer == 0)
    glErrorOut(GL_INVALID_OPERATION); //Give an error.

  if(stride == 0)
    stride = CalcStride(size, type);

  GLintptr offset = reinterpret_cast<GLintptr>(pointer);
  glBindVertexBuffer(index, buffer, offset, stride);
}

请注意,此等效函数对属性位置和缓冲区绑定索引使用相同的索引值.如果您正在处理交错属性,则应尽可能避免这种情况;相反,对从同一缓冲区交错的所有属性使用单个缓冲区绑定.

Note that this equivalent function uses the same index value for the attribute location and the buffer binding index. If you're doing interleaved attributes, you should avoid this where possible; instead, use a single buffer binding for all attributes that are interleaved from the same buffer.

这篇关于glVertexAttribPointer 和 glVertexAttribFormat:有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆