Я использую 3D-движок для пространственной визуализации и пишу камеру со следующими функциями навигации:
- Поверните камеру (т.е. аналогично вращению головы).
- Поворот вокруг произвольной трехмерной точки (точка в пространстве, которая, вероятно, не находится в центре экрана, камера должна вращаться вокруг этого, сохраняя одно и то же относительное направление взгляда, т.е. меняется направление взгляда. посмотрите прямо на выбранную точку вращения)
- Панорамирование в плоскости камеры (поэтому перемещайте вверх/вниз или влево/вправо в плоскости, ортогональной к вектору просмотра камеры)
Фотокамера не должна переворачиваться - то есть "вверх" остается вверх. Из-за этого я представляю камеру с местоположением и двумя углами, вращения вокруг осей X и Y (Z будет рулон.) Затем матрицу просмотра пересчитывают с использованием местоположения камеры и этих двух углов. Это отлично работает для панорамирования и поворота глаза, но не для вращения вокруг произвольной точки. Вместо этого я получаю следующее поведение:
- Сам глаз, по-видимому, движется дальше вверх или вниз, чем он должен
- Глаз не перемещается вверх или вниз, когда
m_dRotationX
равно 0 или pi. (Gimbal lock? Как я могу избежать этого?) - Вращение глаза инвертируется (изменение вращения заставляет его смотреть дальше, когда он должен смотреть дальше вниз, вниз, когда он должен смотреть дальше), когда
m_dRotationX
находится между pi и 2pi.
(a) Что вызывает этот "дрейф" во вращении?
Это может быть gimbal lock. Если это так, то стандартный ответ на этот вопрос - "использовать кватернионы для представления вращения", много раз говорил здесь о SO (1, 2, 3, но, к сожалению, без конкретных деталей (. Это лучший ответ, который я нашел до сих пор, он редок.) Я изо всех сил пытался реализовать камера с использованием кватернионов, сочетающая два вышеперечисленных типа вращения. Я, по сути, строю кватернион, используя два вращения, но комментатор ниже сказал, что нет никакой причины - прекрасно сразу создать матрицу.
Это происходит при изменении поворотов X и Y (которые представляют направление просмотра камеры) при вращении вокруг точки, но не происходит просто при прямом изменении поворотов, т.е. вращении камеры вокруг себя. Для меня это не имеет смысла. Это те же значения.
(б) Будет ли другой подход (например, кватернионы) лучше для этой камеры? Если да, то как мне реализовать все три функции навигации камеры выше?
Если другой подход будет лучше, тогда, пожалуйста, рассмотрите вопрос о конкретном реализованном примере этого подхода. (Я использую DirectX9 и С++ и D3DX * библиотеку, которую предоставляет SDK.) В этом втором случае я добавлю и награду щедростью через пару дней, когда я могу добавить ее к вопросу. Это может звучать так, как будто я прыгаю с пистолетом, но я нахожусь вовремя, и вам нужно быстро реализовать или решить эту проблему (это коммерческий проект с ограниченным сроком.) Подробный ответ также улучшит архивы SO, поскольку большинство ответы на камеру, которые я читал до сих пор, являются легкими для кода.
Спасибо за вашу помощь:)
Некоторые разъяснения
Спасибо за комментарии и ответ! Я попытаюсь прояснить несколько вещей о проблеме:
-
Маска представления пересчитывается из положения камеры и двух углов всякий раз, когда изменяется одна из этих вещей. Сама матрица никогда не накапливается (т.е. Не обновляется) - она пересчитывается заново. Однако положение камеры и две угловые переменные накапливаются (всякий раз, когда движется мышь, например, один или оба угла будут иметь небольшое количество, добавленное или вычитаемое, в зависимости от количества пикселей, которые мышь перемещала вверх-вниз и/или слева направо на экране.)
-
Комментарий JCooper утверждает, что я страдаю от блокировки карданного вала, и мне нужно:
добавьте еще одно вращение на ваше преобразование, которое вращает eyePos полностью в плоскости y-z, прежде чем применять преобразование, и затем другой поворот, который перемещает его назад. Повернуть вокруг y по следующему углу непосредственно перед и после применения матрицу валкового вала (один из углов нужно будет отменить; попробовать это самый быстрый способ решить, какой).
double fixAngle = atan2(oEyeTranslated.z,oEyeTranslated.x);
К сожалению, при реализации этого, как описано, мой глаз стреляет над сценой с очень высокой скоростью из-за одного из поворотов. Я уверен, что мой код - это просто плохая реализация этого описания, но мне все еще нужно что-то более конкретное. В общем, я нахожу, что неспецифические текстовые описания алгоритмов менее полезны, чем комментарии, объясняют реализации. Я добавляю награду за конкретный рабочий пример, который интегрируется с кодом ниже (т.е. с другими навигационными методами тоже). Это потому, что я хотел бы понять решение, а также что-то, что работает, и потому, что мне нужно реализовать что-то, что работает быстро, так как я нахожусь в сжатые сроки.
Пожалуйста, если вы ответите текстовым описанием алгоритма, убедитесь, что он достаточно подробен для реализации ( "Поворот вокруг Y, затем преобразование, а затем поворот назад" может иметь смысл для вас, но не хватает деталей, чтобы узнать, что вы Хорошие ответы ясны, обозначены знаками, позволят другим понять даже с другой базой, являются "сплошными погодными информационными досками" .)
В свою очередь, я попытался четко описать проблему, и если я смогу сделать ее более ясной, сообщите мне.
Мой текущий код
Чтобы реализовать вышеупомянутые три функции навигации, в перемещении мыши движение перемещается на основе пикселей, перемещенных курсором:
// Adjust this to change rotation speed when dragging (units are radians per pixel mouse moves)
// This is both rotating the eye, and rotating around a point
static const double dRotatePixelScale = 0.001;
// Adjust this to change pan speed (units are meters per pixel mouse moves)
static const double dPanPixelScale = 0.15;
switch (m_eCurrentNavigation) {
case ENavigation::eRotatePoint: {
// Rotating around m_oRotateAroundPos
const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dRotatePixelScale * D3DX_PI;
const double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dRotatePixelScale * D3DX_PI;
// To rotate around the point, translate so the point is at (0,0,0) (this makes the point
// the origin so the eye rotates around the origin), rotate, translate back
// However, the camera is represented as an eye plus two (X and Y) rotation angles
// This needs to keep the same relative rotation.
// Rotate the eye around the point
const D3DXVECTOR3 oEyeTranslated = m_oEyePos - m_oRotateAroundPos;
D3DXMATRIX oRotationMatrix;
D3DXMatrixRotationYawPitchRoll(&oRotationMatrix, dX, dY, 0.0);
D3DXVECTOR4 oEyeRotated;
D3DXVec3Transform(&oEyeRotated, &oEyeTranslated, &oRotationMatrix);
m_oEyePos = D3DXVECTOR3(oEyeRotated.x, oEyeRotated.y, oEyeRotated.z) + m_oRotateAroundPos;
// Increment rotation to keep the same relative look angles
RotateXAxis(dX);
RotateYAxis(dY);
break;
}
case ENavigation::ePanPlane: {
const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dPanPixelScale;
const double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dPanPixelScale;
m_oEyePos += GetXAxis() * dX; // GetX/YAxis reads from the view matrix, so increments correctly
m_oEyePos += GetYAxis() * -dY; // Inverted compared to screen coords
break;
}
case ENavigation::eRotateEye: {
// Rotate in radians around local (camera not scene space) X and Y axes
const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dRotatePixelScale * D3DX_PI;
const double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dRotatePixelScale * D3DX_PI;
RotateXAxis(dX);
RotateYAxis(dY);
break;
}
Методы RotateXAxis
и RotateYAxis
очень просты:
void Camera::RotateXAxis(const double dRadians) {
m_dRotationX += dRadians;
m_dRotationX = fmod(m_dRotationX, 2 * D3DX_PI); // Keep in valid circular range
}
void Camera::RotateYAxis(const double dRadians) {
m_dRotationY += dRadians;
// Limit it so you don't rotate around when looking up and down
m_dRotationY = std::min(m_dRotationY, D3DX_PI * 0.49); // Almost fully up
m_dRotationY = std::max(m_dRotationY, D3DX_PI * -0.49); // Almost fully down
}
И для создания матрицы вида из этого:
void Camera::UpdateView() const {
const D3DXVECTOR3 oEyePos(GetEyePos());
const D3DXVECTOR3 oUpVector(0.0f, 1.0f, 0.0f); // Keep up "up", always.
// Generate a rotation matrix via a quaternion
D3DXQUATERNION oRotationQuat;
D3DXQuaternionRotationYawPitchRoll(&oRotationQuat, m_dRotationX, m_dRotationY, 0.0);
D3DXMATRIX oRotationMatrix;
D3DXMatrixRotationQuaternion(&oRotationMatrix, &oRotationQuat);
// Generate view matrix by looking at a point 1 unit ahead of the eye (transformed by the above
// rotation)
D3DXVECTOR3 oForward(0.0, 0.0, 1.0);
D3DXVECTOR4 oForward4;
D3DXVec3Transform(&oForward4, &oForward, &oRotationMatrix);
D3DXVECTOR3 oTarget = oEyePos + D3DXVECTOR3(oForward4.x, oForward4.y, oForward4.z); // eye pos + look vector = look target position
D3DXMatrixLookAtLH(&m_oViewMatrix, &oEyePos, &oTarget, &oUpVector);
}