Why shadows are not rendered when PBR directional light is applied?

601 Views Asked by At

I've got a problem with rendering hard shadows in a PBR pipeline. I believe there is something wrong with PBR calculations because with a Blinn-Phong lighting model everything looks fine.

These are lightning calculations - basic PBR

struct DirectionalLight
{
    vec3 direction;
};

layout(std140, binding = 2) uniform Scene
{
    DirectionalLight directionalLight;
    vec3 viewPosition;
} u_scene;

layout(std140, binding = 4) uniform Material
{
    vec4 baseColor;
    float roughness;
    float metalness;
} u_material;

const float PI = 3.14159265359;
const float epsilon = 0.00001;

int lightCount = 1;

vec3 CalculateDirectionalLight(vec3 N, vec3 V, float NdotV, vec3 F0)
{
    vec3 result;
    for(int i = 0; i < lightCount; ++i) {
        vec3 L = normalize(-u_scene.directionalLight.direction);

        float NdotL = max(0.0f, dot(N, L));

        vec3 H = normalize(V + L);
        float NdotH = max(0.0f, dot(N, H));

        vec3 F  = FresnelSchlickRoughness(max(0.0f, dot(H, V)), F0, u_material.roughness);
        float D = NDFGGX(NdotH, u_material.roughness);
        float G = GeometrySmith(NdotL, NdotV, u_material.roughness);

        vec3 kd = (1.0f - F) * (1.0f - u_material.metalness);
        vec3 diffuse = kd * u_material.baseColor.rgb;

        vec3 nominator = F * G * D;
        float denominator = max(epsilon, 4.0f * NdotV * NdotL);
        vec3 specular = nominator / denominator;
        specular = clamp(specular, vec3(0.0f), vec3(10.0f));

        result += (diffuse + specular) /* u_material.radiance */ * NdotL;
    }

    return result;
}

float NDFGGX(float NdotH, float roughness)
{
    float alpha = roughness * roughness;
    float alphaSq = alpha * alpha;

    float denom = (NdotH * NdotH) * (alphaSq - 1.0) + 1.0;
    return alphaSq / (PI * denom * denom);
}

float GeometrySchlickGGX(float Ndot, float k)
{
    float nom   = Ndot;
    float denom = Ndot * (1.0 - k) + k;

    return nom / denom;
}

float GeometrySmith(float NdotL, float NdotV, float roughness)
{    
    float r = (roughness + 1.0f);
    float k = (r * r) / 8.0f;

    float ggx2 = GeometrySchlickGGX(NdotV, k);
    float ggx1 = GeometrySchlickGGX(NdotL, k);

    return ggx1 * ggx2;
}

vec3 FresnelSchlick(float cosTheta, vec3 F0)
{
    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}

vec3 FresnelSchlickRoughness(float cosTheta, vec3 F0, float roughness)
{
    return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - cosTheta, 5.0);
}

shadow functions

layout(binding = 2) uniform sampler2D u_shadowMap;

float ShadowFade = 1.0;

float GetShadowBias()
{
    const float MINIMUM_SHADOW_BIAS = 0.002;
    float bias = max(MINIMUM_SHADOW_BIAS * (1.0 - dot(normalize(v_normal), -normalize(u_scene.directionalLight.direction))), MINIMUM_SHADOW_BIAS);
    return bias;
}

float HardShadows_DirectionalLight(vec4 fragPosLightSpace)
{
    vec3 shadowCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    float bias = GetShadowBias();
    float shadowMapDepth = texture(u_shadowMap, vec2(shadowCoords.xy * 0.5 + 0.5)).r;
    return step(shadowCoords.z, shadowMapDepth + bias) * ShadowFade;
}

and the main function

void main()
{   
    vec3 F0 = vec3(0.04f);
    F0 = mix(F0, u_material.baseColor.rgb, u_material.metalness);
    
    vec3 N = normalize(v_normal);
    vec3 V = normalize(u_scene.viewPosition - v_position);
    float NdotV = max(0.0f, dot(N, V));

    //v_positionFromLight is calculated in a vertex shader like this:
    //v_positionFromLight = u_lightViewProjection * vec4(v_position, 1.0f);
    //where v_position is modelMatrix * a_position;
    //where a_position is a input position of a vertex

    float shadow = HardShadows_DirectionalLight(v_positionFromLight);
    vec3 ambient = u_material.baseColor.rgb * 0.3f;
    vec3 lightContribution = ambient + CalculateDirectionalLight(N, V, NdotV, F0) * shadow;

    f_color = vec4(lightContribution, 1.0);
}

and this is how the scene looks like - there should be visible shadows, but there aren't: enter image description here

I've tested 2 things. First - Blinn-Phong lighting model - shadows render just fine. Second - output shadow calculations without PBR lightning like this:

void main()
{   
    float shadow = HardShadows_DirectionalLight(v_positionFromLight);
    vec3 ambient = u_material.baseColor.rgb * 0.3f;
    f_color = vec4(ambient * shadow, 1.0f);
}

and it also works (besides that they're not placed in a good spot, but that is another topic): enter image description here

Why this PBR model does not work with shadows? How can I fix it?

0

There are 0 best solutions below