简体   繁体   English

计算阴影映射的紧密正交投影矩阵

[英]Calculating tight ortho projection matrix for shadow mapping

I'm trying to calculate tight ortho projection around the camera for better shadow mapping.我正在尝试计算相机周围的紧密正交投影以获得更好的阴影映射。 I'm first calculating the camera frustum 8 points in world space using basic trigonometry using fov, position, right, forward, near, and far parameters of the camera as follows:我首先使用 fov、position、相机的右、前、近和远参数使用基本三角法计算世界空间中的相机平截头体 8 点,如下所示:

PerspectiveFrustum::PerspectiveFrustum(const Camera* camera)
{
    float height = tanf(camera->GetFov() / 2.0f) * camera->GetNear();
    float width = height * Screen::GetWidth() / Screen::GetHeight();
    glm::vec3 nearTop = camera->GetUp() * camera->GetNear() * height;
    glm::vec3 nearRight = camera->GetRight() * camera->GetNear() * width;
    glm::vec3 nearCenter = camera->GetEye() + camera->GetForward() * camera->GetNear();
    glm::vec3 farTop = camera->GetUp() * camera->GetFar() * height;
    glm::vec3 farRight = camera->GetRight() * camera->GetFar() * width;
    glm::vec3 farCenter = camera->GetEye() + camera->GetForward() * camera->GetFar();
    m_RightNearBottom = nearCenter + nearRight - nearTop;
    m_RightNearTop = nearCenter + nearRight + nearTop;
    m_LeftNearBottom = nearCenter - nearRight - nearTop;
    m_LeftNearTop = nearCenter - nearRight + nearTop;
    m_RightFarBottom = farCenter + nearRight - nearTop;
    m_RightFarTop = farCenter + nearRight + nearTop;
    m_LeftFarBottom = farCenter - nearRight - nearTop;
    m_LeftFarTop = farCenter - nearRight + nearTop;
}

Then I calculate the frustum in light view and calculating the min and max point in each axis to calculate the bounding box of the ortho projection as follows:然后我计算光视图中的截锥体并计算每个轴上的最小和最大点以计算正射投影的边界框,如下所示:

inline glm::mat4 GetView() const
{
    return glm::lookAt(m_Position, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
}

glm::mat4 DirectionalLight::GetProjection(const Camera& camera) const
{
    PerspectiveFrustum frustum = camera.GetFrustum();
    glm::mat4 lightView = GetView();
    std::array<glm::vec3, 8> frustumToLightView
    {
        lightView * glm::vec4(frustum.m_RightNearBottom, 1.0f),
        lightView * glm::vec4(frustum.m_RightNearTop, 1.0f),
        lightView * glm::vec4(frustum.m_LeftNearBottom, 1.0f),
        lightView * glm::vec4(frustum.m_LeftNearTop, 1.0f),
        lightView * glm::vec4(frustum.m_RightFarBottom, 1.0f),
        lightView * glm::vec4(frustum.m_RightFarTop, 1.0f),
        lightView * glm::vec4(frustum.m_LeftFarBottom, 1.0f),
        lightView * glm::vec4(frustum.m_LeftFarTop, 1.0f)
    };

    glm::vec3 min{ INFINITY, INFINITY, INFINITY };
    glm::vec3 max{ -INFINITY, -INFINITY, -INFINITY };
    for (unsigned int i = 0; i < frustumToLightView.size(); i++)
    {
        if (frustumToLightView[i].x < min.x)
            min.x = frustumToLightView[i].x;
        if (frustumToLightView[i].y < min.y)
            min.y = frustumToLightView[i].y;
        if (frustumToLightView[i].z < min.z)
            min.z = frustumToLightView[i].z;

        if (frustumToLightView[i].x > max.x)
            max.x = frustumToLightView[i].x;
        if (frustumToLightView[i].y > max.y)
            max.y = frustumToLightView[i].y;
        if (frustumToLightView[i].z > max.z)
            max.z = frustumToLightView[i].z;
    }
    return glm::ortho(min.x, max.x, min.y, max.y, min.z, max.z);
}

Doing this gives me empty shadow map, so something clearly wrong and I haven't being doing this right.这样做给了我空的影子 map,所以显然是错误的,我没有做对。 Can someone help me by telling me what I'm doing wrong and why?有人可以告诉我我做错了什么以及为什么吗?

EDIT: As said my calculations of the frustum were wrong and I've changed them to the following:编辑:如前所述,我对截锥体的计算是错误的,我已将它们更改为以下内容:

PerspectiveFrustum::PerspectiveFrustum(const Camera* camera)
{
    float nearHalfHeight = tanf(camera->GetFov() / 2.0f) * camera->GetNear();
    float nearHalfWidth = nearHalfHeight * Screen::GetWidth() / Screen::GetHeight();
    float farHalfHeight = tanf(camera->GetFov() / 2.0f) * camera->GetFar();
    float farHalfWidth = farHalfHeight * Screen::GetWidth() / Screen::GetHeight();

    glm::vec3 nearCenter = camera->GetEye() + camera->GetForward() * camera->GetNear();
    glm::vec3 nearTop = camera->GetUp() * nearHalfHeight;
    glm::vec3 nearRight = camera->GetRight() * nearHalfWidth;

    glm::vec3 farCenter = camera->GetEye() + camera->GetForward() * camera->GetFar();
    glm::vec3 farTop = camera->GetUp() * farHalfHeight;
    glm::vec3 farRight = camera->GetRight() * farHalfWidth;

    m_RightNearBottom = nearCenter + nearRight - nearTop;
    m_RightNearTop = nearCenter + nearRight + nearTop;
    m_LeftNearBottom = nearCenter - nearRight - nearTop;
    m_LeftNearTop = nearCenter - nearRight + nearTop;
    m_RightFarBottom = farCenter + farRight - farTop;
    m_RightFarTop = farCenter + farRight + farTop;
    m_LeftFarBottom = farCenter - farRight - farTop;
    m_LeftFarTop = farCenter - farRight + farTop;
}

Also flipped the z coordinates when creating the ortho projection as follows:在创建正射投影时还翻转了z坐标,如下所示:

return glm::ortho(min.x, max.x, min.y, max.y, -min.z, -max.z);

Yet still nothing renders to the depth map.然而仍然没有渲染到深度 map。 Any ideas?有任何想法吗?
Here's captured results as you can see top left corner quad shows the shadow map which is completely wrong even drawing shadows on the objects themselves as a result as can be seen: https://gfycat.com/brightwealthybass这是捕获的结果,您可以看到左上角四边形显示阴影 map,即使在对象本身上绘制阴影也是完全错误的,因此可以看出: https://gfycat.com/brightwealthybass

(The smearing of the shadow map values is just an artifact of the gif compresser I used it doesn't really happen so there's no problem of me not clearing the z-buffer of the FBO) (阴影 map 值的涂抹只是我使用的 gif 压缩器的一个伪影,它并没有真正发生,所以我没有清除 FBO 的 z 缓冲区没有问题)

EDIT2:: Ok few things GetFov() returned degrees and not radians.. changed it. EDIT2::好的, GetFov()返回度数而不是弧度.. 改变了它。 I Also try the transformation from NDC to world space with the following code:我还尝试使用以下代码从 NDC 转换到世界空间:

    glm::mat4 inverseProjectViewMatrix = glm::inverse(camera.GetProjection() * camera.GetView());

    std::array<glm::vec4, 8> NDC =
    {
        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},
    };

    for (size_t i = 0; i < NDC.size(); i++)
    {
        NDC[i] = inverseProjectViewMatrix * NDC[i];
        NDC[i] /= NDC[i].w;
    }

For the far coordinates of the frustum they're equal to my calculation of the frustum, but for the near corners they're off as if my calculation of the near corners is halved by 2 (for x and y only).对于截锥体的远坐标,它们等于我对截锥体的计算,但对于近角,它们是关闭的,好像我对近角的计算减半了 2(仅适用于 x 和 y)。 For example: RIGHT TOP NEAR CORNER: my calculation yields - {0.055, 0.041, 2.9} inverse NDC yields - {0.11, 0.082, 2.8}例如: RIGHT TOP NEAR CORNER:我的计算收益率 - {0.055, 0.041, 2.9}反向 NDC 收益率 - {0.11, 0.082, 2.8}

So I'm not sure where my calculation got wrong, maybe you could point out?所以我不确定我的计算哪里出错了,也许你能指出来? Even with the inversed NDC coordinates I tried to use them as following:即使使用反转的 NDC 坐标,我也尝试按如下方式使用它们:

glm::mat4 DirectionalLight::GetProjection(const Camera& camera) const
{
    glm::mat4 lightView = GetView();

    glm::mat4 inverseProjectViewMatrix = glm::inverse(camera.GetProjection() * camera.GetView());

    std::array<glm::vec4, 8> NDC =
    {
        glm::vec4{-1.0f, -1.0f, 0.0f, 1.0f},
        glm::vec4{1.0f, -1.0f, 0.0f, 1.0f},
        glm::vec4{-1.0f, 1.0f, 0.0f, 1.0f},
        glm::vec4{1.0f, 1.0f, 0.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},
    };

    for (size_t i = 0; i < NDC.size(); i++)
    {
        NDC[i] = lightView * inverseProjectViewMatrix * NDC[i];
        NDC[i] /= NDC[i].w;
    }

    glm::vec3 min{ INFINITY, INFINITY, INFINITY };
    glm::vec3 max{ -INFINITY, -INFINITY, -INFINITY };
    for (unsigned int i = 0; i < NDC.size(); i++)
    {
        if (NDC[i].x < min.x)
            min.x = NDC[i].x;
        if (NDC[i].y < min.y)
            min.y = NDC[i].y;
        if (NDC[i].z < min.z)
            min.z = NDC[i].z;

        if (NDC[i].x > max.x)
            max.x = NDC[i].x;
        if (NDC[i].y > max.y)
            max.y = NDC[i].y;
        if (NDC[i].z > max.z)
            max.z = NDC[i].z;
    }
    return glm::ortho(min.x, max.x, min.y, max.y, min.z, max.z);
}

And still got bad result: https://gfycat.com/negativemalealtiplanochinchillamouse结果仍然很糟糕: https://gfycat.com/negativemalealtiplanochinchillamouse

Let's start with your frustum calculation here:让我们从您的平截头体计算开始:

 float height = tanf(camera->GetFov() / 2.0f) * camera->GetNear(); [...] glm::vec3 nearTop = camera->GetUp() * camera->GetNear() * height; [...] glm::vec3 farTop = camera->GetUp() * camera->GetFar() * height;

That's one to many GetNear in your multiplications.这是您乘法中的一对多GetNear Conceptually, you could height represent half of the frustum height at unit distance (I still would name it differently) without projecting it to the near plane, then the rest of your formulas make more sense.从概念上讲,您可以height表示单位距离处截锥体高度的一半(我仍然会以不同的方式命名)而不将其投影到近平面,那么公式的 rest 更有意义。

However, the whole approach is doubtful to begin with.然而,整个方法一开始是值得怀疑的。 To get the frustum corners in world space, you can simply unproject all 8 vertices of the [-1,1]^3 NDC cube.要获得世界空间中的平截头体角,您可以简单地取消投影[-1,1]^3 NDC 立方体的所有 8 个顶点。 Since you want to transform that into your light space, you can even combine it to a single matrix m = lightView * inverse(projection * view) , just don't forget the perspective divide after the multiplying the NDC cube vertices.由于您想将其转换为您的光照空间,您甚至可以将其组合为单个矩阵m = lightView * inverse(projection * view) ,只是不要忘记乘以 NDC 立方体顶点后的透视除法。

 return glm::ortho(min.x, max.x, min.y, max.y, min.z, max.z);

Standard GL conventions use a view space where the camera is looking into negative z direction, but the zNear and zFar parameters are interpreted as distances along the viewing directions, so the actual viewing volume will range from -zFar, -zNear in view space.标准 GL 约定使用摄像机朝向z 方向的视图空间,但zNearzFar参数被解释为沿观察方向的距离,因此实际观察体积的范围为-zFar, -zNear在视图空间中。 You'll have to flip the signs of your z dimension to get the actual bounding box you're looking for.您必须翻转z维度的符号才能获得您正在寻找的实际边界框。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM