Point Light Shadow Maps

Hi all,

My renderer currently has PBR shading and shadow maps for one directional light. Now I want to add shadows for point lights as well. The shadows I have so far are based on the examples provided by kosmonautgames in this thread and it’s working great.

For the point lights I have generated cube maps for each light, but I’m just struggling with how to now apply those cube maps. I know it’s similar to what I have for the directional light, I think I just need a bit more code to calculate which face of the cube map to use.

Here’s the relevant parts of my shader: the function that calculates the shadows, and the lighting for the direcitonal and point lights. You can see how I have the shadows working for the directional light already, just not sure how to modify that to use a cube map for the point lights.

//----------------------------------------------------------------------------------
// Calculates the shadow term using PCF
//----------------------------------------------------------------------------------
float CalcShadowTermPCF(float light_space_depth, float ndotl, float2 shadow_coord)
{
    float shadow_term = 0;

    float variableBias = clamp(0.001 * tan(acos(ndotl)), 0, _DepthBias);

    //safe to assume it's a square
    float size = 1 / _ShadowMapSize;
    	
    float samples[4];
    samples[0] = (light_space_depth - variableBias < _ShadowMap.Sample(ShadowMapSampler, shadow_coord).r);
    samples[1] = (light_space_depth - variableBias < _ShadowMap.Sample(ShadowMapSampler, shadow_coord + float2(size, 0)).r);
    samples[2] = (light_space_depth - variableBias < _ShadowMap.Sample(ShadowMapSampler, shadow_coord + float2(0, size)).r);
    samples[3] = (light_space_depth - variableBias < _ShadowMap.Sample(ShadowMapSampler, shadow_coord + float2(size, size)).r);

    shadow_term = (samples[0] + samples[1] + samples[2] + samples[3]) / 4.0;

    return shadow_term;
}

//----------------------------------------------------------------------------------
// Lights
//----------------------------------------------------------------------------------
float3 CalculatePointLights(float3 worldPosition, float3 N, float3 albedo, float metallic, float roughness)
{
    float3 V = normalize(_CameraPos - worldPosition);

    //Calculate surface reflection at zero incidence, default to 0.04 but adjust for metallic surfaces.
    float3 F0 = float3(0.04, 0.04, 0.04);
    F0 = lerp(F0, albedo, metallic);

    //Reflection equation
    float3 Lo = float3(0.0, 0.0, 0.0);
    for(int i = 0; i < MaxPointLights; ++i)
	{
        // calculate per-light radiance
        float3 L = normalize(_PointLightPos[i] - worldPosition);
        float3 H = normalize(V + L);
        float distance = length(_PointLightPos[i] - worldPosition);
        float attenuation = _PointLightRange[i] / (distance * distance);
        float3 radiance = _PointLightColors[i] * attenuation * _PointLightIntensity[i];

        // cook-torrance brdf
        float NDF = DistributionGGX(N, H, roughness);
        float G = GeometrySmith(N, V, L, roughness);
        float3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);

        float3 kS = F;
        float3 kD = float3(1.0, 1.0, 1.0) - kS;
        kD *= 1.0 - metallic;

        float3 numerator = NDF * G * F;
        float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
        float3 specular = numerator / max(denominator, 0.001);

        // add to outgoing radiance Lo
        float NdotL = max(dot(N, L), 0.0);

        //Shadows
        float shadowContribution = 1.0;

        //TO DO: Read shadow maps here
        //_ShadowCubes: One point light's shadow map stored as a cube map
        //_PointLightSpaceMatrix: Array of 6 view projection matrices for the point light

        Lo += (kD * albedo / PI + specular) * radiance * NdotL * shadowContribution;
    }
    return Lo;
}

float3 CalculateDirectionalLights(float3 worldPosition, float3 N, float3 albedo, float metallic, float roughness)
{
    float3 V = normalize(_CameraPos - worldPosition);

    //Calculate surface reflection at zero incidence, default to 0.04 but adjust for metallic surfaces.
    float3 F0 = float3(0.04, 0.04, 0.04);
    F0 = lerp(F0, albedo, metallic);

    //Reflection equation
    float3 Lo = float3(0.0, 0.0, 0.0);
    for(int i = 0; i < MaxDirectionalLights; ++i)
	{
        // calculate per-light radiance
        float3 L = -_DirectionalLights[i];
        float3 H = normalize(V + L);
        float3 radiance = _DirectionalColors[i] * _DirectionalIntensity[i];

        // cook-torrance brdf
        float NDF = DistributionGGX(N, H, roughness);
        float G = GeometrySmith(N, V, L, roughness);
        float3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);

        float3 kS = F;
        float3 kD = float3(1.0, 1.0, 1.0) - kS;
        kD *= 1.0 - metallic;

        float3 numerator = NDF * G * F;
        float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
        float3 specular = numerator / max(denominator, 0.001);

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

        //Shadows
        float shadowContribution = 1.0;

        if(i == _DirectionalShadowIndex)
        {
            float4 lightingPosition = mul(float4(worldPosition, 1), _LightSpaceMatrix);
            float2 shadowTexCoord = mad(0.5, lightingPosition.xy / lightingPosition.w, float2(0.5, 0.5));
            shadowTexCoord.y = 1.0f - shadowTexCoord.y;
            float ourDepth = (lightingPosition.z / lightingPosition.w);
            shadowContribution = CalcShadowTermPCF(ourDepth, NdotL, shadowTexCoord);
        }

        // add to outgoing radiance Lo
        Lo += (kD * albedo / PI + specular) * radiance * NdotL * shadowContribution;
    }
    return Lo;
}

Can anyone provide any guidance?
Thanks