首页 > 解决方案 > 为什么 VSM 深度图模糊会产生奇怪的结果?

问题描述

我正在尝试使用 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. 将深度和 depth^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 生成代码有什么问题吗?

标签: openglshadow-mapping

解决方案


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

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

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

我设法调整了最小方差和轻微出血减少量以找到最佳位置。但是,我永远无法完全摆脱这个神器。

Variance Shadow Mapping 的一个更好的替代方法是它的扩展 - Exponential Variance Shadow Maps

我没有正确理解数学,但我仍然很容易实现它。检查 gamedev.stackexchange 上的这个问题以获取提示 - EVSM

ESVM 通过将出血减少到不能被注意到或被忽略的程度做得很好。


推荐阅读