Directional Light Shadow Mapping Issues

Started by
9 comments, last by Geri 3 years, 3 months ago

So I've been trying to re-implement shadow mapping in my engine using directional lights, but I have to throw shade on my progress so far (see what I did there?).

Anyways, I had it working in a previous commit a while back but refactored the engine and am trying to redo some of the shadow mapping. Wouldn't say I'm the best in terms of drawing shadows so thought I'd come on here for some help.

Basically my issue seems to stem from the calculation of the light space matrix (seems a lot of people have the same issue). Initially I had a hardcoded projection matrix and simple view matrix for the light like this

void ZLight::UpdateLightspaceMatrix()
{
    // …
   if (type == ZLightType::Directional) {
       auto lightDir = normalize(glm::eulerAngles(Orientation()));
       glm::mat4 lightV = glm::lookAt(lightDir, glm::vec3(0.f), WORLD_UP);
       glm::mat4 lightP = glm::ortho(-50.f, 50.f, -50.f, 50.f, -100.f, 100.f);
       lightspaceMatrix_ = lightP * lightV;
   }
    // …
}

This then gets passed unmodified as a shader uniform, with which I multiply the vertex world space positions by. A few months ago this was working but with the recent refactor I did on the engine it no longer shows anything. The output to the shadow map looks like this

And my scene isn't showing any shadows, at least not where it matters

Aside from this, after hours of scouring posts and articles about how to implement a dynamic frustrum for the light that will encompass the scene's contents at any given time, I also implemented a simple solution based on transforming the camera's frustum into light space, using an NDC cube and transforming it with the inverse camera VP matrix, and computing a bounding box from the result, which gets passed in to glm::ortho to make the light's projection matrix

void ZLight::UpdateLightspaceMatrix()
{
    static std::vector <glm::vec4> ndcCube = {
        glm::vec4{ -1.0f, -1.0f, -1.0f, 1.0f },
        glm::vec4{ 1.0f, -1.0f, -1.0f, 1.0f },
        glm::vec4{ -1.0f, 1.0f, -1.0f, 1.0f },
        glm::vec4{ 1.0f, 1.0f, -1.0f, 1.0f },
        glm::vec4{ -1.0f, -1.0f, 1.0f, 1.0f },
        glm::vec4{ 1.0f, -1.0f, 1.0f, 1.0f },
        glm::vec4{ -1.0f, 1.0f, 1.0f, 1.0f },
        glm::vec4{ 1.0f, 1.0f, 1.0f, 1.0f }
    };
    if (type == ZLightType::Directional) {
        auto activeCamera = Scene()->ActiveCamera();

        auto lightDir = normalize(glm::eulerAngles(Orientation()));
        glm::mat4 lightV = glm::lookAt(lightDir, glm::vec3(0.f), WORLD_UP);

        lightspaceRegion_ = ZAABBox();
        for (const auto& corner : ndcCube) {
            auto invVPMatrix = glm::inverse(activeCamera->ProjectionMatrix() * activeCamera->ViewMatrix());
            auto transformedCorner = lightV * invVPMatrix * corner;
            transformedCorner /= transformedCorner.w;
            lightspaceRegion_.minimum.x = glm::min(lightspaceRegion_.minimum.x, transformedCorner.x);
            lightspaceRegion_.minimum.y = glm::min(lightspaceRegion_.minimum.y, transformedCorner.y);
            lightspaceRegion_.minimum.z = glm::min(lightspaceRegion_.minimum.z, transformedCorner.z);
            lightspaceRegion_.maximum.x = glm::max(lightspaceRegion_.maximum.x, transformedCorner.x);
            lightspaceRegion_.maximum.y = glm::max(lightspaceRegion_.maximum.y, transformedCorner.y);
            lightspaceRegion_.maximum.z = glm::max(lightspaceRegion_.maximum.z, transformedCorner.z);
        }

        glm::mat4 lightP = glm::ortho(lightspaceRegion_.minimum.x, lightspaceRegion_.maximum.x,
            lightspaceRegion_.minimum.y, lightspaceRegion_.maximum.y,
            -lightspaceRegion_.maximum.z, -lightspaceRegion_.minimum.z);

        lightspaceMatrix_ = lightP * lightV;
    }
}

What results is the same output in my scene (no shadows anywhere) and the following shadow map

I've checked the light space matrix calculations over and over, and tried tweaking values dozens of times, including all manner of lightV matrices using the glm::lookAt function, but I never get the desired output.

For more reference, here's my shadow vertex shader

#version 450 core

#include "Shaders/common.glsl" //! #include "../common.glsl"

layout (location = 0) in vec3 position;
layout (location = 5) in ivec4 boneIDs;
layout (location = 6) in vec4 boneWeights;
layout (location = 7) in mat4 instanceM;

uniform mat4 P_lightSpace;
uniform mat4 M;
uniform mat4 Bones[MAX_BONES];
uniform bool rigged = false;
uniform bool instanced = false;

void main()
{
vec4 pos = vec4(position, 1.0);
   if (rigged) {
    mat4 boneTransform = Bones[boneIDs[0]] * boneWeights[0];
 boneTransform += Bones[boneIDs[1]] * boneWeights[1];
 boneTransform += Bones[boneIDs[2]] * boneWeights[2];
 boneTransform += Bones[boneIDs[3]] * boneWeights[3];

       pos = boneTransform * pos;
   }

   gl_Position = P_lightSpace * (instanced ? instanceM : M) * pos;
}

my soft shadow implementation

float PCFShadow(VertexOutput vout, sampler2D shadowMap) {
 vec3 projCoords = vout.FragPosLightSpace.xyz / vout.FragPosLightSpace.w;
 if (projCoords.z > 1.0)
   return 0.0;

 projCoords = projCoords * 0.5 + 0.5;

 // PCF
 float shadow = 0.0;
 float bias = max(0.05 * (1.0 - dot(vout.FragNormal, vout.FragPosLightSpace.xyz - vout.FragPos.xzy)), 0.005);  
 for (int i = 0; i < 4; ++i) {
   float z = texture(shadowMap, projCoords.xy + poissonDisk[i]).r;
   shadow += z < (projCoords.z - bias) ? 1.0 : 0.0;
 }
 return shadow / 4;
}
...
...
float shadow = PCFShadow(vout, shadowSampler0);
vec3 color = (ambient + (1.0 - shadow) * (diffuse + specular)) + materials[materialIndex].emission;
FragColor = vec4(color, albd.a);

and my camera view and projection matrix getters

glm::mat4 ZCamera::ProjectionMatrix()
{
   glm::mat4 projectionMatrix(1.f);

   auto scene = Scene();
   if (!scene) return projectionMatrix;

   if (cameraType_ == ZCameraType::Orthographic)
   {
       float zoomInverse_ = 1.f / (2.f * zoom_);
       glm::vec2 resolution = scene->Domain()->Resolution();
       float left = -((float)resolution.x * zoomInverse_);
       float right = -left;
       float bottom = -((float)resolution.y * zoomInverse_);
       float top = -bottom;
       projectionMatrix = glm::ortho(left, right, bottom, top, -farClippingPlane_, farClippingPlane_);
   }
   else
   {
       projectionMatrix = glm::perspective(glm::radians(zoom_),
           (float)scene->Domain()->Aspect(),
           nearClippingPlane_, farClippingPlane_);
   }
   return projectionMatrix;
}

glm::mat4 ZCamera::ViewMatrix()
{
   return glm::lookAt(Position(), Position() + Front(), Up());
}

Been trying all kinds of small changes but I still don't get correct shadows. Don't know what I'm doing wrong here. The closest I've gotten is by scaling lightspaceRegion_ bounds by a factor of 10 in the lightspace matrix calculations (only in X and Y) but the shadows are still nowhere near correct.

Any help here would be much appreciated. Let me know if I left out any important code or info.

EDIT: Camera near and far clipping planes are set at reasonable values (0.01 and 100.0, respectively), camera zoom is 45.0 degrees and scene→Domain()→Aspect() just returns the width/height aspect ratio of the framebuffer's resolution.

Advertisement

my ultimate hint is to put random data as your shadow map and see if it shows anything

(also no offense but your code looks terrifying, oop usage in code snippets like this are guarantee of shady quality. )

@Geri thanks for the hint, might try that to see if I get results in the scene, though I suspect it has more to do with the light space matrix than anything. Just not too sure how to get the right tightening of the bounds yet.

Not sure how this guarantees shady quality. My code base has gotten quite large and OOP def helps with reusaiblity and readability for when I have to get back to it after having stepped away for a while. All the pointer and vtable indirection is definitely not great on performance, which is something I plan to optimize in the future, but so far it's worked wonders for me, though I do admit I might've gone on a frenzy and posted a lot more code than I should've here.

well.. actually, when you calculate shadow map, you will have to flip and rearrange some of your matrix values in the matrix after the calculation, i dont know if you do it (maybe that is the problem right now) or where you do it, and how, but it already defeats the oop conception about reusable code because you have to do it with raw array magic.

in my oppinion (you dont have to agree, its your code, you do what you want) you didnt made your task simpler, shorter, just harder and longer, by incorporating oop clownery based on popular propagandistic disbeliefs and judging from these code snippets it seems your code is already 10x longer than it should and the readibility is decreased to zero. i am sure oop could be done properly, but people never do it properly (especially its evangelists spectacularly suck at coding).

Lol that's alright @Geri does make a point. No code base is ever perfect, especially long OOP code bases that abstract away a lot of detail. It's understandable.

@geri which matrix values do you mean specifically? The relevant matrices in this scenario are in the first two code snippets of this post, but I guess that's where the issues lies. I'm having trouble seeing how those light space matrices are miscalculated.

i digged out my old source codes, and its

swap values in your matrix at indexes:

1 with 4,

2 with 8

3 with 12,

6 with 9

7 with 13

11 with 14

and then it goes to glTexGenfv in s t r q gen modes (at least thats how it worked in the fixed function era)

So after days of bashing my head against this one, I finally figured out what was going on. My encompassing frustum and light space matrix calculations were correct, but after tweaking the shadow bias in the shadow sampling shader function a bit, and progressively making the bias smaller and smaller, from something like this

    float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);

To something like this

    float bias = max(0.002 * (1.0 - dot(normal, lightDir)), 0.0002);

The shadows started coming into view, albeit in a terribly blocky fashion, due to the size of the light's bounding view frustum (very large) relative to the total resolution of my shadow map (apparently not enough pixels).

I guess it's time for some CSM ¯\_(ツ)_/¯

in my old code, bias is 0.5. maybe you should try with bigger values as well.

@geri Yeah maybe. I'll play around with the bias values again once I get CSM working. I appreciate the help though had to tweak my matrix and shadow map values a bunch to realize that wasn't the issue.

This topic is closed to new replies.

Advertisement