在四元数的球面插值(Slerp)中对点积进行夹紧

3

我正在查看两个关于Slerp方法在两个四元数之间插值的源代码。它们非常相似,但有一个显著的区别:一个将点积夹在0和1之间,另一个将其夹在-1和1之间。其中这里是其中之一:

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个回答

4
如果你在两个四元数q1和q2之间进行slerp操作,即使在将它们传递到函数之前正确归一化它们,浮点差异也可能导致它们的内积略大于1或小于-1。这会导致acos崩溃。当然,在提供的两个代码片段中,如果dot(q1,q2)>1,则代码执行线性插值并立即返回。因此,将其夹紧到+1是不必要的。我没有看到第二个示例中Lerp的任何特殊问题。
对于这两种情况,夹紧到0或-1通常是不必要的,并且可能是一个坏主意。
主要问题是,如果两个四元数的内积为负,则在它们之间进行插值意味着绕远路走。澄清一下,如果两条线之间的角度为30度,则您也可以通过旋转330度将一条线插入另一条线。同样适用于方向空间。
单位四元数是方向的2倍冗余表示;因此,如果两个四元数之间的内积小于零,则通常在插值之前反转其中一个四元数的所有元素。
如果您确实想要沿长路插值,则将其夹紧到-1是正确的。将点积夹紧到零会破坏事情。如果您不想沿长路插值,则应始终确保在将四元数传递给这两个函数之前存在非负的内积,因为它们不会为您执行此操作。因此,将其夹紧到零是不必要的。

说得好。第二段代码也不能通过一个简单的嗅探测试。它首先检查1-eps,然后再夹到1。即使你想在代码中非常防御性地处理,acos应该在-1或-1+eps处夹住,而不是在0处夹住。 - starmole

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