为什么 VSM 深度图模糊会产生奇怪的结果?

Why does VSM Depth Map Blurring produces strange results?

我正在尝试使用 OpenGL 在我的渲染引擎中为定向阴影实施方差阴影映射。

我已经阅读了多篇文章,例如 - https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-8-summed-area-variance-shadow-maps, https://graphics.stanford.edu/~mdfisher/Shadows.html 来开发这个。

算法的基本流程如下:

  1. 在深度纹理中存储深度和深度^2。
  2. 应用具有 5 x 5 内核和 10 遍的两遍高斯模糊。
  3. 采样深度值,计算片段与光的距离,
  4. 将它们放入切比雪夫不等式来确定片段在阴影中的最大概率
  5. 使用结果使片段变暗。

这是我的具有正交投影矩阵的定向光的深度着色器:

#version 440 core

uniform float farPlane;
uniform vec3 lightPos;
uniform mat4 directional_light_space_matrix;

in vec4 FragPos;
out vec2 depth;

void main()
{   

     vec4 FragPosLightSpace = directional_light_space_matrix * FragPos;
     float d = FragPosLightSpace.z / FragPosLightSpace.w;

     d = d * 0.5 + 0.5;

     float m1 = d;
     float m2 = d * d;

     float dx = dFdx(depth.x);
     float dy = dFdx(depth.y);

     m2 += 0.25 * (dx * dx + dy * dy);

     depth.r = m1;
     depth.g = m2;

}

这是片段着色器的片段,它检查片段被照亮的程度。

float linstep(float mi, float ma, float v)
{
    return clamp ((v - mi)/(ma - mi), 0, 1);
}

float ReduceLightBleeding(float p_max, float Amount) 
{
     return linstep(Amount, 1, p_max); 
} 

float chebyshevUpperBound(float dist, vec2 moments)
{
    float p_max;
    if(dist <= moments.x)
    {
         return 1.0;
    }

    float variance = moments.y - (moments.x * moments.x);
    variance = max(variance, 0.1);
    float d = moments.x - dist;

    p_max = variance / (variance + d * d);

    return ReduceLightBleeding(p_max, 1.0);
}

float CheckDirectionalShadow(float bias, vec3 lightpos, vec3 FragPos)
{       
     vec3 projCoords = FragPosLightSpace.xyz / FragPosLightSpace.w;
     projCoords = projCoords * 0.5 + 0.5;

     vec2 closest_depth = texture(shadow_depth_map_directional, projCoords.xy).rg;

     return chebyshevUpperBound(projCoords.z, closest_depth);
}

这是二次高斯模糊着色器。

#version 440 core

layout (location = 0) out vec2 out_1;

in vec2 TexCoords;

uniform sampler2D inputTexture_1;

uniform bool horizontal;
float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);

void main()
{
    vec2 tex_offset = 1.0 / textureSize(inputTexture_1,0);
    vec2 o1 = texture(inputTexture_1, TexCoords).rg * weights[0];

    if(horizontal)
    {
        for(int i=1; i<4; i++)
        {
            o1 += texture(inputTexture_1, TexCoords + vec2(tex_offset.x * i, 0.0)).rg * weights[i];
            o1 += texture(inputTexture_1, TexCoords - vec2(tex_offset.x * i, 0.0)).rg * weights[i];
        }
    }
    else
    {
        for(int i=1; i<4; i++)
        {
            o1 += texture(inputTexture_1, TexCoords + vec2(0.0, tex_offset.y * i)).rg * weights[i];
            o1 += texture(inputTexture_1, TexCoords - vec2(0.0, tex_offset.y * i)).rg * weights[i];
        }
    }

    out_1 = o1;
}

我将我的帧缓冲区生成代码放入有关我如何存储时刻的信息。

// directional ----------------------------------------------------------------------------------------------------------------------------------------------
glGenFramebuffers(1, &directional_shadow_framebuffer);

glGenTextures(1, &directional_shadow_framebuffer_depth_texture);
glBindTexture(GL_TEXTURE_2D, directional_shadow_framebuffer_depth_texture); 

glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, shadow_map_width, shadow_map_height, 0, GL_RG, GL_FLOAT, NULL);

float border_color[] = { 0.0f,0.0f,0.0f,1.0f };

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);

glBindFramebuffer(GL_FRAMEBUFFER, directional_shadow_framebuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, directional_shadow_framebuffer_depth_texture, 0);

glGenRenderbuffers(1, &directional_shadow_framebuffer_renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, directional_shadow_framebuffer_renderbuffer);

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, shadow_map_width, shadow_map_height);

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, directional_shadow_framebuffer_renderbuffer);


if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    LOGGER->log(ERROR, "Renderer : createShadowMapBuffer", "Directional Shadow Framebuffer is incomplete!");

glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// ----------------------------------------------------------------------------------------------------------------------------------------------

上述操作的结果与预期相去甚远。我没有得到柔和的半影阴影,而是得到了像锐利阴影一样的斑点。

这是第一个时刻(深度)的样子,第二个时刻几乎相同但更暗。

我已经尝试用最小方差、阴影内核大小、高斯样本、模糊通道进行试验。但我还没有接近解决方案。

我感觉我在上面给出的帧缓冲区生成代码中设置纹理过滤参数的方式可能有问题。

我最后的问题是:

  1. 我的 VSM 实现不正确吗?
  2. 为什么我看不到柔和的半影?
  3. 我对纹理的过滤方式感觉不太好,Framebuffer 生成代码有什么问题吗?

所以,我已经解决了这个问题。

实施非常好,但 ReduceLightBleeding 的最小方差和数量参数需要调整。

我发现减小最小方差参数会使阴影更加柔和,但会大大增加 Light Bleeding。 为了应对这种副作用,我们可以将 p_max 值调整为在低于某个阈值时变为 0,否则在 0 和 1 之间重新调整。这正是 ReduceLightBleeding 函数所做的,在上面链接的同一站点中也有描述. 但是,增加 ReduceLightBleeding 中的 amount 参数会使阴影看起来像斑点,这可以在我上面发布的屏幕截图中看到。

我设法调整了最小方差和光出血减少量以找到最佳点。然而,我永远无法完全摆脱这个神器。

方差阴影映射的更好替代方法是它的扩展 - Exponential Variance Shadow Maps

我没有正确理解数学,但我仍然很容易地实现了它。 在 gamedev.stackexchange 上查看此问题以获取提示 - EVSM.

ESVM 做得很好,将出血减少到可以不被注意或忽略的程度。