Инвертирование камеры Arcball по азимуту 90 градусов

#c #opengl #opengl-3 #glm-math #arcball

#c #opengl #opengl-3 #glm-математика #arcball

Вопрос:

Я пытаюсь реализовать камеру в стиле arcball. Я использую glm::LookAt, чтобы держать камеру направленной на цель, а затем перемещать ее по поверхности сферы, используя углы азимута / наклона для поворота вида.

Я столкнулся с проблемой, из-за которой вид переворачивается с ног на голову, когда азимут приближается к 90 градусам.

Вот соответствующий код:

  1. Получить проекцию и просмотреть графики. Выполняется в основном цикле

     void Visual::updateModelViewProjection()
    {
        model = glm::mat4();
        projection = glm::mat4();
        view = glm::mat4();
    
        projection = glm::perspective
            (
            (float)glm::radians(camera.Zoom),
            (float)width / height, // aspect ratio
            0.1f, // near clipping plane
            10000.0f // far clipping plane
            );
    
        view = glm::lookAt(camera.Position, camera.Target, camera.Up);
    }
      
  2. Событие перемещения мыши для поворота камеры

     void Visual::cursor_position_callback(GLFWwindow* window, double xpos, double ypos)
    {
        if (leftMousePressed)
        {
           ...
        }
    
        if (rightMousePressed)
        {
            GLfloat xoffset = (xpos - cursorPrevX) / 4.0;
            GLfloat yoffset = (cursorPrevY - ypos) / 4.0;
    
            camera.inclination  = yoffset;
            camera.azimuth -= xoffset;
            if (camera.inclination > 89.0f)
                camera.inclination = 89.0f;
            if (camera.inclination < 1.0f)
                camera.inclination = 1.0f;
    
            if (camera.azimuth > 359.0f)
                camera.azimuth = 359.0f;
            if (camera.azimuth < 1.0f)
                camera.azimuth = 1.0f;
    
            float radius = glm::distance(camera.Position, camera.Target);
            camera.Position[0] = camera.Target[0]   radius * cos(glm::radians(camera.azimuth)) * sin(glm::radians(camera.inclination));
            camera.Position[1] = camera.Target[1]   radius * sin(glm::radians(camera.azimuth)) * sin(glm::radians(camera.inclination));
            camera.Position[2] = camera.Target[2]   radius * cos(glm::radians(camera.inclination));
    
            camera.updateCameraVectors();
        }
    
        cursorPrevX = xpos;
        cursorPrevY = ypos;
    }
      
  3. Вычисление векторов ориентации камеры

     void updateCameraVectors()
    {
        Front = glm::normalize(Target-Position);
        Right = glm::rotate(glm::normalize(glm::cross(Front, {0.0, 1.0, 0.0})), glm::radians(90.0f), Front);
        Up = glm::normalize(glm::cross(Front, Right));
    }
      

Я почти уверен, что это связано с тем, как я вычисляю правильный вектор моей камеры, но я не могу понять, как это компенсировать.

Кто-нибудь сталкивался с этим раньше? Есть предложения?

Комментарии:

1. При Right вычислении вы берете перекрестное произведение Front и (0, 1, 0) , которое равно нулю, если Front оно коллинеарно (0, 1, 0) . И нормализация нулевого вектора могут привести к странному поведению.

Ответ №1:

Это распространенная ошибка — использовать lookAt для поворота камеры. Вы не должны. Направления назад / вправо / вверх являются столбцами вашей матрицы просмотра. Если они у вас уже есть, то вам даже не нужны lookAt , что пытается переделать некоторые ваши вычисления. С другой стороны, lookAt это не поможет вам в поиске этих векторов в первую очередь.

Вместо этого сначала создайте матрицу просмотра как композицию перемещений и поворотов, а затем извлеките эти векторы из ее столбцов:

 void Visual::cursor_position_callback(GLFWwindow* window, double xpos, double ypos)
{
    ...
    if (rightMousePressed)
    {
        GLfloat xoffset = (xpos - cursorPrevX) / 4.0;
        GLfloat yoffset = (cursorPrevY - ypos) / 4.0;

        camera.inclination = std::clamp(camera.inclination   yoffset, -90.f, 90.f);
        camera.azimuth = fmodf(camera.azimuth   xoffset, 360.f);

        view = glm::mat4();
        view = glm::translate(view, glm::vec3(0.f, 0.f, camera.radius)); // add camera.radius to control the distance-from-target
        view = glm::rotate(view, glm::radians(camera.inclination   90.f), glm::vec3(1.f,0.f,0.f));
        view = glm::rotate(view, glm::radians(camera.azimuth), glm::vec3(0.f,0.f,1.f));
        view = glm::translate(view, camera.Target);

        camera.Right = glm::column(view, 0);
        camera.Up = glm::column(view, 1);
        camera.Front = -glm::column(view, 2); // minus because OpenGL camera looks towards negative Z.
        camera.Position = glm::column(view, 3);

        view = glm::inverse(view);
    }
    ...
}
  

Затем удалите код, который вычисляет вид и векторы направления из updateModelViewProjection и updateCameraVectors .

Отказ от ответственности: этот код не тестировался. Возможно, вам потребуется где-то исправить знак минус, порядок операций или соглашения могут не совпадать (Z — вверх или Y — вверх и т.д.).

Комментарии:

1. Спасибо! Это намного проще. Однако мне интересно, какова цель glm ::inverse в конце? Кажется, без этого работает лучше, и я просто хочу убедиться, что я ничего не упускаю

2. @lmoyn: Векторы вправо / вверх / назад / положения в мировом пространстве — это столбцы матрицы, преобразующиеся из пространства камеры в мировое пространство (предполагая, что столбцы-векторы). Но матрица просмотра обычно предназначена для перевода из мирового пространства в пространство камеры . Отсюда обратное: я сначала вычисляю преобразование камера-> мир, затем извлекаю векторы (этот шаг необязателен, если они вам не нужны), затем инвертирую матрицу, чтобы получить преобразование мир-> камера. Если вы говорите, что это выглядит неправильно, это, вероятно, потому, что я неправильно определил порядок glm::translates/rotates . Попробуйте поменять их местами.

3. Я не использую glm — одна из причин в том, что я предпочитаю другие библиотеки, которые используют явный порядок умножения матриц — в отличие от glm, где неясно, glm::rotate(M, ...) вычисляет ли матрица поворота R , возвращает ли она M*R или R*M . Даже их документация расплывчата в этом отношении. Я предположил, что это R*M то, что он делает, если нет, то все должно быть сделано в обратном порядке.