当前位置: 首页 > 知识库问答 >
问题:

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

班言
2023-03-14

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

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

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

调用glGetIntgerv返回的值GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB作为目标返回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]));
}

预期输出(仅当

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

是否有可能我在OpenGL设置代码中做了一些错误和/或跳过了一些内容?我是否不当地使用了UBO功能?这真的是一个数据填充/格式错误,我不知怎么错过了吗?非常感谢您的帮助。

共有2个答案

越骏俊
2023-03-14

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

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

一个愚蠢的建议是把灯分成两个均匀的缓冲区?

// 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_相机也是一个统一的,所以也许只需将统一移动到片段着色器?(并避免将其作为片段输入传递?)

uniform vec2 u_Camera;

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

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

卢杰
2023-03-14

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

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

解决方案是将UBO上的操作推迟到片段着色器,方法是将顶点着色器中的相关值作为vec2中的单个传递给它。这就解决了我的问题,我现在可以把我想要的尽可能多的数据放入UBO(达到GL\u MAX\u UNIFORM\u BLOCK\u 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\u MAX\u VERTEX\u UNIFORM\u COMPONENTS\u ARB

 类似资料:
  • 我正在努力理解这两个,如何使用它们,以及它们是如何联系的。假设我想创建一个简单的地形和一个有纹理的立方体。对于这两个对象,我有三角形、顶点的数组,对于立方体,我有一个包含纹理数据的数组。我的问题是:我如何使用VAOs和VBOs来创建和呈现这两个? null

  • 我正在尝试从ArrayList读取并创建VBO,然后对其进行渲染。问题是我只是渲染了一个空白屏幕。当我立即渲染时,一切都很好;只有现在VBO的才不起作用。 游戏循环如下所示,相机调用glTranslate和glRotate函数。 渲染方法: 阵列列表具有顶点和链接的颜色。每个体素将其顶点添加到ArrayList(而不是立即渲染)。

  • 我有一个JSON对象,我正在将它转换成一个,并在这里进行一些处理。稍后,我想将相同的缓冲区数据转换为有效的JSON对象。 我的工作节点V6.9.1 下面是我尝试过的代码,但当我转换回JSON并且无法打开此对象时,我得到了。 所以我试着用检查的方式打印整个物体 如果我试着像数组一样读取它 我试图解析它也抛出SynTaxError:意外令牌o在JSON在位置2 我需要像我创建的那样将其视为真实对象(我

  • 我目前正在使用LWJGL / OpenGL创建一个2D,自上而下的游戏,但是在绘制实体之后,在使用顶点缓冲区对象渲染实体后,在让它们四处移动的过程中,我遇到了一些问题。这是渲染线程的 run() 方法,以及设置方法: 实体从抽象超类继承这些方法来设置 VBO 并绘制实体(呈现线程中的 drawEntities 方法仅调用该方法,实体更新方法(见下文),而 setUpEntities 调用 setU

  • 问题内容: 在编写用于OpenGL库的Matrix类时,我遇到了一个问题,即使用Java数组还是使用Buffer策略存储数据(JOGL为Matrix操作提供直接缓冲区复制)。为了对此进行分析,我编写了一个小型性能测试程序,该程序比较了Arrays vs Buffers和Direct Buffers上循环和批量操作的相对速度。 我想在这里与您分享我的结果(因为我发现它们很有趣)。请随时发表评论和/或

  • 我有一个使用paramiko的缓冲区的问题,我在这里发现了同样的问题,其中一个解决方案指出: 如果您只是调用.open()来获取SFTPFile实例,而不是使用.get(),那么就对该对象调用.read(),或者只是将其交给Python标准库函数shutil.copyFileObj()来下载内容。这将避免Paramiko预取缓存,并且允许您下载文件,即使它没有那么快。 如果我有: