unity中camera摄像头控制详解

2021-05-18 00:30

阅读:298

在Unity的Transform中,rotation属性对应的就是欧拉角,一共分为3个轴,x、 y和z,而每一个数值对应的是绕对应的轴旋转的度数。

如上图所示,表示按照坐标顺序旋转,X轴旋转30°,Y轴旋转90°,Z轴旋转 10°。欧拉角的优点:只需使用3个值,即三个坐标轴的旋转角度;缺点:必须 严格按照顺序进行旋转(顺序不同结果就不同;容易造成“万向节锁”现象,造 成这个现象的原因是因为欧拉旋转是按顺序先后旋转坐标轴的,并非同时旋转, 所以当旋转中某些坐标重合就会发生万向节锁,这时就会丢失一个方向上的选择 能力,除非打破原来的旋转顺序或者三个坐标轴同时旋转;由于万向节锁的存在, 欧拉旋转无法实现球面平滑插值。

四元数是用于表示旋转的一种方式,而且transform中的rotation属性的数据类 型就是四元数,那么四元数该如何表示呢?从本质上来讲,四元数就是一个高阶 复数,也就是一个四维空间。话说当时十九世纪的时候,爱尔兰的数学家 Hamilton一直在研究如何将复数从2D扩展至3D,他一直以为扩展至3D应该有两个 虚部(可是他错了,哈哈)。有一天他在路上突发奇想,我们搞搞三个虚部的试 试!结果他就成功了,于是乎他就把答案刻在了Broome桥上。说到这里,也就明 白了,四元数其实就是定义了一个有三个虚部的复数w xi yj zk。记法 [w,(x,y,z)]。四元数优点:可以避免万向节锁现象;只需要一个4维的四元数就 可以执行绕任意过原点的向量的旋转,方便快捷,在某些实现下比旋转矩阵效率 更高;可以提供平滑插值;缺点:比欧拉旋转稍微复杂了一点点,因为多了一个 维度;理解更困难,不直观。四元数与欧拉角转换:

// 获取摄像机欧拉角
Vector3 angles = transform.eulerAngles;
// 设置摄像头欧拉角
targetRotation.eulerAngles = new Vector3(euler.y, euler.x, 0);

现在让我们再看Update里的旋转代码:

if (Mathf.Abs(dx) > 0 || Mathf.Abs(dy) > 0)
{
        // 获取摄像机欧拉角
        Vector3 angles = transform.rotation.eulerAngles;
        // 欧拉角表示按照坐标顺序旋转,比如angles.x=30,表示按x轴旋转30°,dy改变引起x轴的变化
        angles.x = Mathf.Repeat(angles.x + 180f, 360f) - 180f;
        angles.y += dx;
        angles.x -= dy;
        // 设置摄像头旋转
        Quaternion rotation = Quaternion.identity;
        rotation.eulerAngles = new Vector3(angles.x, angles.y, 0);
        transform.rotation = rotation;
        // 重新设置摄像头位置
        Vector3 position = model.position;
        Vector3 distance = rotation * new Vector3(0, 0, default_distance);
        transform.position = position - distance;
  }

首先我们从四元数(transform.rotation)取得欧拉角angles,由于欧拉角表示按 照坐标顺序旋转,比如angles.x=30,表示按x轴旋转30°,dy改变引起欧拉角的 x轴的变化,所以angles.y+=dx,然后再设置摄像头旋转,即设置摄像头四元数 rotation,现在明白了设置旋转的写法了吧。

下面是重点,重新设置摄像头位置,我们看到rotation*new Vector3(0,0,default_distance)这句,一个Quaternion实例和一个Vector3相乘 的运算,作用是对参数坐标点进行rotation变换,也就是说对 Vector3(0,0,default_distance)进行rotation旋转,最后一句 transform.position = position - distance,进行一个Vector3的向量计算, 最终结果就是摄像头沿着选中后的方向移动-distance的距离,就是我们要的结果。

如果对向量计算不清楚,请看下面的向量计算这节

在进行下面开发之前我们把程序西安优化一下,我们不在Update函数里直接修改 摄像头旋转和位置,而是记录旋转变化,在FixUpdate函数里设置摄像头最终的 旋转和位置,Update和FixedUpdate的区别:Update跟当前平台的帧数有关,而 FixedUpdate是真实时间,所以处理物理逻辑的时候要把代码放在FixedUpdate而 不是Update。

using UnityEngine;

/**
 * 自由摄像头
 * 2018-10-03 by flysic, 119238122@qq.com
 */
public class FreeCameraController : MonoBehaviour
{
        // 模型
        public Transform model;
        // 默认距离
        private const float default_distance = 5f;

        // 计算移动
        private Vector3 position;
        // 计算旋转
        private Quaternion rotation;

        // Use this for initialization
        void Start ()
        {
                // 旋转归零
                transform.rotation = Quaternion.identity;
                // 初始位置是模型
                position = model.position;
        }

        // Update is called once per frame
        void Update()
        {
                float dx = Input.GetAxis("Mouse X");
                float dy = Input.GetAxis("Mouse Y");
                // 鼠标右键旋转
                if (Input.GetMouseButton(1))
                {
                        if (Mathf.Abs(dx) > 0 || Mathf.Abs(dy) > 0)
                        {
                                // 获取摄像机欧拉角
                                Vector3 angles = transform.rotation.eulerAngles;
                                // 欧拉角表示按照坐标顺序旋转,比如angles.x=30,表示按x轴旋转30°,dy改变引起x轴的变化
                                angles.x = Mathf.Repeat(angles.x + 180f, 360f) - 180f;
                                angles.y += dx;
                                angles.x -= dy;
                                // 计算摄像头旋转
                                rotation.eulerAngles = new Vector3(angles.x, angles.y, 0);
                        }
                }
        }

        private void FixedUpdate()
        {
                // 设置摄像头旋转
                transform.rotation = rotation;
                // 设置摄像头位置
                transform.position = position - rotation * new Vector3(0, 0, default_distance);
        }
}

最上面定义了两个私有属性,private Vector positon,private Quaternion rotation,position在Start函数里记录模型的位置(目前不变化,后面移动时要 变化),rotation用于记录Update里计算的旋转,FixedUpdate函数里根据 rotation、position、default_distance计算摄像头最终的位置。

我们操作一下发现,虽然旋转达到要求,但是操作感觉很生硬,现在给旋转加上 速度和阻尼,效果就会好很多。

using UnityEngine;

/**
 * 自由摄像头
 * 2018-10-03 by flysic, 119238122@qq.com
 */
public class FreeCameraController : MonoBehaviour
{
        // 模型
        public Transform model;
        // 旋转速度
        public float rotateSpeed = 32f;
        public float rotateLerp = 8;    

        // 计算移动
        private Vector3 position;
        // 计算旋转
        private Quaternion rotation, targetRotation;
        // 默认距离
        private const float default_distance = 5f;

        // Use this for initialization
        void Start ()
        {
                // 旋转归零
                transform.rotation = Quaternion.identity;
                // 初始位置是模型
                position = model.position;
        }

        // Update is called once per frame
        void Update()
        {
                float dx = Input.GetAxis("Mouse X");
                float dy = Input.GetAxis("Mouse Y");

                // 鼠标右键旋转
                if (Input.GetMouseButton(1))
                {
                        dx *= rotateSpeed;
                        dy *= rotateSpeed;
                        if (Mathf.Abs(dx) > 0 || Mathf.Abs(dy) > 0)
                        {
                                // 获取摄像机欧拉角
                                Vector3 angles = transform.rotation.eulerAngles;
                                // 欧拉角表示按照坐标顺序旋转,比如angles.x=30,表示按x轴旋转30°,dy改变引起x轴的变化
                                angles.x = Mathf.Repeat(angles.x + 180f, 360f) - 180f;
                                angles.y += dx;
                                angles.x -= dy;
                                // 计算摄像头旋转
                                targetRotation.eulerAngles = new Vector3(angles.x, angles.y, 0);
                        }
                }
        }

        private void FixedUpdate()
        {
                rotation = Quaternion.Slerp(rotation, targetRotation, Time.deltaTime * rotateLerp);

                // 设置摄像头旋转
                transform.rotation = rotation;
                // 设置摄像头位置
                transform.position = position - rotation * new Vector3(0, 0, default_distance);
        }
}

最上面增加了旋转速度(rotateSpeed)和苏尼(rotateLerp),rotateSpeed越高旋 转越快,rotateLerp越高阻尼越小,阻尼使用了四元数的球面差值(前面说过, 只有四元数能做到球面差值),使旋转有个渐变过程,大家可以在Inspector的 tabFree Camera Controller脚本处修改参数尝试最佳的设置;定义了新的变量 targetRotation,用于计算最终旋转,配合rotation实现阻尼效果;positon目 前只是记录模型位置,后面移动时就会改变。


评论


亲,登录后才可以留言!