1

我正在尝试计算相机周围的紧密正交投影以获得更好的阴影映射。我首先使用相机的 fov、位置、右、前、近和远参数使用基本三角法计算世界空间中的相机平截头体 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;
}

然后我计算光视图中的截锥体并计算每个轴上的最小和最大点以计算正射投影的边界框,如下所示:

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);
}

这样做会给我空白的阴影贴图,所以显然有些错误,而且我没有做对。有人可以告诉我我做错了什么以及为什么吗?

编辑: 如前所述,我对截锥体的计算是错误的,我已将它们更改为以下内容:

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;
}

在创建正射投影时还翻转了z坐标,如下所示:

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

然而仍然没有渲染到深度图。有任何想法吗?
这是捕获的结果,您可以看到左上角四边形显示了完全错误的阴影贴图,即使在对象本身上绘制阴影,结果也可以看出: https ://gfycat.com/brightwealthybass

(阴影贴图值的拖尾只是我使用的 gif 压缩器的一个伪影,它并没有真正发生,所以我没有清除 FBO 的 z 缓冲区没有问题)

EDIT2:: 好的,很少有东西GetFov()返回度数而不是弧度......改变了它。我还尝试使用以下代码从 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;
    }

对于截锥体的远坐标,它们等于我对截锥体的计算,但对于近角,它们是关闭的,好像我对近角的计算减半了 2(仅适用于 x 和 y)。例如: RIGHT TOP NEAR CORNER:我的计算收益率 -{0.055, 0.041, 2.9} 逆 NDC 收益率 -{0.11, 0.082, 2.8}

所以我不确定我的计算哪里出错了,也许你能指出来?即使使用反转的 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);
}

仍然得到不好的结果: https ://gfycat.com/negativemalealtiplanochinchillamouse

4

1 回答 1

1

让我们从您的平截头体计算开始:

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;

这是你的乘法中的一对多GetNear。从概念上讲,您可以height在单位距离处表示截锥体高度的一半(我仍然会以不同的方式命名)而不将其投影到近平面,然后您的其余公式更有意义。

然而,整个方法一开始是值得怀疑的。要获得世界空间中的平截头体角,您可以简单地取消投影[-1,1]^3NDC 立方体的所有 8 个顶点。由于您想将其转换为您的光照空间,您甚至可以将其组合成一个矩阵m = lightView * inverse(projection * view),只是不要忘记乘以 NDC 立方体顶点之后的透视除法。

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

标准 GL 约定使用相机在z 方向查看的视图空间,但zNearzFar参数被解释为沿查看方向的距离,因此实际查看体积将-zFar, -zNear在视图空间范围内。您必须翻转z尺寸的符号才能获得您正在寻找的实际边界框。

于 2020-04-09T16:47:36.480 回答