首页 > 解决方案 > 统一缓冲区对象中的数组问题

问题描述

我在 OpenGL 方面有半经验,目前正在研究 2D 游戏的简单照明。我的计划如下:

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

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

使用 GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 作为目标调用 glGetIntegerv 返回的值返回 4096,我认为这意味着我应该能够在单个统一缓冲区中存储多达 4096 个浮点数/4 个浮点数/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 个灯时发生): 预期输出

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

是否有可能我做错了什么和/或在我的 OpenGL 设置代码中跳过了某些内容?我是否不正确地使用 UBO 功能?它实际上是我以某种方式错过的数据填充/格式错误吗?任何帮助深表感谢。

标签: c++arraysopenglglsl

解决方案


感谢@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 纠正我对 value 的误解GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB


推荐阅读