Я реализую 3D-движок для пространственной визуализации и пишу камеру со следующими функциями навигации:
- Поверните камеру (т. Е. Аналогично вращению головы)
- Поверните вокруг произвольной трехмерной точки (точки в пространстве, которая, вероятно, находится не в центре экрана; камера должна вращаться вокруг нее, сохраняя то же относительное направление взгляда, то есть направление взгляда тоже меняется. Это не смотрит прямо на выбранная точка вращения)
- Панорамирование в плоскости камеры (поэтому перемещайтесь вверх / вниз или влево / вправо в плоскости, ортогональной вектору взгляда камеры)
Камера не должна вращаться - то есть «вверх» остается вверху. Из-за этого я представляю камеру с местоположением и двумя углами, вращениями вокруг осей X и Y (Z будет вращаться). Затем матрица обзора пересчитывается с использованием местоположения камеры и этих двух углов. Это отлично подходит для панорамирования и вращения глаза, но не для вращения вокруг произвольной точки. Вместо этого я получаю следующее поведение:
- Сам глаз, по-видимому, движется дальше вверх или вниз, чем должен.
- Глаз вообще не движется вверх или вниз, когда
m_dRotationX
равно 0 или пи. (Блокировка кардана? Как этого избежать?) - Вращение глаза инвертируется (изменение поворота заставляет его смотреть дальше вверх, когда он должен смотреть дальше вниз, вниз, когда он должен смотреть дальше вверх), когда
m_dRotationX
находится между пи и 2pi.
(а) Что вызывает этот «дрейф» вращения?
Это может быть карданный замок. Если это так, стандартный ответ на этот вопрос - «использовать кватернионы для представления вращения», о чем много раз говорилось здесь, в SO (1, 2, 3 например), но, к сожалению, без конкретных подробностей (example. Это лучший ответ Я нашел до сих пор; это редко.) Я изо всех сил пытался реализовать камеру, используя кватернионы, объединяющие два вышеупомянутых типа вращения. Фактически, я строю кватернион, используя два поворота, но комментатор ниже сказал, что для этого нет причин - можно сразу построить матрицу.
Это происходит при изменении поворота по осям X и Y (которые представляют направление взгляда камеры) при повороте вокруг точки, но не происходит просто при прямом изменении поворота, то есть при повороте камеры вокруг себя. Для меня это не имеет смысла. Это те же ценности.
(b) Подойдет ли для этой камеры другой подход (например, кватернионы)? Если да, как мне реализовать все три вышеупомянутые функции навигации по камере?
Если другой подход был бы лучше, то, пожалуйста, рассмотрите возможность предоставления конкретного реализованного примера такого подхода. (Я использую DirectX9 и C ++, а также библиотеку 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);
}