统一缓冲区对象中的数组问题

Trouble with arrays in Uniform Buffer Objects

我在 OpenGL 方面经验不足,目前正在为 2D 游戏开发简单的光照。我的计划如下:

  1. 创建一个统一缓冲区对象来存储两个代表场景中所有活动灯光的 vec4 数组:一个用于位置,一个用于颜色。
  2. 在我的顶点着色器中将那个统一缓冲区对象作为统一(很明显)。以某种方式转换数据并将其复制到与制服具有相同格式的输出接口块(抱歉,如果这是 confusing/incorrect 术语,请不要犹豫,要求澄清)。
  3. 使用顶点着色器输出的接口块作为我的片段着色器的输入,并使用块的内容执行光照计算。

这是一个简单的任务,但有一个问题:如果我的统一缓冲区中的数组大小 大于 14,我的着色器似乎无声地失败(没有错误来自编译或链接过程的字符串),这奇怪地使所有其他制服无法访问。

以 GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 为目标调用 glGetIntegerv 返回的值 returns 4096,我认为这意味着我应该能够存储最多 4096 个浮点数 / 4 floats/vec4 / 2 vec4s/light = 单个统一缓冲区中有 512 盏灯。

这是相关的 C++ 代码(请注意,浮点向量的大小适合填充 vec4 - 我不认为这是与我正在使用的 std140 布局有关的对齐问题):

    // generate some test light data and upload it to a UBO
    constexpr uint lightCount = 14;
    renderer.GetShader().SetUniform1i("u_LightCount", lightCount);
    struct 
    { 
        float pos[4 * lightCount] = { 0 };
        float color[4 * lightCount] = { 0 };
    } lights;
    for (uint i = 0; i < 4 * lightCount; i += 4)
    {
        lights.pos[i + 0] = i * 4;
        lights.pos[i + 1] = i * 4;
        lights.pos[i + 2] = 100.f;
        lights.pos[i + 3] = 1;
        lights.color[i + 0] = 1;
        lights.color[i + 1] = 0;
        lights.color[i + 2] = 0;
        lights.color[i + 3] = 1;
    }
    GLuint ubo = 0;
    glGenBuffers(1, &ubo);
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(lights), &lights, GL_DYNAMIC_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    uint block = glGetUniformBlockIndex(renderer.GetShader().GetId(), "BlockLight");
    GLuint bind = 0;
    glUniformBlockBinding(renderer.GetShader().GetId(), block, bind);

我在这里绑定可能会做一些奇怪的事情。着色器工作需要 glBindBufferRange 调用,但我看不出它与 glBindBuffer 调用有何不同。

    // update light positions based on scroll wheel and upload to the UBO
    lightDistance = math::clamp(lightDistance + engine.gl->GetMouseScroll().y, 0, 255);
    for (uint i = 0; i < lightCount; i++)
        lights.pos[i * 4 + 2] = lightDistance;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(lights), &lights, GL_DYNAMIC_DRAW);
    glBindBufferRange(GL_UNIFORM_BUFFER, bind, ubo, 0, sizeof(lights));

我的顶点着色器代码:

#version 420 core
layout(location = 0) in vec2 i_Position;
layout(location = 1) in vec2 i_TexCoord;
layout(location = 2) in float i_TexIndex;

uniform int u_LightCount;
uniform vec2 u_Scale;
uniform vec2 u_Camera;

layout (std140) uniform BlockLight
{
    vec4 pos[14];
    vec4 color[14];
} u_Light;

out vec2 v_TexCoord;
out float v_TexIndex;
out BlockLight
{
    vec4 pos[14];
    vec4 color[14];
} v_Light;

void main()
{
    gl_Position = vec4((i_Position + u_Camera) * u_Scale, 0, 1);
    v_TexCoord = i_TexCoord;
    v_TexIndex = i_TexIndex;

    for(int i = 0; i < u_LightCount; i++)
    {
        v_Light.pos[i] = u_Light.pos[i] + vec4(u_Camera, 0, 1);
        v_Light.color[i] = u_Light.color[i];
    }
}

片段着色器:

#version 330 core
#define PIXEL_SIZE 5
layout(location = 0) out vec4 o_Color;

uniform int u_LightCount;
uniform int u_TextureFrames[32];
uniform vec2 u_Resolution;
uniform sampler2DArray u_Textures[32];

in float v_TexIndex;
in vec2 v_TexCoord;

in BlockLight
{
    vec4 pos[14];
    vec4 color[14];
} v_Light;

void main()
{
    vec2 fragPos = floor((gl_FragCoord.xy - u_Resolution / 2.0) / PIXEL_SIZE);
    vec3 lightColor = vec3(0);

    for(int i = 0; i < u_LightCount; i++)
    {
        float x = length(v_Light.pos[i].xy - fragPos) * 2;
        float z = v_Light.pos[i].z;
        float intensity = (x > z ? 0 : clamp((z - sqrt(z * z - (x - z) * (x - z))) / 255, 0, 1));
        lightColor += v_Light.color[i].xyz * intensity;
    }
    lightColor = clamp(lightColor, vec3(0), vec3(1));

    int index = int(v_TexIndex);
    o_Color = vec4(lightColor, 1) * texture(u_Textures[index], vec3(v_TexCoord, u_TextureFrames[index]));
}

预期输出(仅当使用 <= 14 个灯时才会发生): intended output

错误输出(将着色器中的一个或多个数组大小更改为大于 14 的值时发生): error output

我是否做错了什么 and/or 在我的 OpenGL 设置代码中跳过了一些东西?我是否错误地使用了 UBO 函数?它实际上是我以某种方式错过的数据 padding/formatting 错误吗?非常感谢任何帮助。

我处理这些东西已经有一段时间了,所以我相信有人会在这里纠正我,但是 GL_MAX_FRAGMENT_INPUT_COMPONENTS 报告了什么?

我感觉这与您的统一缓冲区输入无关,但可能与片段着色器输入有关? 14 * 2 * 4 == 112,这似乎非常接近默认限制 128?

一个愚蠢的建议是将灯光分成 2 个统一的缓冲区吗?

// in the vertex shader
layout (std140) uniform BlockLightPos
{
    vec4 pos[15];
} u_Light_Pos;

in BlockLightPos
{
    vec4 pos[15];
} v_Light_pos;

因为这里只是修改位置(颜色保持统一):

        v_Light_pos.pos[i] = u_Light_Pos.pos[i] + vec4(u_Camera, 0, 1);

所以理论上在片段着色器中你可以这样做:

layout (std140) uniform BlockLightColour
{
    vec4 color[15];
} u_Light_Colour;

并希望保持在片段输入组件限制内?

虽然现在我刚刚输入了它,但我意识到 u_Camera 也是一种制服,所以也许只是将制服移至片段着色器? (并避免将它们作为片段输入传递?)

uniform vec2 u_Camera;

layout (std140) uniform BlockLight
{
    vec4 pos[15];
    vec4 color[15];
} u_Light;

否则,在片段着色器中指定一个统一缓冲区块,甚至着色器存储缓冲区?

感谢@robthebloke 解决了这个问题。这个问题与我的 UBO 设置无关,而是我如何编写着色器。我在顶点着色器中对 UBO 进行操作,将结果写入另一个缓冲区(因为着色器没有对 UBO 的写入权限),然后在片段着色器中使用该结果缓冲区。

但是,正如 Rob 指出的那样,可以通过 GL_MAX_FRAGMENT_INPUT_COMPONENTS 访问的片段着色器作为输入参数发送的字节数是有限制的。我的缓冲区超过了这个值,导致整个管道失败(但没有抛出错误,我仍然对此感到恼火)。

解决方案是将 UBO 上的操作推迟到片段着色器,方法是将来自顶点着色器的相关值作为单个 in vec2 传递给片段着色器。这解决了我的问题,现在我可以将尽可能多的数据放入 UBO(达到 GL_MAX_UNIFORM_BLOCK_SIZE 指定的限制)。

最终 C++ 代码:

    // generate some test light data and upload to a UBO
    constexpr uint lightCount = 1000;
    renderer.GetShader().SetUniform1i("u_LightCount", lightCount);
    struct 
    { 
        float pos[4 * lightCount] = { 0 };
        float color[4 * lightCount] = { 0 };
    } lights;
    for (uint i = 0; i < 4 * lightCount; i += 4)
    {
        lights.pos[i + 0] = i * 4;
        lights.pos[i + 1] = i * 4;
        lights.pos[i + 2] = 100.f;
        lights.pos[i + 3] = 1;
        lights.color[i + 0] = 1;
        lights.color[i + 1] = 0;
        lights.color[i + 2] = 0;
        lights.color[i + 3] = 1;
    }
    GLuint ubo = 0;
    glGenBuffers(1, &ubo);
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(lights), &lights, GL_DYNAMIC_DRAW);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    uint block = glGetUniformBlockIndex(renderer.GetShader().GetId(), "BlockLight");
    GLuint bind = 0;
    glUniformBlockBinding(renderer.GetShader().GetId(), block, bind);

    ...
    // inside render loop
    lightDistance = math::clamp(lightDistance + engine.gl->GetMouseScroll().y, 0, 255);
    for (uint i = 0; i < lightCount; i++)
        lights.pos[i * 4 + 2] = lightDistance;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(lights), &lights, GL_DYNAMIC_DRAW);
    glBindBufferRange(GL_UNIFORM_BUFFER, bind, ubo, 0, sizeof(lights));
    ...

顶点着色器:

#version 330 core
layout(location = 0) in vec2 i_Position;
layout(location = 1) in vec2 i_TexCoord;
layout(location = 2) in float i_TexIndex;

uniform vec2 u_Scale;
uniform vec2 u_Camera;

out vec2 v_Camera;
out vec2 v_TexCoord;
out float v_TexIndex;

void main()
{
    gl_Position = vec4((i_Position + u_Camera) * u_Scale, 0, 1);
    v_TexCoord = i_TexCoord;
    v_TexIndex = i_TexIndex;
    v_Camera = u_Camera;
}

片段着色器:

#version 330 core
#define PIXEL_SIZE 5
layout(location = 0) out vec4 o_Color;

uniform int u_LightCount;
uniform int u_TextureFrames[32];
uniform vec2 u_Resolution;
uniform sampler2DArray u_Textures[32];
layout (std140) uniform BlockLight
{
    vec4 pos[1000];
    vec4 color[1000];
} u_Light;

in float v_TexIndex;
in vec2 v_TexCoord;
in vec2 v_Camera;

void main()
{
    vec2 fragPos = floor((gl_FragCoord.xy - u_Resolution / 2.0) / PIXEL_SIZE);
    vec3 lightColor = vec3(0);

    for(int i = 0; i < u_LightCount; i++)
    {
        float x = length(u_Light.pos[i].xy + v_Camera - fragPos) * 2;
        float z = u_Light.pos[i].z;
        float intensity = (x > z ? 0 : clamp((z - sqrt(z * z - (x - z) * (x - z))) / 255, 0, 1));
        lightColor += u_Light.color[i].xyz * intensity;
    }
    lightColor = clamp(lightColor, vec3(0), vec3(1));

    int index = int(v_TexIndex);
    o_Color = vec4(lightColor, 1) * texture(u_Textures[index], vec3(v_TexCoord, u_TextureFrames[index]));
}

再次感谢@robthebloke 提供解决方案并感谢@Rabbid76 纠正我对值 GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB.

的误解