Функция Frustum Culling прерывается при взгляде вверх или вниз

Название все объясняет, но подведем итог. Когда направление камеры не направлено вниз или вверх, отсечение Frustum работает нормально (я проверял количество вызовов отрисовки и т. д. через консоль). НО он не работает при взгляде вверх и вниз (похоже, что он не масштабируется должным образом, но я не уверен). Я загрузил это видео здесь, так что вам, вероятно, стоит его посмотреть. https://streamable.com/pjp0nh

Фрустум.h

struct BoundingBox
{
    Vec center;
    Vec extents;
};

struct Plane_t
{
    Vec normal;
    float distance;
};

struct Frustum_t
{
    Plane_t topface;
    Plane_t bottomface;

    Plane_t rightface;
    Plane_t leftface;

    Plane_t farface;
    Plane_t nearface;
};

Frustum_t MakeFrustumFromView(const Vec& pos, const Vec& dir, float fov, float near, float far);

bool isBoundingBoxOnForwardPlane(const BoundingBox& boundingbox, const Plane_t& plane);
bool IsBoundingBoxInFrustum(const BoundingBox& boundingbox, const Frustum_t& frustum, const Mat4& transform);

Фрустум.cpp

inline Plane_t MakeFrustumPlane(const Vec& point, const Vec& norm) 
{ 
    return { norm, norm.Dot(point) }; 
}

Frustum_t MakeFrustumFromView(const Vec& pos, const Vec& dir, float fov, float near, float far)
{
    Vec normdir = dir.Normalize();
    Vec upvec = { 0.0f, 1.0f, 0.0f };

    Vec right = normdir.Cross(upvec).Normalize();
    Vec up = right.Cross(normdir).Normalize();

    Frustum_t frustum;
    const float fHalfVSide = far * tanf(fov * DEG2RAD * 0.5f);
    const float fHalfHSide = fHalfVSide * ((float)g_pGlobals->m_iScreenWidth / g_pGlobals->m_iScreenHeight);
    const Vec fDirFar = normdir * far;
    
    frustum.nearface = MakeFrustumPlane(pos + normdir * near, normdir);
    frustum.farface = MakeFrustumPlane(pos + fDirFar, -normdir);

    frustum.rightface = MakeFrustumPlane(pos, (fDirFar - right * fHalfHSide).Cross(up));
    frustum.leftface = MakeFrustumPlane(pos, up.Cross(fDirFar + right * fHalfHSide));

    frustum.topface = MakeFrustumPlane(pos, right.Cross(fDirFar - up * fHalfVSide));
    frustum.bottomface = MakeFrustumPlane(pos, (fDirFar + up * fHalfVSide).Cross(right));

    return frustum;
}

bool isBoundingBoxOnForwardPlane(const BoundingBox& boundingbox, const Plane_t& plane)
{
    const float r = boundingbox.extents.x * fabsf(plane.normal.x) + boundingbox.extents.y * fabsf(plane.normal.y) + boundingbox.extents.z * fabsf(plane.normal.z);

    return -r <= (plane.normal.Dot(boundingbox.center) - plane.distance);
}

bool IsBoundingBoxInFrustum(const BoundingBox& boundingbox, const Frustum_t& frustum, const Mat4& transform)
{
    Vec4D globalCenter = transform * Vec4D(boundingbox.center, 1.0f);

    Vec bbRight = Vec(transform.m0, transform.m1, transform.m2) * boundingbox.extents.x;
    Vec bbUp = Vec(transform.m4, transform.m5, transform.m6) * boundingbox.extents.y;
    Vec bbForward = -Vec(transform.m8, transform.m9, transform.m10) * boundingbox.extents.z;

    const float newIi = fabsf(Vec(1.0f, 0.0f, 0.0f).Dot(bbRight)) + fabsf(Vec(1.0f, 0.0f, 0.0f).Dot(bbUp)) + fabsf(Vec(1.0f, 0.0f, 0.0f).Dot(bbForward));
    const float newIj = fabsf(Vec(0.0f, 1.0f, 0.0f).Dot(bbRight)) + fabsf(Vec(0.0f, 1.0f, 0.0f).Dot(bbUp)) + fabsf(Vec(0.0f, 1.0f, 0.0f).Dot(bbForward));
    const float newIk = fabsf(Vec(0.0f, 0.0f, 1.0f).Dot(bbRight)) + fabsf(Vec(0.0f, 0.0f, 1.0f).Dot(bbUp)) + fabsf(Vec(0.0f, 0.0f, 1.0f).Dot(bbForward));

    const BoundingBox globalBoundingBox = { globalCenter.ToVec(), {newIi, newIj, newIk} };

    return (isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.leftface) &&
        isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.rightface) &&
        isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.topface) &&
        isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.bottomface) &&
        isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.nearface) &&
        isBoundingBoxOnForwardPlane(globalBoundingBox, frustum.farface));
}

Чтобы использовать код, я поместил ограничивающие рамки своих сеток в функцию IsBoundingBoxInFrustum рядом с усеченной пирамидой и преобразованием экземпляра сетки (которое включает перемещение, масштабирование и вращение). Также я хотел бы отметить, что для этого кода я следил за LearnOpenGL.

Если вы посмотрите точно вверх или вниз, векторное произведение upvec иnormaldir будет нулевым вектором. Я предполагаю, что люди не голосуют за ваш вопрос, потому что вы могли легко заметить это, установив точку останова или выведя свои значения на консоль. Вопрос о том, как решить проблему, если вы знаете, что ее вызывает, возможно, стоит задать здесь.

BDL 10.05.2024 17:34

@BDL У меня уже есть все значения, включая перекрестное произведение upvec иnormaldir, как вы упомянули. Я также хотел бы отметить, что я следовал руководству Learnopengl...

Alphenex 11.05.2024 15:25
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
74
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

  1. В следующий раз предоставьте MCVE.

  2. Используйте библиотеку GLM вместо собственного кода.

  3. Способ вычисления левой, правой, верхней и нижней плоскостей отсечения мне немного странен. Предположим, у нас есть:

     ----------*-b--*------------ far
           |    /
          a|   /
     ----------------- near
           | /
           |/
           /
          pos-----x
           |
           |
           z
    

если a — вектор от дальней плоскости до pos, а b — масштабированный правый вектор, вектор направления правой плоскости отсечения равен:

fDirFar + right * fHalfHSide

(как видите, знак изменился). Те же исправления, подобные этому, следует применить для левой, верхней и нижней плоскостей.

  1. Вы сказали, что ваша сетка преобразуется с помощью transform, который содержит функции перемещения, масштабирования и вращения (перемещение из локального пространства в мировое), поэтому я предполагаю, что она не содержит преобразования представления. Поскольку вы тестируете ограничивающую рамку вашей сетки на соответствие плоскостям отсечения, выраженным в пространстве обзора, определенном размещением камеры в точке pos, преобразование сетки должно также включать преобразование вида.
Ответ принят как подходящий

Проблема заключалась в том, что я не использовал GLM (да, по какой-то причине в математической библиотеке, которую я использовал, были некоторые проблемы, которые я не мог понять.)

Другие вопросы по теме