将点积夹在四元数的Slerp中

时间:2013-06-12 13:04:34

标签: math graphics 3d quaternions

我正在研究两个四元数之间插值的Slerp方法的两个不同来源。除了一个值得注意的区别之外,它们非常相似:一个将点积夹在0和1之间,另一个夹在-1和1之间。Here就是其中之一:

glm::fquat Slerp(const glm::fquat &v0, const glm::fquat &v1, float alpha)
{
    float dot = glm::dot(v0, v1);

    const float DOT_THRESHOLD = 0.9995f;
    if (dot > DOT_THRESHOLD)
        return Lerp(v0, v1, alpha);

    glm::clamp(dot, -1.0f, 1.0f); //<-- The line in question
    float theta_0 = acosf(dot);
    float theta = theta_0*alpha;

    glm::fquat v2 = v1 - v0*dot;
    v2 = glm::normalize(v2);

    return v0*cos(theta) + v2*sin(theta);
}

这是另一个:

template <typename T>
inline QuaternionT<T> QuaternionT<T>::Slerp(T t, const QuaternionT<T>& v1) const
{
    const T epsilon = 0.0005f;
    T dot = Dot(v1);

    if (dot > 1 - epsilon) {
        QuaternionT<T> result = v1 + (*this - v1).Scaled(t);
        result.Normalize();
        return result;
    }

    if (dot < 0) //<-The lower clamp
        dot = 0;

    if (dot > 1)
        dot = 1;

    T theta0 = std::acos(dot);
    T theta = theta0 * t;

    QuaternionT<T> v2 = (v1 - Scaled(dot));
    v2.Normalize();

    QuaternionT<T> q = Scaled(std::cos(theta)) + v2.Scaled(std::sin(theta));
    q.Normalize();
    return q;
}

我认为值得注意的是,第二个算法中的Lerp算法似乎并不适用于所有情况?

我只是希望得到一些关于这些差异的反馈,以及它们是否真的重要。

1 个答案:

答案 0 :(得分:4)

如果您在两个四元数slerpq1之间进行q2,即使您在将它们传递给函数之前将它们标准化,浮点差异也可以使它他们的内在产品最终略大于一个或小于负一个。这会使acos崩溃。当然,在您提供的两个代码段中,如果dot(q1,q2)>1,代码执行线性插值并立即返回。因此不需要钳位到+1。我没有看到第二个例子中Lerp的任何特殊问题。

对于这两种情况,通常不需要钳制到0或-1,这可能是一个坏主意。

主要的是,如果两个四元数的内积是负的,那么它们之间的插值就意味着要走很远的路。为了澄清,如果两条线之间的角度是30度,您还可以通过旋转330度将一条线插入到另一条线上。方位空间也一样。

单位四元数是方向的2倍冗余表示;因此,如果两个四元数之间的内积小于零,则通常在插值之前否定其中一个元素的所有元素。

如果想要长途插值,则钳制到-1是正确的。将点积压缩为零会使事情变得非常糟糕。如果想要插入很长的路径,那么在将四元数传递给这些函数之前,应始终确保非负内积,因为它们不会为您执行此操作。因此不需要钳制到零。