具有不同速度的四元数lerp用于偏航/俯仰/横滚

时间:2018-10-06 21:21:35

标签: unity3d vector quaternions lerp

我想在unity3d中的三个不同轴(偏航/俯仰/横摇)上以不同速度在两次旋转之间进行紧缩,并尝试使用Quaternion.LookRotation()来实现。

Quaternion.LookRotation()将“方向向量”作为第一个参数,因此我认为我可以先确定方向,然后再使用“向上向量”进行查看。

Vector3.lerp()应该没问题,但是在这种情况下,我需要在相对于初始方向的两个轴(X和Y)上以不同的速度束紧方向。

例如,我有一个摄像头面向目标,然后该目标会向上和向右移动一点,现在我希望摄像头也向右缓慢倾斜,但要快一点直到目标位置(保持其位置自己的位置)。

如何在两个轴上以不同的速度束紧方向矢量以在Quaternion.LookRotation()中使用它?

编辑: 将标题从“ ”在X / Y上具有不同速度的Vector3之间的Lerp” 更改为“ 具有不同速度的偏航/俯仰/侧倾的四元数” ,并修改了要匹配的问题主题。

3 个答案:

答案 0 :(得分:0)

感谢minorlogic和CjLib,我尝试了以下操作:

<application>

要尝试执行此操作而不下载CjLib,请参见以下完整代码,其中包括用于解码挥杆/扭转的相关部分:

public Quaternion QuaternionLerpOn3Axis(
    Quaternion rot1,
    Quaternion rot2,
    Vector3 lerpSpeed
) {
    if (rot1 != rot2) {
        float lerpSpeedPitch = lerpSpeed.x * Time.deltaTime;
        float lerpSpeedYaw = lerpSpeed.y * Time.deltaTime;
        float lerpSpeedRoll = lerpSpeed.z * Time.deltaTime;
        // Lerp up direction
        Vector3 vecUp = Vector3.Slerp(
            rot1 * Vector3.up,
            rot2 * Vector3.up,
            lerpSpeedRoll
        );
        // Get new rotation with lerped yaw/pitch
        Quaternion rotation = QuaternionUtil.Sterp(
            rot1,
            rot2,
            rot1 * Vector3.right,
            lerpSpeedYaw,
            lerpSpeedPitch,
            QuaternionUtil.SterpMode.Slerp
        );
        // Look at new direction and return rotation
        return Quaternion.LookRotation(
            rotation * rot1 * Vector3.forward,
            vecUp
        );
    } else {
        return rot1;
    }
}

现在,这是我能够在三个不同的轴上获得具有不同速度的四元数环的唯一方法,并且结果是可以接受的。

但是我认为这不是一个真正的数学解决方案,如果lerp值低于〜1.5f(尤其是Z / Roll轴),它就不能很好地工作,而且开销很大。

有什么想法用更少/更好的代码解决这个难题吗?

答案 1 :(得分:0)

...另一种方法:

现在,我尝试将分解挥杆/扭转的概念扩展为分解偏航/俯仰/侧倾。

如果目标没有翻转超过180°,这仍然可以正常工作(?),并且仍然需要真正了解如何处理四元数旋转的人员进行输入/反馈。

public Quaternion QuaternionLerpYawPitchRoll(
    Quaternion rot1,
    Quaternion rot2,
    Vector3 lerpSpeed
) {
    if (rot1 != rot2) {
        float lerpSpeedPitch = lerpSpeed.x * Time.deltaTime;
        float lerpSpeedYaw = lerpSpeed.y * Time.deltaTime;
        float lerpSpeedRoll = lerpSpeed.z * Time.deltaTime;
        // Decompose quaternion into yaw/pitch/roll
        Quaternion rotYaw;
        Quaternion rotPitch;
        Quaternion rotRoll;
        DecomposeYawPitchRoll(rot1, rot2, out rotYaw, out rotPitch, out rotRoll);
        // Lerp swing & twist
        rotYaw = Quaternion.Slerp(Quaternion.identity, rotYaw, lerpSpeedYaw);
        rotPitch = Quaternion.Slerp(Quaternion.identity, rotPitch, lerpSpeedPitch);
        rotRoll = Quaternion.Slerp(Quaternion.identity, rotRoll, lerpSpeedRoll);
        // Combine yaw/pitch/roll with current rotation
        return Quaternion.LookRotation(
            rotPitch * rotYaw * rot1 * Vector3.forward,
            rotRoll * rot1 * Vector3.up
        );
    } else {
        return rot1;
    }
}
public static void DecomposeYawPitchRoll(
    Quaternion rot1,
    Quaternion rot2,
    out Quaternion yaw,
    out Quaternion pitch,
    out Quaternion roll
) {
    Vector3 pitchAxis = rot1 * Vector3.right;
    Vector3 rollAxis = rot1 * Vector3.forward;
    Vector3 yawAxis = rot1 * Vector3.up;
    // Get difference between two rotations
    Quaternion diffQ = rot2 * Quaternion.Inverse(rot1);

    Vector3 r = new Vector3(diffQ.x, diffQ.y, diffQ.z); // (rotation axis) * cos(angle / 2)
    float Epsilon = 1.0e-16f;

    // Singularity: rotation by 180 degree
    if (r.sqrMagnitude < Epsilon) {
        Vector3 rotatedPitchAxis = diffQ * pitchAxis;
        Vector3 rotatedYawAxis = Vector3.Cross(pitchAxis, rotatedPitchAxis);
        Vector3 rotatedRollAxis = diffQ * rollAxis;

        if (rotatedYawAxis.sqrMagnitude > Epsilon) {
            float yawAngle = Vector3.Angle(pitchAxis, rotatedPitchAxis);
            yaw = Quaternion.AngleAxis(yawAngle, rotatedYawAxis);
        } else {
            // More singularity: yaw axis parallel to pitch axis
            yaw = Quaternion.identity; // No yaw
        }
        if (rotatedRollAxis.sqrMagnitude > Epsilon) {
            float rollAngle = Vector3.Angle(yawAxis, rotatedYawAxis);
            roll = Quaternion.AngleAxis(rollAngle, rotatedRollAxis);
        } else {
            // More singularity: roll axis parallel to yaw axis
            roll = Quaternion.identity; // No roll
        }

        // Always twist 180 degree on singularity
        pitch = Quaternion.AngleAxis(180.0f, pitchAxis);
    } else {
        // Formula & proof: 
        // http://www.euclideanspace.com/maths/geometry/rotations/for/decomposition/
        pitch = GetProjectedRotation(diffQ, pitchAxis);
        roll = GetProjectedRotation(diffQ, rollAxis);
        yaw = diffQ * Quaternion.Inverse(pitch);
    }
}
public static Quaternion GetProjectedRotation(Quaternion rotation, Vector3 direction) {
    Vector3 r = new Vector3(rotation.x, rotation.y, rotation.z);
    Vector3 proj = Vector3.Project(r, direction);
    rotation = new Quaternion(proj.x, proj.y, proj.z, rotation.w);
    return Normalize(rotation);
}
public static Quaternion Normalize(Quaternion q) {
    float magInv = 1.0f / Magnitude(q);
    return new Quaternion(magInv * q.x, magInv * q.y, magInv * q.z, magInv * q.w);
}
public static float Magnitude(Quaternion q) {
    return Mathf.Sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
}

答案 2 :(得分:0)

此处是CjLib的作者。

听起来您实际上不需要摆动扭曲分解。 我想说的只是获得当前四元数和所需四元数的分解偏航/俯仰/行。然后根据希望它们分别跟踪目标值的速度来更新偏航/俯仰/行值,并从该组偏航/俯仰/行值中生成更新的四元数。

使用最大速度上限(我称为“寻求”)进行滚动可能会很好,但是看起来并不流畅。我建议使用临界阻尼数字弹簧。这是我为此主题撰写的三部分系列文章中的shameless placement