通过阴影贴图纹素找到从光世界位置发出的光线

Finding the light ray that goes from light world position through the shadow map texel

我想从基本阴影贴图转向自适应偏置阴影贴图。 我找到了一篇描述如何做的论文,但我不确定如何实现过程中的某个步骤: 这个想法是要有一个平面 P(基本上只是片段着色器阶段中当前片段表面的法线)和片段的世界 space 位置(上图中的 F1)。

为了计算正确的偏差(对抗阴影痤疮)我需要找到 F2 的世界 space 位置,如果我从光源射出一条光线穿过阴影贴图的纹素中心。然后这条射线最终击中平面 P,从而产生所需的点 F2。

现在已知 F1 和 F2,然后我可以计算 F1 和 F2 之间沿光线的距离(我猜),从而获得对抗阴影粉刺的理想偏差。

现在我的基本着色器代码如下所示:

顶点着色器:

in  vec3 aLocalObjectPos;
out vec4 vShadowCoord;
out vec3 vF1;

// to shift the coordinates from [-1;1] to [0;1]
const mat4 biasMatrix = mat4(
    0.5, 0.0, 0.0, 0.0,
    0.0, 0.5, 0.0, 0.0,
    0.0, 0.0, 0.5, 0.0,
    0.5, 0.5, 0.5, 1.0
);

int main()
{
    // get the vertex position in the light's view space:
    vShadowCoord =  (biasMatrix * viewProjShadowMap * modelMatrix) * vec4(aLocalObjectPos, 1.0);
    vF1 = (modelMatrix * vec4(aLocalObjectPos, 1.0)).xyz;
}

片段着色器中的辅助方法:

uniform sampler2DShadow uTextureShadowMap;
float calculateShadow(float bias)
{
    vShadowCoord.z -= bias;
    return textureProjOffset(uTextureShadowMap, vShadowCoord, ivec2(0, 0));
}

我现在的问题是:

我已经找到了这个主题:Adaptive Depth Bias for Shadow Maps Ray Casting

不幸的是没有答案而且我不太明白作者在说什么:-/

所以,我想我自己已经弄明白了。我按照本文中的说明进行操作: http://cwyman.org/papers/i3d14_adaptiveBias.pdf

顶点着色器(那里没有太多进展):

const mat4 biasMatrix = mat4(
    0.5, 0.0, 0.0, 0.0,
    0.0, 0.5, 0.0, 0.0,
    0.0, 0.0, 0.5, 0.0,
    0.5, 0.5, 0.5, 1.0
);

in vec4 aPosition;          // vertex in model's local space (not modified in any way)
uniform mat4 uVPShadowMap;  // light's view-projection matrix
out vec4 vShadowCoord;

void main()
{
    // ...

    vShadowCoord = (biasMatrix * uVPShadowMap * uModelMatrix) * aPosition;
    
    // ...
}

片段着色器:

#version 450

in      vec3 vFragmentWorldSpace; // fragment position in World space
in      vec4 vShadowCoord;        // texture coordinates for shadow map lookup (see vertex shader)

uniform sampler2DShadow uTextureShadowMap;

uniform vec4 uLightPosition; // Light's position in world space
uniform vec2 uLightNearFar;  // Light's zNear and zFar values
uniform float uK;            // variable offset faktor to tweak the computed bias a little bit

uniform mat4 uVPShadowMap;   // light's view-projection matrix

const vec4 corners[2] = vec4[](    // frustum diagonal points in light's view space normalized [-1;+1]
    vec4(-1.0, -1.0, -1.0, 1.0),   // left  bottom near
    vec4( 1.0,  1.0,  1.0, 1.0)    // right top    far
);

float calculateShadowIntensity(vec3 fragmentNormal)
{
    // get fragment's position in light space:
    vec4 fragmentLightSpace = uVPShadowMap * vec4(vFragmentWorldSpace, 1.0);
    vec3 fragmentLightSpaceNormalized = fragmentLightSpace.xyz / fragmentLightSpace.w;              // range [-1;+1]
    vec3 fragmentLightSpaceNormalizedUV = fragmentLightSpaceNormalized * 0.5 + vec3(0.5, 0.5, 0.5); // range [ 0; 1]

    // get shadow map's texture size:
    ivec2 textureDimensions = textureSize(uTextureShadowMap, 0);
    vec2 delta = vec2(textureDimensions.x, textureDimensions.y);
    
    // get width of every texel:
    vec2 textureStep = vec2(1.0 / textureDimensions.x, 1.0 / textureDimensions.y);
    
    // get the UV coordinates of the texel center:
    vec2 fragmentLightSpaceUVScaled = fragmentLightSpaceNormalizedUV.xy * delta;
    vec2 texelCenterUV = floor(fragmentLightSpaceUVScaled) * textureStep + textureStep / 2;
    
    // convert range for texel center in light space in range [-1;+1]:
    vec2 texelCenterLightSpaceNormalized = 2.0 * texelCenterUV - vec2(1.0, 1.0);
    
    // recreate light ray in world space:
    vec4 recreatedVec4 = vec4(texelCenterLightSpaceNormalized.x, texelCenterLightSpaceNormalized.y, -uLightsNearFar.x, 1.0);
    mat4 vpShadowMapInversed = inverse(uVPShadowMap);
    vec4 texelCenterWorldSpace = vpShadowMapInversed * recreatedVec4;
    vec3 lightRayNormalized = normalize(texelCenterWorldSpace.xyz - uLightsPositions.xyz);

    // compute scene scale for epsilon computation:
    vec4 frustum1 = vpShadowMapInversed * corners[0];
    frustum1 = frustum1 / frustum1.w;
    vec4 frustum2 = vpShadowMapInversed * corners[1];
    frustum2 = frustum2 / frustum2.w;

    float ln = uLightNearFar.x;
    float lf = uLightNearFar.y;

    // compute light ray intersection with fragment plane:
    float dotLightRayfragmentNormal = dot(fragmentNormal, lightRayNormalized);
    float d = dot(fragmentNormal, vFragmentWorldSpace);
    float x = (d - dot(fragmentNormal, uLightsPositions.xyz)) / dot(fragmentNormal, lightRayNormalized);
    vec4 intersectionWorldSpace = vec4(uLightsPositions.xyz + lightRayNormalized * x, 1.0);

    // compute bias:
    vec4 texelInLightSpace = uVPShadowMap * intersectionWorldSpace;
    float intersectionDepthTexelCenterUV = (texelInLightSpace.z / texelInLightSpace.w) / 2.0 + 0.5;
    float fragmentDepthLightSpaceUV = fragmentLightSpaceNormalizedUV.z;
    float bias = intersectionDepthTexelCenterUV - fragmentDepthLightSpaceUV;

    float depthCompressionResult = pow(lf - fragmentLightSpaceNormalizedUV.z * (lf - ln), 2.0) / (lf * ln * (lf - ln));
    float epsilon = depthCompressionResult * length(frustum1.xyz - frustum2.xyz) * uK;
    bias += epsilon;

    vec4 shadowCoord = vShadowCoord;
    shadowCoord.z -= bias;

    float shadowValue = textureProj(uTextureShadowMap, shadowCoord);
    return max(shadowValue, 0.0);
}

请注意,这是一个非常冗长的方法(我知道您可以优化几个步骤)以更好地解释我为使其工作所做的工作。 我所有的阴影投射灯都使用透视投影。 我在一个单独的项目中测试了 CPU 方面的结果(只有 c# 和 OpenTK 包中的数学结构),它们看起来很合理。我使用了几个灯光位置、纹理大小等。在我所有的测试中,偏差值看起来都不错。当然,这不是证据,但我对此有一种感觉。

最后:

收益非常小。视觉效果很好(尤其是对于每个维度 >= 2048 个样本的阴影贴图),但我仍然必须为每个场景调整偏移值(片段着色器中的 uniform float uK)。我发现从 0.01 到 0.03 的值可以提供可用的结果。

与我以前的方法(斜率偏差)相比,我损失了大约 10% 的性能(fps-wise),而在阴影(痤疮、彼得平移)方面获得了大约 1% 的视觉保真度。 1% 没有测量 - 只有我感觉到 :-)

我希望这种方法成为“解决所有问题的单一方法”。但我想,在阴影贴图方面没有“一劳永逸”的解决方案;-/