如何在两个四元数之间进行 Lerp?
How to Lerp between two quaternions?
我有两个四元数:
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)
}
}
最新的 SDK 具有 <simd/quaternion.h>
,它公开了 simd_quatf
类型来表示四元数。还公开了处理四元数的不同实用程序,其中 simd_slerp
对四元数插值执行 "the right thing"。
在 iOS 11 和 macOS 10.13 中,SceneKit 公开了新的 API 来直接处理 SIMD 类型。例如除了 SCNNode.orientation
you now also have access to SCNNode.simdOrientation
.
编辑
大多数 SIMD API 都是内联的,因此可以在 SDK 版本之前的 OS 版本上使用。如果你真的想坚持使用 GLKit,他们的球形插值版本是 GLKQuaternionSlerp
.
- 如果四元数点积为负,则进行符号翻转。
- 归一化生成的四元数。
如果你问我,你列出的 quat 值似乎是错误的。 'w' 2 或 4 的值加起来不等于标准化的 quat,所以我对 lerping 它们给你奇数值并不感到惊讶。当使用 quats 进行旋转时,它们应该是单位长度(这两个 quats 不是单位长度)。
至于 lerping,您基本上想要使用归一化 lerp (nlerp) 或球形 lerp (slerp)。当您从一个 quat 旋转到另一个时,NLerp 会导致轻微的加速/减速。 Slerp 为您提供恒定的 angular 速度(尽管它使用正弦,因此计算速度较慢)。
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);
}
我从你的原始值得到这两个结果 quats:
(-0.479296037597, -0.520682836178, 0.496325592199, 0.50281768624)
(0.47649598094, 0.523503659143, -0.499014409188, -0.499880083257)
我有两个四元数:
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)
}
}
最新的 SDK 具有 <simd/quaternion.h>
,它公开了 simd_quatf
类型来表示四元数。还公开了处理四元数的不同实用程序,其中 simd_slerp
对四元数插值执行 "the right thing"。
在 iOS 11 和 macOS 10.13 中,SceneKit 公开了新的 API 来直接处理 SIMD 类型。例如除了 SCNNode.orientation
you now also have access to SCNNode.simdOrientation
.
编辑
大多数 SIMD API 都是内联的,因此可以在 SDK 版本之前的 OS 版本上使用。如果你真的想坚持使用 GLKit,他们的球形插值版本是 GLKQuaternionSlerp
.
- 如果四元数点积为负,则进行符号翻转。
- 归一化生成的四元数。
如果你问我,你列出的 quat 值似乎是错误的。 'w' 2 或 4 的值加起来不等于标准化的 quat,所以我对 lerping 它们给你奇数值并不感到惊讶。当使用 quats 进行旋转时,它们应该是单位长度(这两个 quats 不是单位长度)。
至于 lerping,您基本上想要使用归一化 lerp (nlerp) 或球形 lerp (slerp)。当您从一个 quat 旋转到另一个时,NLerp 会导致轻微的加速/减速。 Slerp 为您提供恒定的 angular 速度(尽管它使用正弦,因此计算速度较慢)。
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);
}
我从你的原始值得到这两个结果 quats:
(-0.479296037597, -0.520682836178, 0.496325592199, 0.50281768624)
(0.47649598094, 0.523503659143, -0.499014409188, -0.499880083257)