希望这可以澄清一些误解,让你更好地理解通用着色器存储器的设置。需要理解的是缓冲对象在GL中的实际工作原理。你经常会听到人们区分诸如“顶点缓冲对象”和“统一缓冲对象”之类的东西。实际上,没有根本性的区别——无论存储什么内容,缓冲对象都被同样对待。它只是一个通用数据存储区,只有在绑定到特定点(例如
GL_ARRAY_BUFFER
或
GL_UNIFORM_BUFFER
)时才具有特殊意义。
不要认为特殊用途的顶点缓冲区位于GPU上,要更一般地考虑——它实际上是未格式化的内存,如果您知道其结构,则可以读取/写入。像
glVertexAttribPointer(...)
之类的调用足以描述缓冲对象的数据结构,使得
glDrawArrays(...)
可以从缓冲对象的内存中有意义地提取每个顶点着色器调用的顶点属性。
对于计算着色器,你需要自己做同样的事情,如下所示。你需要熟悉
7.6.2.2 - 标准统一块布局中讨论的规则,才能完全理解以下数据结构。
使用着色器存储块描述顶点数据结构的方式如下:
struct VtxData {
vec4 vtx_pos;
vec3 vtx_normal;
float vtx_padding7;
vec2 vtx_st;
vec2 vtx_padding10;
};
layout (std140, binding = 1) buffer VertexBuffer {
VtxData verts [];
};
使用上面定义的数据结构,在计算着色器中可以使用交错顶点缓冲区。但是在这样做时需要注意数据对齐,通常情况下你可以随意使用任何对齐方式或跨度,但是这里你需要遵守std140
布局规则。这意味着使用3分量向量并不总是明智的内存使用方式;你需要将其对齐到N(float
)、2N(vec2
)或4N(vec3
/vec4
)边界,这通常需要插入填充和/或巧妙地打包数据。在上面的示例中,你可以在所有由于对齐填充而浪费的空间中放下整个3分量向量的数据。
伪代码展示了如何创建和绑定双重使用的缓冲区:
struct Vertex {
GLfloat pos [4];
GLfloat normal [3];
GLfloat padding7;
GLfloat st [2];
GLfloat padding10 [2];
} *verts;
[... code to allocate and fill verts ...]
GLuint vbo;
glGenBuffers (1, &vbo);
glBindBuffer (GL_ARRAY_BUFFER, vbo);
glBufferData (GL_ARRAY_BUFFER, sizeof (Vertex) * num_verts, verts, GL_STATIC_DRAW);
glVertexAttribPointer (0, 4, GL_FLOAT, GL_FALSE, 48, 0);
glVertexAttribPointer (1, 3, GL_FLOAT, GL_FALSE, 48, 16);
glVertexAttribPointer (2, 2, GL_FLOAT, GL_FALSE, 48, 32);
glBindBufferBase (GL_SHADER_STORAGE_BUFFER, 1, vbo);