OpenGL着色器存储缓冲区/memoryBarrierBuffer

Posted

技术标签:

【中文标题】OpenGL着色器存储缓冲区/memoryBarrierBuffer【英文标题】:OpenGL Shader Storage Buffer / memoryBarrierBuffer 【发布时间】:2018-09-26 09:59:31 【问题描述】:

我目前创建了两个 SSBO 来处理一些灯光,因为 VS-FS in out 接口无法处理很多灯光(我使用前向着色)。 对于第一个,我只将值传递给着色器(基本上是只读的)[cpp]:

struct GLightProperties

    unsigned int numLights;
    LightProperties properties[];
;

...

glp = (GLightProperties*)malloc(sizeof(GLightProperties) + sizeof(LightProperties) * lastSize);

...

glGenBuffers(1, &ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLightProperties) + sizeof(LightProperties) * lastSize, glp, GL_DYNAMIC_COPY);
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ssbo);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

着色器文件 [GLSL]:

layout(std430, binding = 1) buffer Lights

    uint numLights;
    LightProperties properties[];
lights;

所以这第一个 SSBO 可以正常工作。但是,在另一个目的是 VS-FS 接口中,存在一些问题:

glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo2);
glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(float) * 4 * 3 * lastSize, nullptr, GL_DYNAMIC_COPY);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

GLSL:

struct TangentProperties

    vec4 TangentLightPos;
    vec4 TangentViewPos;
    vec4 TangentFragPos;
;

layout(std430, binding = 0) buffer TangentSpace

    TangentProperties tangentProperties[];
tspace;

所以你注意到我将nullptr 传递给glBufferData 因为vs 将写入缓冲区而fs 将读取其内容。 就像在 VS 阶段一样:

for(int i = 0; i < lights.numLights; i++)

    tspace.tangentProperties[index].TangentLightPos.xyz = TBN * lights.properties[index].lightPosition.xyz;
    tspace.tangentProperties[index].TangentViewPos.xyz  = TBN * camPos;
    tspace.tangentProperties[index].TangentFragPos.xyz  = TBN * vec3(worldPosition);
    memoryBarrierBuffer();

在此之后,FS 读取值,结果只是垃圾。我在记忆障碍方面做错了吗?

输出结果是这样的:

【问题讨论】:

我自己永远不要使用memoryBarrierBuffer(),但是根据文档,它说屏障确保在内存屏障之前执行的所有更改在后续阶段都可见,这意味着它不会对当前阶段(如果我理解正确的话)。既然你有它在一个循环中,我猜你希望它为下一次迭代同步线程?文档:khronos.org/registry/OpenGL-Refpages/gl4/html/… 【参考方案1】:

好的,让我们解决这个明显的错误:

for(int i = 0; i < lights.numLights; i++)

    tspace.tangentProperties[index].TangentLightPos.xyz = TBN * lights.properties[index].lightPosition.xyz;
    tspace.tangentProperties[index].TangentViewPos.xyz  = TBN * camPos;
    tspace.tangentProperties[index].TangentFragPos.xyz  = TBN * vec3(worldPosition);
    memoryBarrierBuffer();

index 在这个循环中永远不会改变,所以你只写一个灯,你只写 last 个灯的值。所有其他灯都将具有垃圾/未定义值。

所以你的意思可能是i 而不是index

但这只是问题的开始。看,如果你做出改变,你会得到这个:

for(int i = 0; i < lights.numLights; i++)

    tspace.tangentProperties[i].TangentLightPos.xyz = TBN * lights.properties[i].lightPosition.xyz;
    tspace.tangentProperties[i].TangentViewPos.xyz  = TBN * camPos;
    tspace.tangentProperties[i].TangentFragPos.xyz  = TBN * vec3(worldPosition);

memoryBarrierBuffer();

注意屏障在循环之外。

这就产生了一个新问题。此代码将使每个顶点着色器调用写入相同的内存缓冲区。毕竟,SSBO 不是 VS 输出 变量。输出变量存储为顶点的一部分。然后,光栅化器在对图元进行光栅化时在图元中插入此顶点数据,从而为 FS 提供the input values。所以一个 VS 不能踩到另一个 VS 的输出变量。

SSBO 不会发生这种情况。每个 VS 都作用于相同 SSBO 内存。因此,如果它们写入同一数组的相同索引,它们将写入相同的内存地址。这是一个竞争条件(因为兄弟调用之间不能同步),因此是未定义的行为。

因此,唯一可行的方法是,如果您的缓冲区对整个场景中的每个顶点都有numLights 条目,则可能会起作用。

这是一个根本不合理的空间量。即使您可以将其降低到特定绘图调用中的顶点数(这是可行的,但我不会说如何),您仍然会落后于性能。每次 FS 调用都必须读取 144 字节的数据每个光源(3 个表条目,三角形的每个顶点一个),线性插值这些值,然后进行光照计算。 p>

将 TBN 矩阵作为 VS 输出传递并在 FS 中进行矩阵乘法会更快。是的,这是很多矩阵乘法,但 GPU 在矩阵乘法上非常快,而在读取内存时真的很慢

另外,重新考虑您是否需要切线空间片段位置。一般来说,你永远不会这样做。

【讨论】:

以上是关于OpenGL着色器存储缓冲区/memoryBarrierBuffer的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL:着色器存储缓冲区映射/绑定

从顶点着色器中修改着色器存储缓冲区对象

带字节的着色器存储缓冲区对象

OpenGL:缓冲区对象/着色器超出范围

帧缓冲区和在opengl中使用着色器

OpenGL 计算着色器中的样本深度缓冲区