使用OpenGL和GLSL的SSAO算法的奇怪性能行为

使用OpenGL和GLSL的SSAO算法的奇怪性能行为,opengl,glsl,shader,ssao,Opengl,Glsl,Shader,Ssao,我正在研究使用定向半球渲染技术的SSAO(屏幕空间环境光遮挡)算法 I)算法 此算法要求输入: 1个包含预计算样本的数组(在主循环之前加载->在我的示例中,我使用64个根据z轴定向的样本) 1噪波纹理,包含也根据z轴定向的归一化旋转向量(此纹理生成一次) GBuffer中的2个纹理:“PositionSampler”和“NormalSampler”包含视图空间中的位置和法向量 以下是我使用的片段着色器源代码: #version 400 /* ** Output color value. *

我正在研究使用定向半球渲染技术的SSAO(屏幕空间环境光遮挡)算法

I)算法

此算法要求输入:

  • 1个包含预计算样本的数组(在主循环之前加载->在我的示例中,我使用64个根据z轴定向的样本)
  • 1噪波纹理,包含也根据z轴定向的归一化旋转向量(此纹理生成一次)
  • GBuffer中的2个纹理:“PositionSampler”和“NormalSampler”包含视图空间中的位置和法向量
以下是我使用的片段着色器源代码:

#version 400

/*
** Output color value.
*/
layout (location = 0) out vec4 FragColor;

/*
** Vertex inputs.
*/
in VertexData_VS
{
    vec2 TexCoords;

} VertexData_IN;

/*
** Inverse Projection Matrix.
*/
uniform mat4 ProjMatrix;

/*
** GBuffer samplers.
*/
uniform sampler2D PositionSampler;
uniform sampler2D NormalSampler;

/*
** Noise sampler.
*/
uniform sampler2D NoiseSampler;

/*
** Noise texture viewport.
*/
uniform vec2 NoiseTexOffset;

/*
** Ambient light intensity.
*/
uniform vec4 AmbientIntensity;

/*
** SSAO kernel + size.
*/
uniform vec3 SSAOKernel[64];
uniform uint SSAOKernelSize;
uniform float SSAORadius;

/*
** Computes Orientation matrix.
*/
mat3 GetOrientationMatrix(vec3 normal, vec3 rotation)
{
    vec3 tangent = normalize(rotation - normal * dot(rotation, normal)); //Graham Schmidt process 
    vec3 bitangent = cross(normal, tangent);

    return (mat3(tangent, bitangent, normal)); //Orientation according to the normal
}

/*
** Fragment shader entry point.
*/
void main(void)
{
    float OcclusionFactor = 0.0f;

    vec3 gNormal_CS = normalize(texture(
        NormalSampler, VertexData_IN.TexCoords).xyz * 2.0f - 1.0f); //Normal vector in view space from GBuffer
    vec3 rotationVec = normalize(texture(NoiseSampler,
        VertexData_IN.TexCoords * NoiseTexOffset).xyz * 2.0f - 1.0f); //Rotation vector required for Graham Schmidt process

    vec3 Origin_VS = texture(PositionSampler, VertexData_IN.TexCoords).xyz; //Origin vertex in view space from GBuffer
    mat3 OrientMatrix = GetOrientationMatrix(gNormal_CS, rotationVec);

    for (int idx = 0; idx < SSAOKernelSize; idx++) //For each sample (64 iterations)
    {
        vec4 Sample_VS = vec4(Origin_VS + OrientMatrix * SSAOKernel[idx], 1.0f); //Sample translated in view space

        vec4 Sample_HS = ProjMatrix * Sample_VS; //Sample in homogeneus space
        vec3 Sample_CS = Sample_HS.xyz /= Sample_HS.w; //Perspective dividing (clip space)
        vec2 texOffset = Sample_CS.xy * 0.5f + 0.5f; //Recover sample texture coordinates

        vec3 SampleDepth_VS = texture(PositionSampler, texOffset).xyz; //Sample depth in view space

        if (Sample_VS.z < SampleDepth_VS.z)
            if (length(Sample_VS.xyz - SampleDepth_VS) <= SSAORadius)
                OcclusionFactor += 1.0f; //Occlusion accumulation
    }
    OcclusionFactor = 1.0f - (OcclusionFactor / float(SSAOKernelSize));

    FragColor = vec4(OcclusionFactor);
    FragColor *= AmbientIntensity;
}
顺便说一句:

FragColor = vec4(OcclusionFactor);
FragColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
性能的缺乏消失了

这意味着如果SSAO代码被正确执行(我试图在执行过程中放置一些断点来检查它),并且我不会在最后使用这个遮挡因子来填充最终的输出颜色,那么性能就不会下降

我认为我们可以得出结论,问题不在于“FragColor=vec4(OcclusionFactor);”行之前的着色器代码。。。我想

你怎么解释这种行为

我在客户端代码和片段着色器代码中尝试了大量代码组合,但我找不到解决此问题的方法!我真的迷路了


非常感谢您的帮助

简单的答案是缓存效率

为了理解这一点,让我们看一下内部循环中的以下行:

    vec4 Sample_VS = vec4(Origin_VS + OrientMatrix * SSAOKernel[idx], 1.0f); //Sample translated in view space

    vec4 Sample_HS = ProjMatrix * Sample_VS; //Sample in homogeneus space
    vec3 Sample_CS = Sample_HS.xyz /= Sample_HS.w; //Perspective dividing (clip space)
    vec2 texOffset = Sample_CS.xy * 0.5f + 0.5f; //Recover sample texture coordinates

    vec3 SampleDepth_VS = texture(PositionSampler, texOffset).xyz; //Sample depth in view space
你在这里做的是:

  • 在视图空间中平移原始点
  • 将其变换为剪辑空间
  • 对纹理进行采样
  • 那么,这与缓存效率是如何对应的呢

    缓存在访问相邻像素时工作良好。例如,如果您使用的是高斯模糊,则仅访问邻居,这些邻居很可能已经加载到缓存中

    假设你的目标现在非常遥远。然后,在剪辑空间中采样的像素也非常接近原始点->高局部性->良好的缓存性能

    如果摄影机非常靠近对象,则生成的采样点会离得更远(在剪辑空间中),并且您将获得随机内存访问模式。这将大大降低您的性能,尽管您实际上没有执行更多操作

    编辑:

    为了提高性能,可以从上一个过程的深度缓冲区重建视图空间位置

    如果您使用的是32位深度缓冲区,它将一个样本所需的数据量从12字节减少到4字节

    位置重组如下所示:

    vec4 reconstruct_vs_pos(vec2 tc){
      float depth = texture(depthTexture,tc).x;
      vec4 p = vec4(tc.x,tc.y,depth,1) * 2.0f + 1.0f; //tranformed to unit cube [-1,1]^3
      vec4 p_cs = invProj * p; //invProj: inverse projection matrix (pass this by uniform)
      return p_cs / p_cs.w;
    }
    

    在进行此操作时,可以进行的另一个优化是以较小的大小渲染SSAO纹理,最好是主视口大小的一半。如果执行此操作,请确保将深度纹理复制到另一个半尺寸纹理(glBlitFramebuffer)并从中采样位置。我希望这能将性能提高一个数量级,特别是在您给出的最坏情况下。

    Ok。因此,问题应该来自与缓存相关的纹理采样。实际上,位置采样器是一个32位纹理(GL_RGB32F)。我选择这种格式是因为我需要存储法线不需要的位置(GL_RGB就足够了)。你认为我使用这样的纹理格式对纹理采样和缓存效率更差吗?无论如何,对于我来说,下一步不是将位置存储在RGB32F纹理中,而是将线性深度存储到RGB纹理中,并将位置直接重建到片段着色器中。你怎么看?是的,这个职位应该会有帮助。我在答案中添加了这个。非常感谢您提供完整的答案。再见。通过消除零矩阵元素,可以大大加快视图空间位置重建的速度。“我希望这能将性能提高一个数量级”,更像是降低分辨率。半分辨率=4倍更少像素=4倍更快尽管如此,因为由于dari解释的原因,纹理采样也可能更快。既然你是ALU有限公司的。少4倍像素=少4倍计算。这并不能使它快10倍。我已经实现了MSSAO(它使用了不同分辨率的多层),它正好展示了我刚才描述的行为。当然,我假设您的全分辨率SSAO没有使用非常愚蠢的缓存抖动采样模式。如果真是这样,lowres版本也会受到影响。