Unity欧拉旋转与万向锁

以前不在意,但现在旋转的时候乱七八糟的,搞的很烦,特来研究一下

这里不讲基础知识,比如x轴指向物体的右方,Inspector面板中x表示绕x旋转等等。若有疏漏,敬请指出。

 

unity中使用欧拉改变角度时并不会按照直觉中的来,你以为旋转是按照物体本身坐标系旋转的。像这样:

然后你写了一段欧拉的代码:

 //inputEulerX为判断是否按着x返回的值,按着返回1,否则返回0。shiftMultiplier表示按住shift时输入取反。
transform.eulerAngles += new Vector3(inputEulerX, inputEulerY, inputEulerZ)*shiftMultiplier ;

试一下效果:

难以控制,绕x轴旋转即是飞机朝自身上方爬升,然而爬到90度后,继续按住x,飞机就会剧烈抖动,无法继续爬升,此时按住y轴和x轴旋转方向皆是绕世界y轴旋转,导致像图里一样无法控制,最终飞出屏幕。而且这个写法在运行一会后就会发现角度逐渐混乱,角度会出现偏差。在unity中,即是你只控制欧拉角的x值,在一定条件下,其他轴也会产生微小的变化,偏差的这个锅扔给unity吧。而y轴和x轴旋转方向一致则是所谓的万向锁。

详细说一下万向锁的形成原因吧:

在unity中,进行一次欧拉旋转,是按照先绕y轴旋转,再绕x轴旋转,再绕Z轴旋转。绕三个轴旋转时形成各自形成形成一个旋转平面(或者说旋转环):

绿色代表绕y轴形成的旋转平面,红色代表绕x轴旋转形成的旋转平面,蓝色则是绕Z轴旋转形成的旋转平面。
而在Unity中!!!Y轴永远指向世界坐标的Y坐标,所以Y轴的旋转平面永远水平!!!

当绕y轴旋转时,会带动x轴旋转平面旋转,绕x轴旋转时又会带动Z轴选转平面旋转。什么意思呢,看下面这个图

将其想象成这样的一个机械结构,绿色是绕y轴,红色是绕x轴,蓝色(灰色?)是绕Z轴。注意其中的机械结构,绿色环,即y轴的旋转平面,固定在水平面上,只能水平旋转。x轴旋转平面嵌套在y轴旋转平面中,z轴嵌套在x轴旋转平面中,注意嵌套出的接口表示每个环只能朝向固定的方向旋转。旋转绿色环(绕y轴旋转),红色环跟随着转动,蓝色环也跟随着红色环转动,这就是unity的绕y轴旋转,转动完成后再转动x轴(红色环),蓝色环跟着红色环转动,当红色环绕x轴转动90度后,蓝色环就会和绿色环处于同一水平面,即y轴旋转平面和z轴旋转平面水平。此时旋转y轴和z轴,飞机都表现为只能绕世界y轴转动,这就是所谓的万向锁。当然还有其他万向锁,基本上都是其中两个旋转平面重合导致失去其中一个方向的旋转能力。这就是unity中的万向锁的形成原因。在unity的日常旋转控制中,只要限制x轴的角度不要达到90度基本上就可以避免万向锁的产生。

相关视频,一定要看https://www.bilibili.com/video/av76536794?from=search&seid=6785398138233897786。感谢这位up的搬运。

还可以参考另一篇博客:https://blog.csdn.net/fengya1/article/details/50721768

至于飞机的抖动和无法爬升,在unity官方文档中,明确说明eulerAngles只可获取并赋值,不能累加,因为超过360度就会失效。虽然我没有理解这句话,不过以后别累加了。(明明才90度啊,没有到360度啊,怎么就这样了呢,剧烈抖动又是因为什么呢?为什么无法继续旋转了呢,无法理解,有人明白的话麻烦解释一下)。总而言之,这段话可以无视。

 

那么我们不累加,而是使用一个额外的值来计算旋转值,然后将这个值赋值给欧拉:

  void Start()
    {
        InitialEuler = transform.eulerAngles;
       
    }
InitialEuler += new Vector3(inputEulerX, inputEulerY, inputEulerZ) * shiftMultiplier;
InitialEuler = new Vector3(Mathf.Clamp(InitialEuler.x, -80, 80), InitialEuler.y%360, InitialEuler.z%360);
Debug.Log(transform.eulerAngles + "_____" + transform.localEulerAngles);
transform.eulerAngles =InitialEuler ;

这里对x值进行了限制,不过不限制也并没有问题。使用这个写法,可以无限制的进行旋转,在x轴旋转90度后也能继续旋转,虽然此时依然有万向锁。在后面对y,z取余360的原因是避免值无限增大,如若物体不停旋转,超过了Vector3中数值类型的最大值就炸了。

 

你以为这样就能想最开始那张图一样运行了(至少我之前是那么想的)?错!好好思考一下,会有什么问题。

飞机在侧旋后想要朝飞机自身上方爬升时,爬升方向却莫名其妙(图中的急转弯)。

这并不算错误,而是用欧拉不适合这一用法,在之前的学习中我们了解到,飞机想要爬升,就要绕自身x轴旋转,而只有绕y轴旋转才能使x轴的旋转平面旋转,侧旋只是绕z轴旋转,所以无法影响到x轴旋转平面,所以当按下x时进行的爬升只与当前y轴平面有关系,只有绕y轴旋转后,爬升平面才会改变。

要想飞机像图1一样能够侧旋后爬升只需使用以下代码:

transform.Rotate(Vector3.right * inputEulerX * shiftMultiplier);
transform.Rotate(Vector3.up * inputEulerY * shiftMultiplier);
transform.Rotate(Vector3.forward * inputEulerZ * shiftMultiplier);

“你说半天欧拉结果给我用Rotate?”

不急,我将这么多是为了让你深入了解万向锁的原因和欧拉的旋转原理。欧拉适合类似第一人称视角或者第三人称视角的算法,只绕x和y轴旋转:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FPSCam : MonoBehaviour
{
    float mouseX = 0;
    float mouseY = 0;
    Vector3 InitialEuler = new Vector3(0, 0, 0);
    // Start is called before the first frame update
    void Start()
    {
        InitialEuler = transform.eulerAngles;

        Cursor.lockState = CursorLockMode.Locked;
    }
    // Update is called once per frame
    void Update()
    {
        mouseX = Input.GetAxis("Mouse X");
        mouseY = Input.GetAxis("Mouse Y");
        InitialEuler += new Vector3(-mouseY, mouseX, 0) ;
        InitialEuler = new Vector3(Mathf.Clamp(InitialEuler.x, -80, 80), InitialEuler.y, InitialEuler.z);
        transform.localEulerAngles = InitialEuler;
    }
}

只需这么一点代码,一个简单的带角度限制的第一人称相机就做好了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值