在OpenGL中使用向量无法限制摄像机俯仰角在[-90º,90º]之间!

3
我在以下代码中遇到了限制相机俯仰角度(介于-90º和90º之间)的大问题。这是某种程度上对此问题的跟进。
问题似乎是相机旋转超过-90º或超过+90º,并且当发生这种情况时,我将向下(或向上)看,但是视图只是围绕Y轴旋转了180º。
例如:我面向北方,看着地平线,然后开始往下看,直到无法再往下走(由下面的代码限制)。然后我开始往上看,我就会面向南方。
void Camera::Rotate(Vector3D angle) {
    angle = angle * CAMERA_ROTATION_SPEED;

    accumPitchAngle += angle.x;

    if(accumPitchAngle > 90.0f) {
        angle.x = 90.0f - (accumPitchAngle - angle.x);
        accumPitchAngle = 90.0f;
    }

    if(accumPitchAngle < -90.0f) {
        angle.x = -90.0f - (accumPitchAngle - angle.x);
        accumPitchAngle = -90.0f;
    }

    // Rotate along the WORLD_SKY_VECTOR axis (yaw/heading rotation)
    // WORLD_SKY_VECTOR = (0.0f, 1.0f, 0.0f)
    if(angle.y != 0.0f) {
        Reference = RotateArbitraryAxis(Reference, WORLD_SKY_VECTOR, angle.y);
        RightVector = Vector3D::CrossProduct(Reference, WORLD_SKY_VECTOR);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    }

    // Rotate along the x axis (pitch rotation)
    if(angle.x != 0.0f) {
        Reference = RotateArbitraryAxis(Reference, RightVector, angle.x);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    }

    // Makes sure all vectors are perpendicular all the time
    Reference.Normalize();
    RightVector = Vector3D::CrossProduct(Reference, UpVector);
    RightVector.Normalize();
    UpVector = Vector3D::CrossProduct(RightVector, Reference);
    UpVector.Normalize();
}

Vector3D Camera::RotateArbitraryAxis(const Vector3D v, Vector3D u, double angle) {
    Vector3D result;

    u.Normalize();

    double scalar = Vector3D::DotProduct(v, u);
    double c = cos(Math::DegreesToRadians(angle));
    double s = sin(Math::DegreesToRadians(angle));
    double a = 1.0f - c;

    result.x = u.x * scalar * a + (v.x * c) + ((-u.z * v.y) + (u.y * v.z)) * s;
    result.y = u.y * scalar * a + (v.y * c) + (( u.z * v.x) - (u.x * v.z)) * s;
    result.z = u.z * scalar * a + (v.z * c) + ((-u.y * v.x) + (u.x * v.y)) * s;

    return result;
}
问题很可能出现在if(angle.y != 0.0f)语句中,如果我注释掉该代码块,则根本不存在问题。这与WORLD_SKY_VECTOR有关,但是那段代码是为了让我旋转方向并保持相机水平。如果我使用UpVector,问题就解决了。但那只适用于飞行模拟器,我需要保持地平线水平,这就是WORLD_SKY_VECTOR背后的原因。但似乎这就是当我将相机指向正下方时“侧面切换”的原因。

根据下面的评论请求...这是为第一人称(和第三人称,但我还没有开始实现那部分)相机设计的,当我看直下方时,角度为-90º(或直上方,+90º),当角度从-89º变为-91º(或从+89º变为+91º)时,我希望相机防止这种情况,并且不超过-90º,+90º的限制。当它达到这个限制时,我需要相机能够返回(如果我处于-90º则向上,如果我处于+90º则向下)。现在这只有时而起作用,其他时候会面对另一个方向而不是最初观察的方向。

3个回答

2

问题1:下面的代码“// Makes sure all vectors are perpendicular all the time”确保您的UP相机向量实际上是朝上的(相对于WORLD_SKY_VECTOR)。所以:

  • 当您完全向下看时,“向上”没有太多意义。在您的情况下,您的camera_up应该朝北而不是朝天
  • 当您继续旋转时,相机会旋转以保持其向上指向天空。

向下看你的胸口。你的头顶实际上是向下的。如果您想从同一点、以同一视角查看相同的事物,但头顶朝上,则必须将其旋转(可悲的是,这在人类中无法实现)

问题2:与上述相同。CrossProduct(Reference, WORLD_SKY_VECTOR)给出cross(up,down),这没有任何意义,只需在纸上试试。

如果您想能够“倒立地”查看:

  • 计算您的视线方向向量。由于您具有方向和俯仰角,因此可以可靠地知道它。
  • 计算您的右向量。它是相同的,但方向增加了90°,俯仰角为0(相机始终水平,即您不弯曲头部)
  • 计算您的上向量为cross(right,front)。

所以:

yaw += whatever;
pitch += whatever;
FrontVector = SphericalToCartesian(yaw, pitch);
RightVector = SphericalToCartesian(yaw + PI/2.0f, 0.0);
UpVector = cross(RightVector, FrontVector);

使用SphericalToCartesian函数,类似于如果(pitch=0,yaw=0),表示朝向南方:

x = cos(pitch) * sin(yaw)
y = sin(pitch)
z = cos(pitch) * cos(yaw)

你说得对,我应该使用 UpVector 而不是 WORLD_SKY_VECTOR 来解决“垂直代码”问题。在解决其他问题时我做了这个更改,虽然最终还是解决了那个问题,但对于这个问题却搞砸了。然而,这仍然没有解决问题。 我已经更新了“Rotate()”函数的完整代码,请看一下。 我也删除了我的第二个问题,因为它可能不再相关。如果需要,我会发布另一个问题,如果你想要,可以编辑你的帖子以删除对第二个问题的回答。 - rfgamaral
注意:我按照你的建议,在“// 确保所有向量始终垂直”的代码下方,将“WORLD_SKY_VECTOR”替换为“UpVector”。但是在偏航/航向旋转中仍在使用“WORLD_SKY_VECTOR”,以使相机始终垂直于地面/天空,否则会感觉像飞行模拟器,我不想要那样。我从我在此帖子顶部链接的另一个问题中得到了这个。 - rfgamaral
不,问题还没有解决。问题仍然存在。我刚刚更新了问题,以包含完整的更新代码(之前我剥离了几行,认为它们不重要,但它们可能很重要)。 - rfgamaral
真正的问题是:当accumPitchAngle从89°变为91°时,你希望发生什么?你希望你的相机如何行为?(我不是在谈论代码)。它是FPS相机吗?自由飞行相机?尽可能详细地描述它(如果可能的话在你的问题中),代码取决于它。 - Calvin1602
这比我的解决方案更加混乱,而且需要更多的代码。我的解决方案有什么问题吗? - rfgamaral
显示剩余2条评论

1

稍微思考一下后,我找到了一个相当简单和直接的解决方案。像回答和评论中多次提到的那样,问题出在前向矢量(我的Reference)朝上或朝下时,这意味着该矢量与WORLD_SKY_VECTOR平行,这就是造成混乱的原因。

我的解决方案背后的想法是,在我面对向下(或向上)并且想左右旋转时,这实际上是围绕前向矢量进行横滚旋转。为什么不在俯仰角度为-90º或90º时执行滚动运动呢?

把这些放在一起,我通过简单地替换偏航/航向旋转来解决了问题,使用以下代码:

if(angle.y != 0.0f) {
    if(abs(accumPitchAngle) == 90.0f) {
        RightVector = RotateArbitraryAxis(RightVector, Reference, -angle.y);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    } else {
        Reference = RotateArbitraryAxis(Reference, WORLD_SKY_VECTOR, angle.y);
        RightVector = Vector3D::CrossProduct(Reference, WORLD_SKY_VECTOR);
        UpVector = Vector3D::CrossProduct(RightVector, Reference);
    }
}

这似乎是一个相当简单和直接的工作解决方案,除非有人指出我可能没有考虑到的实现中存在任何严重问题,否则我可能会将我的答案标记为已接受。在这样做之前,我会等待几个小时,以便之前的答案达成某种结论。


角度等于90.0f几乎不可能发生。我已经编辑了我的答案并附上了实际代码,请您尝试一下? - Calvin1602
你的意思是什么?这种情况发生在之前的夹紧中,例如如果角度为88.7,鼠标移动了3.2,88.7 + 3.2 = 91.9,因此该值将被夹紧到90,并且俯仰旋转只足以达到90 (90 - (91.9 - 3.2) = 1.3)。这就是问题所在。除非我误解了你的意思。 - rfgamaral

1

两个平行向量的叉积是不存在的。我认为它失败的地方就在这里,即当accumPitchAngle为±90°时。

您可能想将其限制在-89.999°〜+89.999°之间。

编辑:从头开始,您想将俯仰和偏航转换为用于gluLookAt()的前向矢量和上矢量,对吗?那么我建议:

1)仅使用yaw创建与地面平行的right向量。
2)通过rightWORLD_SKY_VECTOR进行叉积计算,得到一个在yaw中正确的forward向量,但在pitch中不正确。
3)将forwardright旋转pitch度,以获得在pitch和yaw中都正确的前向矢量。(我想您已经完成了这一步。)
4)通过forwardright进行叉积计算,得到一个可适用于所有情况的相机up向量。


我曾经考虑过这个方法,但实际上我并不喜欢它,感觉很粗糙,不是一个真正的解决方案。我在其他情况下也需要处理角度,这样做可能会引起其他问题。目前,我正在尝试避免这种方法,如果找不到更好的解决方案,我将不得不考虑它。 - rfgamaral
在您的编辑建议中,您建议我放弃当前的解决方案,换句话说,放弃我在另一个问题上得到的解决方案(基本上是摆脱RotateArbitraryAxis()),除了那个小问题之外,它已经可以正常工作了。一定有其他方法来解决它,而不是重写一切。 - rfgamaral
但它是有效的。这是一个绕任意轴旋转的函数,如果我使用一个上向量而不是天空向量,它可以正常工作。问题在于向量,而不是方法。这只是一个小问题,因为我可以按照你最初建议的做法来解决问题,问题将得到解决,没有人会注意到差异。你说得对,我没有意识到我需要改变什么,因为我觉得你的回答有点令人困惑。换句话说,我很难将你的向量名称与我的向量名称联系起来。我还认为你建议的向量比我拥有的多一个,这让我更加困惑。 - rfgamaral
1
无论如何,问题在于您试图从“forward”(参考)向量和“WORLD_SKY_VECTOR”创建“right”向量,当它们平行时会失败。您可以更简单地通过yaw的函数创建它:“right =(cos(yaw),0.0,sin(yaw));”然后您可以通过使用函数或简单地旋转“right”围绕“WORLD_SKY_VECTOR”来创建一个“forward_horizontal”向量(cos(yaw-90),0.0,sin(yaw-90))。然后您可以围绕“right”旋转它以获得俯仰。然后您可以将其与“right”交叉以获得“个人”“up”向量,并将其与“gluLookAt()”一起使用。 - aib
是的,我已经意识到这是问题所在了。我尝试实现了您的建议,但仍然感到困惑。我不知道您所说的“偏航”是什么意思(我尝试了不同的角度变量),也不知道应该把所有代码放在哪里以及删除自己的哪些代码。可以说,我尝试的一切都没有成功。无论如何,只要可能,我宁愿使用我的RotateArbitraryAxis而不是所有那些cos/sin。 - rfgamaral
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接