【发布时间】:2012-07-13 07:59:56
【问题描述】:
我正在实现一个用于空间可视化的 3D 引擎,并且正在编写一个具有以下导航功能的相机:
- 旋转相机(即类似于旋转头部)
- 围绕任意 3D 点(空间中的一个点,可能不在屏幕中心;相机需要围绕该点旋转,保持相同的相对观察方向,即观察方向也会发生变化。这不会直接看选择的旋转点)
- 在相机平面中平移(因此在垂直于相机观察矢量的平面中向上/向下或向左/向右移动)
相机不应该滚动 - 也就是说,“向上”仍然是向上的。因此,我用一个位置和两个角度表示相机,围绕 X 和 Y 轴旋转(Z 将是滚动的。)然后使用相机位置和这两个角度重新计算视图矩阵。这对于平移和旋转眼睛非常有用,但对于围绕任意点旋转不。相反,我得到以下行为:
- 眼睛本身明显地向上或向下移动而不是应该的
-
m_dRotationX为 0 或 pi 时,眼睛根本不会上下移动。 (云台锁?如何避免这种情况?) - 当
m_dRotationX介于 pi 和 2pi 之间时,眼睛的旋转会反转(更改旋转会使它在应该往下看时往上看,在往上看时往下看)。
(a) 是什么导致了这种旋转“漂移”?
这可能是gimbal lock。如果是这样,对此的标准答案是“使用四元数表示旋转”,在 SO 上多次说过(1、2、3 例如),但不幸的是 没有具体细节 (example。这是the best answer 到目前为止我发现的;它很少见。)我一直在努力使用结合上述两种旋转类型的四元数来实现相机。事实上,我正在使用两个旋转构建一个四元数,但下面的评论者说没有理由 - 立即构建矩阵就可以了。
当围绕一个点旋转时更改 X 和 Y 旋转(表示相机的观察方向)时会发生这种情况,但不会仅在直接更改旋转时发生,即围绕自身旋转相机。对我来说,这没有意义。这是相同的值。
(b) 不同的方法(例如四元数)是否更适合这款相机?如果是这样,我如何实现上述所有三个相机导航功能?
如果另一种方法会更好,那么请考虑提供该方法的具体实施示例。 (我使用的是 DirectX9 和 C++,以及 SDK 提供的 D3DX* 库。)在第二种情况下,我将在几天内添加并奖励赏金,届时我可以在问题中添加一个。这听起来像是我在抢先一步,但我的时间很短,需要快速实施或解决这个问题(这是一个截止日期很紧的商业项目。)详细的答案也将改进 SO 档案,因为大多数到目前为止我读过的相机答案对代码很简单。
感谢您的帮助:)
一些说明
感谢 cmets 到目前为止的回答!我将尝试澄清有关该问题的一些事项:
只要其中之一发生变化,就会根据相机位置和两个角度重新计算视图矩阵。矩阵本身永远不会累积(即更新) - 它会重新计算。但是,相机位置和两个角度变量是累加的(例如,每当鼠标移动时,一个或两个角度都会有少量添加或减去,具体取决于鼠标上下移动的像素数和/或在屏幕上左右显示。)
评论者JCooper 表示我遇到了云台锁定问题,我需要:
在您的变换上添加另一个旋转,将 eyePos 旋转为 在应用转换之前完全在 y-z 平面中,并且 然后另一个旋转将其向后移动。绕着旋转 y 轴在应用之前和之后的以下角度 yaw-pitch-roll 矩阵(其中一个角度需要取反; 尝试是决定哪种方法的最快方法)。
double fixAngle = atan2(oEyeTranslated.z,oEyeTranslated.x);
不幸的是,当按照描述执行此操作时,由于其中一个旋转,我的眼睛以非常快的速度从场景上方射出。我确信我的代码只是这个描述的一个糟糕的实现,但我仍然需要更具体的东西。一般来说,我发现算法的非特定文本描述不如注释的、解释的实现有用。 我正在为与以下代码集成的具体工作示例添加赏金(即也与其他导航方法)。这是因为我想理解解决方案,还有一些可行的东西,因为我需要快速实施一些可行的东西,因为我的截止日期很紧。
如果您用算法的文字描述回答,请确保它足够详细以实现(“围绕 Y 旋转,然后变换,然后旋转回来”可能对您有意义,但缺乏了解您的详细信息意思是。Good answers are clear, signposted, will allow others to understand even with a different basis, are 'solid weatherproof information boards.')
反过来,我试图清楚地描述问题,如果我可以更清楚地告诉我。
我当前的代码
要实现上述三个导航功能,在鼠标移动事件中根据光标移动的像素移动:
// 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);
}
【问题讨论】:
-
我认为这个错误来自舍入/截断错误。如果你增加值的范围,那么如果我是对的,你就会使漂移变小
-
你应该使用比你需要的更多的有效数字
-
最后一位是舍入/截断的来源
-
@tuğrulbüyükışık:浮点错误是可能的,但我不确定你的意思是什么?角度存储为双精度并且是“整个”旋转(每次鼠标移动增加/调整矩阵会导致更多错误;这里每次都从头开始重新计算。)我也不确定这将如何导致角度-依赖漂移。
标签: c++ math matrix directx quaternions