动态长度数组作为着色器存储缓冲区对象

Dynamic-length arrays as Shader Storage Buffer Objects

假设我有一个 "balls" 的动态数字,我想在我的 OpenGL 着色器中访问它。在 C++ 中,数据可能是这样的:

struct Ball
{
    glm::vec3 position;
    glm:vec3 colour;
    float size;
};

std::vector<Ball> all_balls;

如果我想在我的片段着色器中迭代 all_balls,我相信我将需要一个着色器存储缓冲区对象。

This documentation 涉及数组,但明显不完整。

我假设我可以像这样将数据发送到缓冲区

glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, all_balls.size() * sizeof(Ball), &(all_balls[0]), usage);

在 GLSL 中,我如何指定缓冲区是一个数组,我的着色器如何知道这个数组的大小?

当处理长度不是编译时常量的数组时,可以将 SSBO Interface Block 的成员声明为未确定的长度。

假设存在一个适合 C++ 球结构的 GLSL 结构,代码可能看起来像这样:

struct GLSLBall {...};

layout(std430, binding = 0) buffer BallBuffer
{
    GLSLBall ball_data[];
}

您可以像这样遍历所有元素:

for (int i = 0; i < ball_data.length(); ++i)
{
    GLSLBall currentBall = ball_data[i];
}

当元素个数变化非常频繁的时候,那么我建议不要每次都resize/reallocateSSBO,而是一次预留足够大的buffer,将实际使用的元素个数传递给shader。这可以是独立的统一变量 (uniform uint ballCount;),也可以像这样将其打包到 SSBO 本身:

struct GLSLBall {...};

layout(std430, binding = 0) buffer BallBuffer
{
    uint ball_length;
    GLSLBall ball_data[];
}

那么你只能分配一次内存:

glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, ENOUGH_MEMORY_FOR_ALL_CASES, null, usage);

并在每次内容更改时上传数据,如下所示:

glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(unsigned int), (unsigned int)all_balls.size());
glBufferSubData(GL_SHADER_STORAGE_BUFFER, sizeof(unsigned int), all_balls.size() * sizeof(Ball), &(all_balls[0]));

glsl 循环类似于

for (int i = 0; i < BallBuffer.length; ++i)
{
    GLSLBall currentBall = ball_data[i];
    ...
}

请注意,由于使用了 vec3,您当前的 C++ 结构布局可能会导致一些对齐问题。您可能想阅读 (感谢 Rabbid76 的提示)