GLSL - 无法访问多个灯的 SSBO 数组的第二个索引

GLSL - unable to access second index of a SSBO array for multiple lights

在我的应用程序中,我添加了两个灯。一个在 (0,0,2),第二个在 (2,0,0)。这是我得到的(x、y、z 轴分别由红色、绿色和蓝色线表示):

注意只有第一盏灯亮着,第二盏灯没亮。我使我的应用程序核心配置文件兼容,以使用 RenderDoc 和 NSight 等各种工具检查缓冲区,并且都向我显示缓冲区中存在第二盏灯的数据(在 运行 Nsight 时拍摄的照片):

这些位置似乎已正确传输到 gpu 内存缓冲区。这是我的片段着色器的实现,它使用 SSBO 来处理我的应用程序中的多个灯光:

#version 430

struct Light {
  vec3  position;
  vec3  color;
  float intensity;
  float attenuation;
  float radius;
};

layout (std140, binding = 0) uniform CameraInfo {
  mat4  ProjectionView; 
  vec3  eye;
};

layout (std430, binding = 1) readonly buffer LightsData {
    Light lights[];
};

uniform vec3  ambient_light_color;
uniform float ambient_light_intensity;

in  vec3 ex_FragPos;
in  vec4 ex_Color;
in  vec3 ex_Normal;
out vec4 out_Color;

void main(void)
{
    // Basic ambient light
    vec3 ambient_light = ambient_light_color * ambient_light_intensity;

    int i;
    vec3 diffuse = vec3(0.0,0.0,0.0);
    vec3 specular = vec3(0.0,0.0,0.0);
    for (i = 0; i < lights.length(); ++i) {
        Light wLight = lights[i];
        // Basic diffuse light
        vec3 norm = normalize(ex_Normal); // in this project the normals are all normalized anyway...
        vec3 lightDir = normalize(wLight.position - ex_FragPos);
        float diff = max(dot(norm, lightDir), 0.0);
        diffuse += diff * wLight.color;

        // Basic specular light
        vec3 viewDir = normalize(eye - ex_FragPos);
        vec3 reflectDir = reflect(-lightDir, norm);  
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
        specular += wLight.intensity * spec * wLight.color;  
    }

    out_Color = ex_Color * vec4(specular + diffuse + ambient_light,1.0); 
}

请注意,我已阅读 OpenGL 4.5 spec 的第 7.6.2.2 节,如果我理解正确,我的对齐方式应遵循结构中最大成员的大小,即 vec3 和我的结构大小是 36 字节,所以这里的一切都应该没问题。我还尝试了不同的 std 版本(例如 std140)并添加了一些填充,但没有解决第二个灯的问题。在我的 C++ 代码中,我有这些定义来在我的应用程序中添加灯光:

light_module.h/.cc:

struct Light {
  glm::f32vec3  position;
  glm::f32vec3  color;
  float         intensity;
  float         attenuation;
  float         radius;
};
...
constexpr GLuint        LIGHTS_SSBO_BINDING_POINT = 1U;
std::vector<Light>      _Lights;
...
void AddLight(const Light &light) {
  // Add to _Lights
  _Lights.push_back(light);
UpdateSSBOBlockData(
  LIGHTS_SSBO_BINDING_POINT, _Lights.size()* sizeof(Light),
  static_cast<void*>(_Lights.data()), GL_DYNAMIC_DRAW);
}

shader_module.h/.cc:

using SSBOCapacity = GLuint;
using BindingPoint = GLuint;
using ID = GLuint;
std::map<BindingPoint, std::pair<ID, SSBOCapacity> >  SSBO_list;
...
void UpdateSSBOBlockData(GLuint a_unBindingPoint,
  GLuint a_unSSBOSize, void* a_pData, GLenum a_eUsage) {
  auto SSBO = SSBO_list.find(a_unBindingPoint);
  if (SSBO != SSBO_list.end()) {
    GLuint unSSBOID = SSBO->second.first;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, unSSBOID);
    glBufferData(GL_SHADER_STORAGE_BUFFER, a_unSSBOSize, a_pData, a_eUsage);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); //unbind
  }
  else 
    // error handling...
}

基本上,每次在我的应用程序中添加一盏灯时,我都会尝试 update/reallocate 使用 glBufferData 调整 SSBO 大小。

现在,由于我在处理第二个灯光数据时遇到问题,我更改了我的片段着色器代码,通过强制 i = 1 并循环直到 i < 2 来仅执行我的 SSBO 数组中的第二个灯光,但出现以下错误:

(50) : error C1068: ... or possible array index out of bounds
(50) : error C5025: lvalue in field access too complex
(56) : error C1068: ... or possible array index out of bounds
(56) : error C5025: lvalue in field access too complex

第 50 行和第 56 行分别引用了 diffuse += diff * wLight.color;specular += wLight.intensity * spec * wLight.color;。即使我在第一次绘制调用之前添加灯光,是否真的存在越界访问?为什么当我使用 lights.length() 而不是 2 时着色器编译正确?

最后,我在我的 for 循环中添加了一个简单的 if (i == 1) 来查看 lights.length() 是否等于 2,但它没有进入。 然而我的缓冲区的初始大小为 0,然后我添加了一个将缓冲区大小设置为 36 字节的灯,我们可以看到第一盏灯工作正常。为什么 update/reallocate 第二次不起作用?

所以我所做的是在结构声明的末尾添加一些填充 仅在 C++ 端。所需的填充是 float[3] 或 12 个字节,总计为 48 个字节。我仍然不确定为什么需要这样做,因为规范说明(如 this post 中突出显示)

  1. If the member is a structure, the base alignment of the structure is N, where N is the largest base alignment value of any of its members, and rounded up to the base alignment of a vec4. The individual members of this sub-structure are then assigned offsets by applying this set of rules recursively, where the base offset of the first member of the sub-structure is equal to the aligned offset of the structure. The structure may have padding at the end; the base offset of the member following the sub-structure is rounded up to the next multiple of the base alignment of the structure. [...]

When using the std430 storage layout, shader storage blocks will be laid out in buffer storage identically to uniform and shader storage blocks using the std140 layout, except that the base alignment and stride of arrays of scalars and vectors in rule 4 and of structures in rule 9 are not rounded up a multiple of the base alignment of a vec4.

我猜 glm 定义的 vec3 和 glm::f32vec3 等结构在使用 std430 时会递归舍入到 vec4,因此我的结构必须遵循 vec4 的对齐方式。如果有人能证实这一点,那将很有趣,因为上面链接的 post 直接处理 vec4 而不是 vec3。

双灯都亮的图片:

编辑:

经过更多调查,事实证明 Light 结构的最后 3 个字段(强度、衰减和半径)不可用。我通过将位置和颜色从 glm::f32vec3 更改为 glm::vec4 来解决此问题。可以在 中找到更多信息。由于前面提到的对齐方式,我还留下了一个 float 用于填充。