【问题标题】:How to Lerp between two quaternions?如何在两个四元数之间进行 Lerp?
【发布时间】:2018-02-19 18:23:33
【问题描述】:

我有两个四元数:

SCNVector4(x: -0.554488897, y: -0.602368534, z: 0.57419008, w: 2.0878818) 

SCNVector4(x: 0.55016619, y: 0.604441643, z: -0.576166153, w: 4.18851328)

如果我们创建两个对象,方向将非常相似

但是如果我们尝试从第一个到第二个 Lerp,那么位置会发生非常奇怪的变化(并且查看预期但不正确的值)

[Lerp 进度演示][1]

我在谷歌上搜索并发现了许多功能来做 lerp,例如简单的一个:

extension SCNVector4 {

    func lerp(to: SCNVector4, v: Float) -> SCNVector4 {

        let aX = x + (to.x - x) * v
        let aY = y + (to.y - y) * v
        let aZ = z + (to.z - z) * v
        let aW = w + (to.w - w) * v
        
        return SCNVector4Make(aX, aY, aZ, aW)
        
    }
}

但是如何避免这种奇怪的翻转呢?

PS:我尝试了 GLKit 的不同功能,但结果是一样的 [1]:https://i.stack.imgur.com/8jEvm.png

- 正如建议的那样尝试翻转符号,但问题是我得到的点积大于 0

    extension SCNVector4 {
    
    func glk() -> GLKQuaternion {
        return GLKQuaternion(q: (x, y, z, w))
    }
    
    func lerp(to: SCNVector4, v: Float) -> SCNVector4 {
        
        let a = GLKQuaternionNormalize(glk())
        let b = GLKQuaternionNormalize(to.glk())
        
        let dot =
            a.x * b.x +
            a.y * b.y +
            a.z * b.z +
            a.w * b.w
        
        var target = b
        if dot < 0 {
            target = GLKQuaternionInvert(b)
        }
        
        let norm = GLKQuaternionNormalize(GLKQuaternionSlerp(a, target, v))
        
        return norm.scn()
        
    }
    
}

extension GLKQuaternion {
    
    func scn() -> SCNVector4 {
        return SCNVector4Make(x, y, z, w)
    }
    
}

【问题讨论】:

    标签: scenekit quaternions lerp


    【解决方案1】:

    如果您问我,您列出的 quat 值似乎是错误的。 'w' 值 2 或 4 不会加起来归一化的 quat,所以我不会惊讶地发现它们会给你奇数。当使用四元组进行旋转时,它们应该是单位长度(并且这两个四元组不是单位长度)。

    至于 lerping,您基本上希望使用标准化 lerp (nlerp) 或球形 lerp (slerp)。当您从一个 quat 旋转到另一个 quat 时,NLerp 会导致轻微的加速/减速。 Slerp 为您提供恒定的角速度(尽管它使用正弦,因此计算速度较慢)。

    float dot(quat a, quat b)
    {
      return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
    }
    
    quat negate(quat a)
    {
      return quat(-a.x, -a.y, -a.z, -a.w);
    }
    
    quat normalise(quat a)
    {
      float l = 1.0f / std::sqrt(dot(a, a));
      return quat(l*a.x, l*a.y, l*a.z, l*a.w);
    }
    
    quat lerp(quat a, quat b, float t) 
    {
      // negate second quat if dot product is negative
      const float l2 = dot(a, b);
      if(l2 < 0.0f) 
      {
        b = negate(b);
      }
      quat c;
      // c = a + t(b - a)  -->   c = a - t(a - b)
      // the latter is slightly better on x64
      c.x = a.x - t*(a.x - b.x);
      c.y = a.y - t*(a.y - b.y);
      c.z = a.z - t*(a.z - b.z);
      c.w = a.w - t*(a.w - b.w);
      return c;
    }
    
    // this is the method you want
    quat nlerp(quat a, quat b, float t) 
    {
      return normalise(lerp(a, b, t));
    }
    

    /编辑

    您确定它们是 quat 值吗?如果你问我,这些值看起来很像轴角度值。尝试通过这个转换函数运行这些值,看看是否有帮助:

    quat fromAxisAngle(quat q)
    {
      float ha = q.w * 0.5f;
      float sha = std::sin(ha);
      float cha = std::cos(ha);
      return quat(sha * q.x, sha * q.y, sha * q.z, cha);
    }
    

    我从你的原始值中得到了这两个结果:

    (-0.479296037597, -0.520682836178, 0.496325592199, 0.50281768624)

    (0.47649598094, 0.523503659143, -0.499014409188, -0.499880083257)

    【讨论】:

    • >当使用 quats 进行旋转时,它们应该是单位长度,你觉得呢,应该规范化解决这个问题吗?我从 colada 文件中获得了如此“奇怪”的值,并且无法更改它
    • 试图回应,但最终添加到我的原始答案中。这些值对我来说就像轴角度值。看来您需要先将它们转换为 quats?
    • 这个值我直接从 SCNNode 的旋转属性和对象的实际方向几乎相同
    • 是的,两个轴角度旋转,一旦转换为四元数,或多或少是彼此的否定。这里的问题是您获得的数据不是四元数形式 - 它是轴角表示。您需要预处理该数据,使其采用四元数形式(因为与轴角相比,quats 更易于使用)。我上面提供的 nlerp 代码将完美地适用于我在编辑后的帖子末尾列出的这两个 quat 值。
    • 好的,你说得对,问题是 SCNVector4 不是四元数。我应该如此简单地使用 SCNNode 的方向属性...
    【解决方案2】:
    1. 如果四元数点积为负,则进行符号翻转。
    2. 标准化生成的四元数。

    【讨论】:

    • 很快就会尝试。或者你有完整的功能?
    • 不正常化节点坏了之后不行,发帖前请自己检查,看我的清单3
    • 1. GLKQuaternionInvert 可能会反转四元数,但不会更改符号。手动更改所有四元数组件的符号。 2. 你在标准化之前制作了 GLKQuaternionSlerp,但我建议改为 LERP。 3. GLKQuaternionSlerp 可以做你需要的。它是四元数之间的某种插值。尝试使用它来代替您手工制作的 LERP。 ?
    【解决方案3】:

    最新的 SDK 具有 &lt;simd/quaternion.h&gt;,它公开了 simd_quatf 类型来表示四元数。还公开了处理四元数的不同实用程序,其中simd_slerp 为四元数插值做了“正确的事情”。

    在 iOS 11 和 macOS 10.13 中,SceneKit 公开了新的 API 来直接处理 SIMD 类型。例如,除了SCNNode.orientation,您现在还可以访问SCNNode.simdOrientation

    编辑

    大多数 SIMD API 是内联的,因此可以在比 SDK 版本更早的操作系统版本上使用。如果你真的想坚持使用 GLKit,他们的球面插值版本是 GLKQuaternionSlerp

    【讨论】:

    • 更新了我的评论是你真的想坚持使用 GLKit。但是你不应该有 yo,因为 SIMD 实用程序是内联的,并且很容易将 simd_quatf 桥接到 SCNQuaternion
    猜你喜欢
    • 2022-08-08
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 1970-01-01
    • 2020-11-06
    • 1970-01-01
    • 2020-01-01
    • 1970-01-01
    相关资源
    最近更新 更多