Unity3D 四元数插值的实现
Posted 暗光之痕
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity3D 四元数插值的实现相关的知识,希望对你有一定的参考价值。
环境:Unity2018.3 语言:C#
总起:
本文主要参考《3D数学基础:图形与游戏开发》。
理论:
幂:
首先我们来看q^1/2,这是说明我们只需要q的一半位移,旋转是由θ来表示的,所以需要将w转换为θ(arccos(θ))后就能对其进行幂运算了。
平滑插值Slerp:
从q1到q2之间的差值为dq=inv(q1)q2,那么如果只需要t份dq,则表示为dq^t=(inv(q1)q2)^t。
最终的公式为:slerp(q0, q1, t) = q0 (inv(q1)q2)^t。
因为四元数可以说成是按照某个轴旋转一个角度,即[cos(θ/2) sin(θ/2)n],角度为θ,需要对其进行平滑插值,就是对θ进行插值即可。
实现:
这边我用Unity中的Quaternion类来减少一些类似叉乘的方法,专注于Slerp的实现。
首先我们来看一下Unity直接使用Quaternion进行插值的效果。
public class TestSlerp : MonoBehaviour
public Vector3 TargetRot;
public Quaternion targetRot;
private Quaternion startRot;
private float dTime = 0f;
void OnEnable()
dTime = 0f;
startRot = transform.rotation;
targetRot = Quaternion.Euler(TargetRot);
void Update()
dTime += Time.deltaTime;
if (dTime > 1f)
dTime = 1f;
enabled = false;
// 进行插值
transform.rotation = Quaternion.Slerp(startRot, targetRot, dTime);
跟Mathf.Lerp类似的使用。
好,现在我们根据上面的公式来实现一下Slerp:
public class TestSlerp2 : MonoBehaviour
public Vector3 TargetRot;
public Quaternion targetRot;
private Quaternion startRot;
private float dTime = 0f;
void OnEnable()
dTime = 0f;
startRot = transform.rotation;
targetRot = Quaternion.Euler(TargetRot);
void Update()
dTime += Time.deltaTime;
if (dTime > 1f)
dTime = 1f;
enabled = false;
// 进行插值
transform.rotation = MySlerp(startRot, targetRot, dTime);
Quaternion MySlerp(Quaternion a, Quaternion b, float t)
var q = (Quaternion.Inverse(a) * b);
var result = a * Power(q, t);
return result;
Quaternion Power(Quaternion q, float t)
float x = q.x;
float y = q.y;
float z = q.z;
float w = q.w;
// 防止除零
if (Mathf.Abs(w) < 0.9999f)
// 提取半角alpha = theta/2
float alpha = Mathf.Acos(w);
// 计算新的alpha
float newAlpha = alpha * t;
// 转换成w
w = Mathf.Cos(newAlpha);
// 消去sin(alpha)
float mult = Mathf.Sin(newAlpha) / Mathf.Sin(alpha);
x *= mult;
y *= mult;
z *= mult;
return new Quaternion(x, y, z, w);
以上是我直接照搬公式实现的插值方法,实际上书中的方法针对性得优化了一些性能:
public Quaternion SlerpBook(Quaternion a, Quaternion b, float t)
// 使用点乘计算四元数的夹角
float cosOmega = Quaternion.Dot(a, b);
// 如果点乘为负,则反转一个四元数取得最短的弧
if (cosOmega < 0f)
a.x = -a.x;
a.y = -a.y;
a.z = -a.z;
a.w = -a.w;
float k0, k1;
if (cosOmega > 0.9999f)
// 非常接近 进行线性插值
k0 = 1.0f - t;
k1 = t;
else
//使用sin^2(z)+cos^2(z)=1计算sin值
float sinOmega = Mathf.Sqrt(1.0f - cosOmega * cosOmega);
// 通过sin和cos计算角度
float omega = Mathf.Atan2(sinOmega, cosOmega);
// 计算分母的倒数
float oneOverSinOmega = 1.0f / sinOmega;
// 计算插值变量
k0 = Mathf.Sin((1.0f - t) * omega) * oneOverSinOmega;
k1 = Mathf.Sin(t * omega) * oneOverSinOmega;
// 最后插值
return new Quaternion(
a.x * k0 + b.x * k1,
a.y * k0 + b.y * k1,
a.z * k0 + b.z * k1,
a.w * k0 + b.w * k1);
今天研究了一天的四元数,肯定说不上完全理解吧,但是用法和特性都有了大体上的掌握。感触最深的是,其实很多东西以前学过,但是一直不用就完全忘记了,数学也是一门语言,需要时常锻炼对其的感觉。
以上是关于Unity3D 四元数插值的实现的主要内容,如果未能解决你的问题,请参考以下文章