#c #opengl #opengl-3 #glm-math #arcball
#c #opengl #opengl-3 #glm-математика #arcball
Вопрос:
Я пытаюсь реализовать камеру в стиле arcball. Я использую glm::LookAt, чтобы держать камеру направленной на цель, а затем перемещать ее по поверхности сферы, используя углы азимута / наклона для поворота вида.
Я столкнулся с проблемой, из-за которой вид переворачивается с ног на голову, когда азимут приближается к 90 градусам.
Вот соответствующий код:
-
Получить проекцию и просмотреть графики. Выполняется в основном цикле
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); }
-
Событие перемещения мыши для поворота камеры
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; }
-
Вычисление векторов ориентации камеры
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
то, что он делает, если нет, то все должно быть сделано в обратном порядке.