Расчет матрицы точной ортопроекции для карт теней

Я пытаюсь рассчитать плотную орто-проекцию вокруг камеры для лучшего картографирования теней. Сначала я вычисляю 8 точек усечения камеры в мировом пространстве, используя базовую тригонометрию, используя следующие параметры камеры: fov, position, right, forward, near и far:

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, который я использовал, на самом деле этого не происходит, поэтому нет проблем с тем, что я не очистил z-буфер FBO)

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

Для дальних координат усеченной вершины они равны моим расчетам усеченной вершины, но для ближних углов они отключены, как если бы мой расчет ближайших углов уменьшился вдвое (только для 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


person Jorayen    schedule 09.04.2020    source источник


Ответы (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 представить половину высоты усеченной пирамиды на единичном расстоянии (я бы все же назвал это по-другому), не проецируя ее на ближнюю плоскость, тогда остальные ваши формулы будут иметь больше смысла.

Однако весь подход вначале сомнительный. Чтобы получить углы усеченной пирамиды в мировом пространстве, вы можете просто спроецировать все 8 вершин куба [-1,1]^3 NDC. Поскольку вы хотите преобразовать это в свое световое пространство, вы даже можете объединить его в одну матрицу m = lightView * inverse(projection * view), просто не забудьте разделить перспективу после умножения вершин куба NDC.

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

Стандартные соглашения GL используют пространство обзора, в котором камера смотрит в отрицательное направление по оси Z, но параметры zNear и zFar интерпретируются как расстояния по направлениям обзора, поэтому фактический объем обзора будет варьироваться от -zFar, -zNear в поле зрения. Космос. Вам придется перевернуть знаки вашего z измерения, чтобы получить фактическую ограничивающую рамку, которую вы ищете.

person derhass    schedule 09.04.2020
comment
Привет спасибо! Я отредактировал свой ответ, чтобы отразить те изменения, которые я внес на основе вашего ответа. Также я знаю о методе обратного преобразования матрицы проекции вида на НЦД. Не могли бы вы описать преимущества, если таковые имеются, от использования одного метода для другого для получения координат усеченной пирамиды? - person Jorayen; 09.04.2020
comment
Он работает из коробки с любой матрицей проекции - перпективной, ортогональной и т. Д. И не зависит от каких-либо соглашений о пространстве обзора. - person derhass; 09.04.2020
comment
Попался действительный момент. Я все еще хочу выяснить, что не так с моим решением, поскольку оно все еще не работает. Я обновил вопрос, был бы признателен, если бы вы могли взглянуть. - person Jorayen; 09.04.2020