OpenGL 如何填充缓冲区并将它们读回?

How does OpenGL fill buffers and read them back?

我使用带有一堆 GLfloats 的 OpenGL 缓冲区作为顶点缓冲区,一切都很好。 GLfloats 的格式为 [x1, y1, z1, x2, y2, z2, ...].

但是,在遵循 this tutorial 时,它告诉我改用 glm::vec3

glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLfloat), &vertices[0], GL_STATIC_DRAW);

glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);

现在这段代码是有效的,我想知道 OpenGL 如何知道如何用 glm::vec3 而不是 GLfloats 填充缓冲区。然后我想知道,当我从缓冲区读回数据时,使用:

std::vector<glm::vec3> data;
glGetBufferSubData(mTarget, offset, vertexCount * sizeof(glm::vec3), &data[0]);`

这会产生一堆 glm::vec3 吗?所以问题是,OpenGL 如何用 glm::vec3 填充缓冲区,以及(如果是,如何)读回它?

glm::vec3 只是结构中的三个浮点数。因此,将 glm::vec3 的地址传递给 gl 函数实际上与将地址传递给 float 数组的第一个元素做同样的事情。 GLfloat 只是 float btw 的 typedef。

同样的原则也适用于从 gl 中读取数据。内存中 glm::vec3 的 x 个元素的数组等效于具有 3x 个元素的 GLfloat(float)数组。

根据OpenGL's documentationglBufferData()需要一个指向data的指针(即数组,即顶点的坐标) .

我们先来看看glm::vec3的实现。

如果你签出 glm's Github repo, you'll see that, depending on your compilation flags, glm::vec3 is a typedef of highp_vec3 which is a typedef of tvec3<float, highp>

tvec3type_vec3.hpp (included by vec3.hpp) and the class (template) methods are defined in type_vec3.inl.

中声明

特别是,operator[]'s definition 是:

template <typename T, precision P>
GLM_FUNC_QUALIFIER T & tvec3<T, P>::operator[](typename tvec3<T, P>::length_type i)
{
    assert(i >= 0 && static_cast<detail::component_count_t>(i) < detail::component_count(*this));
    return (&x)[i];
}

鉴于那段代码,人们会假设 x 是 "array" 的第一个元素,其中包含 glm::vec3 的坐标。然而,当我们回到type_vec3.h,我们发现:

union { T x, r, s; };
union { T y, g, t; };
union { T z, b, p; };

因此 xyz 独立的属性 。但是多亏了 how class/struct members are laid out,它们可以被视为一个 &x.

开始的单个数组

我们现在知道,glm::vec3(实际上是 tvec3)以连续的方式存储坐标。但它是否还存储其他属性?

好吧,我们可以继续深入代码,或者用一个简单的程序来给我们答案:

#include <iostream>
#include <ios>

#include <glm/vec3.hpp>

int main()
{
    const glm::vec3 v;

    const size_t sizeof_v   = sizeof(v);
    const size_t sizeof_xyz = sizeof(v.x) + sizeof(v.y) + sizeof(v.z);

    std::cout << "sizeof(v)  : " << sizeof_v   << std::endl;
    std::cout << "sizeof(xyz): " << sizeof_xyz << std::endl;

    std::cout << "sizeof(v) == sizeof(xyz) : " << std::boolalpha << (sizeof_v == sizeof_xyz) << std::endl;
}

在我的机器上打印:

sizeof(v)  : 12
sizeof(xyz): 12
sizeof(v) == sizeof(xyz) : true

因此,glm::vec3 存储 (x, y, z) 坐标。

现在,如果我们创建一个 std::vector<glm::vec3> vertices;,可以肯定地说 &vertices[0] 指向的数据布局(在 C++11 中是 vertices.data())是:

vertices == [vertice1 vertice2 ...]
         == [vertice1.x vertice1.y vertice1.z vertice2.x vertice2.y vertice2.z ...]

回到最初的问题——glBufferData()的要求:当你传递&vertices[0]时,你实际上传递的是data的地址(即指针),就像glBufferData() 预计。同样的逻辑适用于 glGetBufferSubData().