动态长度数组作为着色器存储缓冲区对象
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 的提示)
假设我有一个 "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++ 结构布局可能会导致一些对齐问题。您可能想阅读