Dav*_*vid 22 c++ math directx matrix quaternions
我正在实现用于空间可视化的3D引擎,并且正在编写具有以下导航功能的相机:
相机不应该滚动 - 也就是说,"向上"仍在继续.因此,我代表具有位置和两个角度的摄像机,围绕X和Y轴旋转(Z将滚动.)然后使用摄像机位置和这两个角度重新计算视图矩阵.这适用于平移和旋转眼睛,但不适用于围绕任意点旋转.相反,我得到以下行为:
m_dRotationX0或pi 时,眼睛根本不向上或向下移动.(万向节锁?我怎么能避免这种情况?)m_dRotationX在pi和2pi之间时,改变旋转使其在看起来应该向下看时向下看,当看起来应该向下看时向下看).(a)导致这种"漂移"的原因是什么?
这可能是万向节锁定.如果是这样,标准答案是"用四元数来表示转动"说,这里的许多倍SO(1,2,3为例),但遗憾的是没有具体的细节(例如,这是最好的答案,我发现到目前为止;它很少见.)我一直在努力使用四元数结合上述两种类型的旋转来实现相机.事实上,我正在使用两个旋转来构建四元数,但是下面的评论者说没有理由 - 立即构建矩阵也没关系.
在围绕某个点旋转时更改X和Y旋转(表示摄像机外观方向)时会发生这种情况,但直接更改旋转时不会发生这种情况,即将摄像机绕自身旋转.对我来说,这没有意义.这是相同的价值观.
(b)这种相机会采用不同的方法(例如四元数)吗?如果是这样,我如何实现上述所有三个相机导航功能?
如果不同的方法会更好,那么请考虑提供该方法的具体实施示例.(我使用的是DirectX9和C++,以及SDK提供的D3DX*库.)在第二种情况下,我会在几天内添加并奖励一笔赏金,我可以在问题中添加一个.这可能听起来像我在蹦蹦跳跳,但我的时间很少,需要快速实施或解决这个问题(这是一个紧迫的截止日期的商业项目.)详细的答案也将改善SO档案,因为大多数到目前为止我读过的相机答案对代码很轻松.
谢谢你的帮助 :)
一些澄清
感谢您的评论和答案到目前为止!我将尝试澄清有关该问题的一些事项:
每当其中一个变化时,从摄像机位置和两个角度重新计算视图矩阵.矩阵本身永远不会累积(即更新) - 它会重新重新计算.然而,相机位置和两个角度变量被累积(例如,每当鼠标移动时,基于鼠标上下移动的像素数量,一个或两个角度将增加或减少一小部分和/或或者在屏幕左右.)
评论员JCooper声称我正遭受万向节锁定,我需要:
在变换上添加另一个旋转,在应用变换之前将eyePos旋转到完全在yz平面中,然后再旋转另一个旋转.在应用偏航 - 俯仰 - 滚动矩阵之前和之后立即围绕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;
}
Run Code Online (Sandbox Code Playgroud)
该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
}
Run Code Online (Sandbox Code Playgroud)
并从此生成视图矩阵:
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);
}
Run Code Online (Sandbox Code Playgroud)
在我看来,根据您构建视图矩阵的方式,"Roll"是不可能的.无论所有其他代码(其中一些确实看起来有点滑稽),调用D3DXMatrixLookAtLH(&m_oViewMatrix, &oEyePos, &oTarget, &oUpVector);应该创建一个没有滚动的矩阵[0,1,0]作为'向上'向量,除非oTarget-oEyePos碰巧与向上向量平行.这似乎并非如此,因为你限制m_dRotationY在(-.49pi,+.49pi)之内.
也许你可以澄清你是如何知道"滚动"正在发生的.你有地平面,地平面的地平线是否偏离水平?
顺便说一下,因为你立即转身并将其变成矩阵UpdateView,所以D3DXQuaternionRotationYawPitchRoll看起来完全没必要.只需D3DXMatrixRotationYawPitchRoll像在鼠标事件中那样使用.四元数用于相机,因为它们是累积眼睛坐标中发生旋转的便捷方式.由于你只是按照严格的顺序使用两个旋转轴,所以你的累积角度的方法应该没问题.(0,0,1)的向量变换也不是必需的.本oRotationMatrix应该已经在这些值(_31,_32,_33)项.
更新
鉴于它不是滚动,这就是问题:你创建一个旋转矩阵来移动眼睛的世界坐标,但你希望音调发生在相机坐标中.由于不允许滚动并且最后执行偏航,因此在世界和相机参考帧中偏航总是相同的.考虑下面的图片:
您的代码适用于局部俯仰和偏航,因为这些是在摄像机坐标中完成的.

但是当您围绕参考点旋转时,您将创建一个旋转矩阵,该矩阵位于世界坐标中并使用它旋转摄像机中心.如果相机的坐标系恰好与世界排列,这可以正常工作.但是,如果在旋转摄像机位置之前没有检查是否符合音高限制,则在达到该限制时会出现疯狂的行为.相机会突然开始在世界各地滑行 - 仍然围绕参考点"旋转",但不再改变方向.

如果相机的轴不与世界对齐,则会发生奇怪的事情.在极端情况下,相机根本不会移动,因为你试图让它滚动.

以上是通常会发生的情况,但由于您单独处理相机方向,因此相机实际上不会滚动.

相反,它保持直立,但你会得到奇怪的翻译.
处理这种情况的一种方法是(1)始终将相机置于相对于参考点的规范位置和方向,(2)进行旋转,然后(3)在完成后将其放回(例如,类似于将参考点平移到原点的方式,应用Yaw-Pitch旋转,然后平移回来).然而,考虑更多,这可能不是最佳方式.
更新2
我认为Generic Human的答案可能是最好的.问题仍然是如果旋转是离轴应该应用多少音高,但是现在,我们将忽略它.也许它会给你可接受的结果.
答案的本质是:在鼠标移动之前,你的相机处于c 1 = m_oEyePos并且被M 1 = 定向D3DXMatrixRotationYawPitchRoll(&M_1,m_dRotationX,m_dRotationY,0).考虑参考点a = m_oRotateAroundPos.从相机的角度来看,这一点是'= M 1(ac 1).
您想将摄像机的方向更改为M 2 = D3DXMatrixRotationYawPitchRoll(&M_2,m_dRotationX+dX,m_dRotationY+dY,0).[ 重要提示:由于您不允许m_dRotationY超出特定范围,因此您应确保dY不违反该约束.]当相机更改方向时,您还希望其位置围绕a旋转到新点c 2.这意味着a不会从相机的角度改变.即,M 1(ac 1)== M 2(ac 2).
所以我们求解c 2(记住旋转矩阵的转置与反转相同):
M 2 T M 1(ac 1)==(ac 2) =>
-M 2 T M 1(ac 1)+ a == c 2
现在,如果我们将此视为应用于c 1的变换,那么我们可以看到它首先被否定,然后由a转换,然后旋转M 1,然后旋转M 2 T,再次否定,然后由一试.这些是图形库擅长的转换,它们都可以被压缩成单个转换矩阵.
@Generic Human值得称赞答案,但这里是代码.当然,您需要在应用之前实现该功能以验证音高变化,但这很简单.这段代码可能有一些拼写错误,因为我还没有尝试编译:
case ENavigation::eRotatePoint: {
const double dX = (double)(m_oLastMousePos.x - roMousePos.x) * dRotatePixelScale * D3DX_PI;
double dY = (double)(m_oLastMousePos.y - roMousePos.y) * dRotatePixelScale * D3DX_PI;
dY = validatePitch(dY); // dY needs to be kept within bounds so that m_dRotationY is within bounds
D3DXMATRIX oRotationMatrix1; // The camera orientation before mouse-change
D3DXMatrixRotationYawPitchRoll(&oRotationMatrix1, m_dRotationX, m_dRotationY, 0.0);
D3DXMATRIX oRotationMatrix2; // The camera orientation after mouse-change
D3DXMatrixRotationYawPitchRoll(&oRotationMatrix2, m_dRotationX + dX, m_dRotationY + dY, 0.0);
D3DXMATRIX oRotationMatrix2Inv; // The inverse of the orientation
D3DXMatrixTranspose(&oRotationMatrix2Inv,&oRotationMatrix2); // Transpose is the same in this case
D3DXMATRIX oScaleMatrix; // Negative scaling matrix for negating the translation
D3DXMatrixScaling(&oScaleMatrix,-1,-1,-1);
D3DXMATRIX oTranslationMatrix; // Translation by the reference point
D3DXMatrixTranslation(&oTranslationMatrix,
m_oRotateAroundPos.x,m_oRotateAroundPos.y,m_oRotateAroundPos.z);
D3DXMATRIX oTransformMatrix; // The full transform for the eyePos.
// We assume the matrix multiply protects against variable aliasing
D3DXMatrixMultiply(&oTransformMatrix,&oScaleMatrix,&oTranslationMatrix);
D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oRotationMatrix1);
D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oRotationMatrix2Inv);
D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oScaleMatrix);
D3DXMatrixMultiply(&oTransformMatrix,&oTransformMatrix,&oTranslationMatrix);
D3DXVECTOR4 oEyeFinal;
D3DXVec3Transform(&oEyeFinal, &m_oEyePos, &oTransformMatrix);
m_oEyePos = D3DXVECTOR3(oEyeFinal.x, oEyeFinal.y, oEyeFinal.z)
// Increment rotation to keep the same relative look angles
RotateXAxis(dX);
RotateYAxis(dY);
break;
}
Run Code Online (Sandbox Code Playgroud)
我认为有一个更简单的解决方案,可让您避开所有轮换问题。
表示法:A是我们要旋转的点,C是原始摄影机的位置,M是原始摄影机的旋转矩阵,该矩阵将全局坐标映射到摄影机的本地视口。
另外,旋转行为现在在“眼睛旋转”和“点旋转”模式之间保持一致。