【问题标题】:Look-at quaternion using up vector使用向上向量查看四元数
【发布时间】:2019-02-24 01:51:14
【问题描述】:

我有一个接受四元数进行旋转变换的相机(在自定义 3D 引擎中)。我有两个 3D 点代表一个相机和一个要查看的对象。我想计算从相机到物体的四元数,同时尊重世界上轴

This question 要求相同的东西,但没有“向上”向量。所有三个答案都会导致相机指向正确的方向,但会滚动(如偏航/俯仰/滚动;想象在看某物时将头靠在耳朵上)。

我可以通过以下方式计算与所需坐标系匹配的向量的正交基:

lookAt = normalize(target - camera)
sideaxis = cross(lookAt, worldUp)
rotatedup = cross(sideaxis, lookAt)

如何从这三个向量创建一个四元数? This question 要求同样的事情......但不幸的是,唯一且被接受的答案是〜“让我们假设你不关心滚动”,然后开始忽略向上轴。我确实关心滚动。我不想忽略上轴。

【问题讨论】:

    标签: quaternions


    【解决方案1】:

    先前的答案给出了使用角度的有效解决方案。此答案将提供另一种方法。

    正交基向量,重命名为F = lookAt, R = sideaxis, U = rotatedup,直接形成3x3旋转矩阵的,相当于你想要的四元数:

    与向量相乘相当于将所述向量的分量作为相机基础中的坐标。

    可以将 3x3 旋转矩阵转换为四元数无需转换为角度/使用昂贵的三角函数。下面是一个数值稳定的 C++ sn-p,它会返回一个标准化的四元数:

    inline void CalculateRotation( Quaternion& q ) const {
      float trace = a[0][0] + a[1][1] + a[2][2];
      if( trace > 0 ) {
        float s = 0.5f / sqrtf(trace + 1.0f);
        q.w = 0.25f / s;
        q.x = ( a[2][1] - a[1][2] ) * s;
        q.y = ( a[0][2] - a[2][0] ) * s;
        q.z = ( a[1][0] - a[0][1] ) * s;
      } else {
        if ( a[0][0] > a[1][1] && a[0][0] > a[2][2] ) {
          float s = 2.0f * sqrtf( 1.0f + a[0][0] - a[1][1] - a[2][2]);
          q.w = (a[2][1] - a[1][2] ) / s;
          q.x = 0.25f * s;
          q.y = (a[0][1] + a[1][0] ) / s;
          q.z = (a[0][2] + a[2][0] ) / s;
        } else if (a[1][1] > a[2][2]) {
          float s = 2.0f * sqrtf( 1.0f + a[1][1] - a[0][0] - a[2][2]);
          q.w = (a[0][2] - a[2][0] ) / s;
          q.x = (a[0][1] + a[1][0] ) / s;
          q.y = 0.25f * s;
          q.z = (a[1][2] + a[2][1] ) / s;
        } else {
          float s = 2.0f * sqrtf( 1.0f + a[2][2] - a[0][0] - a[1][1] );
          q.w = (a[1][0] - a[0][1] ) / s;
          q.x = (a[0][2] + a[2][0] ) / s;
          q.y = (a[1][2] + a[2][1] ) / s;
          q.z = 0.25f * s;
        }
      }
    }
    

    来源:http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion

    将其转换为适合您的情况当然只是将矩阵元素与相应的向量分量交换:

    // your code from before
    F = normalize(target - camera);   // lookAt
    R = normalize(cross(F, worldUp)); // sideaxis
    U = cross(R, F);                  // rotatedup
    
    // note that R needed to be re-normalized
    // since F and worldUp are not necessary perpendicular
    // so must remove the sin(angle) factor of the cross-product
    // same not true for U because dot(R, F) = 0
    
    // adapted source
    Quaternion q;
    double trace = R.x + U.y + F.z;
    if (trace > 0.0) {
      double s = 0.5 / sqrt(trace + 1.0);
      q.w = 0.25 / s;
      q.x = (U.z - F.y) * s;
      q.y = (F.x - R.z) * s;
      q.z = (R.y - U.x) * s;
    } else {
      if (R.x > U.y && R.x > F.z) {
        double s = 2.0 * sqrt(1.0 + R.x - U.y - F.z);
        q.w = (U.z - F.y) / s;
        q.x = 0.25 * s;
        q.y = (U.x + R.y) / s;
        q.z = (F.x + R.z) / s;
      } else if (U.y > F.z) {
        double s = 2.0 * sqrt(1.0 + U.y - R.x - F.z);
        q.w = (F.x - R.z) / s;
        q.x = (U.x + R.y) / s;
        q.y = 0.25 * s;
        q.z = (F.y + U.z) / s;
      } else {
        double s = 2.0 * sqrt(1.0 + F.z - R.x - U.y);
        q.w = (R.y - U.x) / s;
        q.x = (F.x + R.z) / s;
        q.y = (F.y + U.z) / s;
        q.z = 0.25 * s;
      }
    }
    

    (如果您使用的是 OpenGL,则不用说交换 yz。)

    【讨论】:

    • 这看起来是一个完美的答案——输入匹配我的,指出我假设中的一个错误(需要标准化侧轴),有示例代码,使用 MathML——但它对我不起作用。我正在使用 OpenGL,我尝试在输入上交换 y 和 z。由此产生的四元数永远不会指向立方体的中心(无论是否滚动)。有什么错误的猜测,或者如何进一步调试?
    • @Phrogz 嗯,我不太确定;您可以上传代码的相关部分吗,例如相机设置?这可能是导致问题的一个单独的逻辑。
    • 最后的评论有点不准确。如果目标是 并且相机是 (向前看 -Z 轴),那么一切正常 :) 你愿意在聊天中与我见面以解决问题吗(如果/当我们解决问题时,我会在这里更新相关代码)?
    • @Phrogz 没问题(给我 30 分钟,我现在不在电脑旁,谢谢)
    • 如果 F、R 和 U 向量定义为 F=normalize(camera-target),则上述代码适用于 OpenGL; R = normalize(cross(worldup,F)); U = cross(F,R)。非常感谢 @meowgoesthedog 的史诗般的帮助,帮助对抗坐标转换并帮助调试它。
    【解决方案2】:

    假设您最初有三个正交向量:worldUp、worldFront 和 worldSide,让我们使用您的等式来表示 lookAt、sideAxis 和 rotateUp。实现结果不需要 worldSide 矢量。

    将操作一分为二。首先,围绕worldUp旋转。然后围绕 sideAxis 旋转,它现在实际上与旋转后的 worldSide 平行。

    Axis1 = worldUp
    Angle1 =(见下文)

    Axis2 = cross(lookAt, worldUp) = sideAxis
    Angle2 =(见下文)

    每个旋转对应一个四元数,使用:

    Q = cos(Angle/2) + i * Axis_x * sin(Angle/2) + j * Axis_y * sin(Angle/2) + k * Axis_z * sin(Angle/2)

    将 Q1 和 Q2 相乘,得到所需的四元数。

    角度的详细信息:

    令P(worldUp)为worldUp方向上的投影矩阵,即P(worldUp).v = cos(worldUp,v).worldUp或使用kets和bras, P(worldUp) = |worldUp >

    1. 在垂直于worldUp的平面上投影lookAt并对其进行归一化。

      tmp1 = (I - P(worldUp)).lookAt
      n1 = 归一化(tmp1)

    2. 角度1 = arccos(dot(worldFront,n1))

    3. Angle2 = arccos(dot(lookAt,n1))

    编辑1:

    请注意,无需计算超越函数。由于一对归一化向量的点积是一个角度的余弦,假设cos(t) = x,我们有三角恒等式:

    • cos(t/2) = sqrt((1 + x)/2)
    • sin(t/2) = sqrt((1 - x)/2)

    【讨论】:

      【解决方案3】:

      看看 侧轴 向上旋转

      如果你对这 3 个向量进行归一化,它是旋转矩阵 3x3 的一个分量。所以只需将此旋转矩阵转换为四元数。

      【讨论】:

      • 我建议这是一个很好的评论,但没有代码,不是一个非常有用的答案。你能用代码说明如何做到这一点吗?
      • 如果你使用一些数学库,它应该已经有从矩阵到四元数的转换过程。如果没有,请使用维基百科获取转换代码。
      猜你喜欢
      • 1970-01-01
      • 2012-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-03
      • 2012-06-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多