四元数“lookAt”函数

Posted

技术标签:

【中文标题】四元数“lookAt”函数【英文标题】:Quaternion "lookAt" function 【发布时间】:2012-09-08 06:38:50 【问题描述】:

我正在努力解决以下问题。我正在处理骨骼动画,我希望(即)玩家的头部跟随空间中的另一个对象。我的上轴是 +Z 我的前轴是 +Y,四元数的大小在 W。我尝试使用 gluLookAt 的台面代码并使用 3x3 矩阵转换为四元数,但它没有按预期工作所以我往另一个方向走……

到目前为止,我得到以下代码“几乎”工作,至少玩家的头部正在向好的方向旋转(但 X 角度似乎会影响 Y 旋转轴),但它看起来是直视而不是跟随地板上大约 65 度角的物体:

qt LookRotation( v3 lookAt, v3 upDirection )

qt t;

v3 forward = lookAt;
v3 up = upDirection;

OrthoNormalize( &forward, &up );

v3 right = v3_cross( up, forward );

mat3 m = mat3_make( right.x, up.x, forward.x,
                    right.y, up.y, forward.y,
                    right.z, up.z, forward.z );

t.w = sqrtf( 1.0f +
             m.r[ 0 ].x +
             m.r[ 1 ].y +
             m.r[ 2 ].z ) * 0.5f;

float w4_recip = 1.0f / ( 4.0f * t.w );

t.x = ( m.r[ 2 ].y - m.r[ 1 ].z ) * w4_recip;

t.y = ( m.r[ 0 ].z - m.r[ 2 ].x ) * w4_recip;

t.z = ( m.r[ 1 ].x - m.r[ 0 ].y ) * w4_recip;

t = qt_normalize( t );

return t;

... ... ...

v3 v = v3_sub( vec4_to_v3( transform.world.r[ 3 ] /* The object XYZ location in the world */),
           skeleton->final_pose.location[ i ] /* i = The head joint location */ );

v = v3_normalize( v );

qt q = LookRotation( v,
        v3_make( 0.0f, 0.0f, 1.0f ) );

有人可以帮我解决这个问题吗...我对四元数有点陌生,真的不知道我可能在哪里搞砸了。经过相当多的研究,基本上我想做的是 Unity API 之类的东西:http://docs.unity3d.com/Documentation/ScriptReference/Quaternion.LookRotation.html

【问题讨论】:

另见this answer,它认为世界上轴是为了防止四元数滚动。 【参考方案1】:

我认为这个功能会满足你的需要:

/// <summary>
/// Evaluates a rotation needed to be applied to an object positioned at sourcePoint to face destPoint
/// </summary>
/// <param name="sourcePoint">Coordinates of source point</param>
/// <param name="destPoint">Coordinates of destionation point</param>
/// <returns></returns>
public static Quaternion LookAt(Vector3 sourcePoint, Vector3 destPoint)

    Vector3 forwardVector = Vector3.Normalize(destPoint - sourcePoint);

    float dot = Vector3.Dot(Vector3.forward, forwardVector);

    if (Math.Abs(dot - (-1.0f)) < 0.000001f)
    
        return new Quaternion(Vector3.up.x, Vector3.up.y, Vector3.up.z, 3.1415926535897932f);
    
    if (Math.Abs(dot - (1.0f)) < 0.000001f)
    
        return Quaternion.identity;
    

    float rotAngle = (float)Math.Acos(dot);
    Vector3 rotAxis = Vector3.Cross(Vector3.forward, forwardVector);
    rotAxis = Vector3.Normalize(rotAxis);
    return CreateFromAxisAngle(rotAxis, rotAngle);


// just in case you need that function also
public static Quaternion CreateFromAxisAngle(Vector3 axis, float angle)

    float halfAngle = angle * .5f;
    float s = (float)System.Math.Sin(halfAngle);
    Quaternion q;
    q.x = axis.x * s;
    q.y = axis.y * s;
    q.z = axis.z * s;
    q.w = (float)System.Math.Cos(halfAngle);
    return q;

这段代码来自这里: https://gamedev.stackexchange.com/questions/15070/orienting-a-model-to-face-a-target 我只是稍微修改了它以适应我的情况,即在不使用 Unity3D 的情况下实现了 transform.LookAt。

【讨论】:

在我看来,您还应该在第一次旋转后使用 up 向量来修复坐标系 - 在其他情况下,您的变换会得到不需要的滚动角。 为什么是dot - (-1.0f) 而不是dot + 1.0f @AndyRay 还有一个比较是dot - (...),括号内的值可以是1.0f-1.0f。以类似的方式输入它们是有意义的,所以看起来不错。【参考方案2】:

您不需要使用acosaxis angle(这又会执行另外两个三角函数)从 2 个向量中获取四元数:

public static Quaternion LookAt(Vector3 sourcePoint, Vector3 destPoint)

    Vector3 forwardVector = Vector3.Normalize(destPoint - sourcePoint);

    Vector3 rotAxis = Vector3.Cross(Vector3.forward, forwardVector);
    float dot = Vector3.Dot(Vector3.forward, forwardVector);

    Quaternion q;
    q.x = rotAxis.x;
    q.y = rotAxis.y;
    q.z = rotAxis.z;
    q.w = dot+1;

    return q.normalize();

点+1 和后续标准化的原因是因为如果不这样做,您将获得双旋转的四元数。这两个步骤将有效地执行slerp(identity, q, 0.5),这将是正确的四元数。

【讨论】:

这在很多情况下都不起作用。例如,考虑source = 0,0,0; dest=0,0,0; forward=1,0,0 这行得通,但是缺少一个向上的向量来防止四元数滚动。 This answer 防止四元数滚动。【参考方案3】:

当前的两个答案都存在边缘情况的各种问题。由于其他原因,接受的答案也不正确,包括它为其中一种情况设置 w=pi 并且它没有做适当的规范。在环顾四周并测试了几个案例后,我还发现您需要前向和向上向量来进行此计算。所以下面是我正在使用的代码:

Quaternion lookAt(const Vector3f& sourcePoint, const Vector3f& destPoint, const Vector3f& front, const Vector3f& up)

    Vector3f toVector = (destPoint - sourcePoint).normalized();

    //compute rotation axis
    Vector3f rotAxis = front.cross(toVector).normalized();
    if (rotAxis.squaredNorm() == 0)
        rotAxis = up;

    //find the angle around rotation axis
    float dot = VectorMath::front().dot(toVector);
    float ang = std::acosf(dot);

    //convert axis angle to quaternion
    return Eigen::AngleAxisf(rotAxis, ang);

Bove 使用流行的 Eigen 库。如果您不想使用它,那么您可能需要以下替换 Eigen::AngleAxisf

//Angle-Axis to Quaternion
Quaternionr angleAxisf(const Vector3r& axis, float angle) 
    auto s = std::sinf(angle / 2);
    auto u = axis.normalized();
    return Quaternionr(std::cosf(angle / 2), u.x() * s, u.y() * s, u.z() * s);

请注意,点积 0 或 1 或 -1 的特殊情况会自动处理,因为 normalized() 为特征库中的零向量返回 0。

在旁注中,对于您所有的转换问题,this 是一个很好的文档。

【讨论】:

非常有用,除了你实际上没有使用up,除了||rotAxis|| == 0的边缘情况。我得到的结果是颠倒的,所以我想有一些小的缺失。 快速更新 .... 颠倒的事情与 up 未被使用无关,这是因为 cosf 在我的四元数实现中应该放在最后。 什么是front和up vector?

以上是关于四元数“lookAt”函数的主要内容,如果未能解决你的问题,请参考以下文章

如何理解glm中四元数与向量相乘函数的写法

四元数

从四元数到 OpenGL 旋转

3D数学基础四元数和欧拉角

基于四元数的 3D 相机应该累积四元数还是欧拉角?

四元数和归一化