Unity Gyro Camera
传感器控制摄像头旋转 + 正北校准
纯原生支持Android+IOS,无需安装ARKit,ARCore等插件
这篇文章主要介绍如何利用手机原生的传感器,控制摄像头的旋转,最终可以实现AR或者VR的摄像头旋转控制
问题提出
虽然,目前有一些用手机传感器控制虚拟摄像头旋转的方案,但是实际在一些应用中,需要手机的旋转和物理世界的是一样的。也就是说,如果手机朝着物理世界的正北方向,游戏里的场景也是朝着正北方向的。如果您不需要做正北的校准,那么可以忽略这篇文章,Unity官方文档就有实现方式。
目前情况
虽然,目前网络上(无论国内还是国外)都有一些案例在Unity中利用传感器控制虚拟摄像头旋转的操作,但是总体来说存在以下的问题:
- 网上的方案都太旧了,适配Unity5或者IOS很早的版本
- 网上的方案层次不齐,有的方案写的代码太复杂了,最后化简下来都一样,但可能就看到一个复杂的方案,研究半天
- 目前的方案,在安卓下可以很好的解决问题(旋转和物理正北是一致的),但是在IOS的手机上却不成功,没有做正北的校准
- 网上又Unity Store中的方案,但是需要花钱购买(实际我购买后发现还是有问题的)
- 屏幕不同转向,结果不一样,有错误
- 或者需要安卓ARKit或者ARCore,ARFoundation等插件(如果要安装ARKit还需要申请摄像头访问权限,但实际上如果在VR的环境,完全不需要用到摄像头)
https://discussions.unity.com/t/how-can-i-make-the-camera-rotate-with-gyroscope-appropriately/220673
Unity gyroscope: Explained with code examples - VionixStudio
GitHub - Deankovitch/UnityGyroAccelCamera: Some way to make gyroscope and accelerometer work on all devices, in all Unity versions, using AHRS algoritm
Using gyroscope to control a camera? - Questions & Answers - Unity Discussions
[Sharing] Gyroscope Camera Script (iOS tested) - Unity Engine - Unity Discussions
Sensor Camera (Not AR): Gyroscope & Accelerometer | 镜头 | Unity Asset Store
GitHub - hbollon/GyroscopeControl: 🌀 Unity script used for smooth and customizable object rotation with gyroscope (initially configured to rotate x and z axis using x and y axis of gyro but can be easily edited). It include initial calibration with offset, rotation speed (Time.deltaTime * velocity), smoothing parameter editable in Unity inspector and debug overlay.
Quaternions applied to Sensor Fusion: gyro.attitude with compass - Questions & Answers - Unity Discussions
Match Unity camera with iPhone camera - Unity Engine - Unity Discussions
About attaching the unity camera to a gyro + magnetometer - Unity Engine - Unity Discussions
目标
所以,我们要实现的目标是:
- 兼容安卓/IOS
- 有正北校准
- 无需安装多余插件
- 无需创建多余游戏物体,直接代码附加在要控制的组件(虚拟摄像头)
- 支持各种屏幕旋转方向
问题的产生
之所以,在安卓的环境下,不会有正北校准的问题,是因为Unity的接口:
Input.gyro.attitude
本身已经融合了地磁的数据,所以无论手机在什么方向,都可以和物理的世界保持一致。但是IOS系统中,并没有做融合,需要手动操作。
虽然,我们可以通过:
Input.compass.trueHeading;
Input.compass.magneticHeading;
两个值,拿到当前物理正北的朝向,但是注意,这里返回的朝向是和屏幕方向密切相关的。它返回的值是,手机下方到上方的这条直线所在的方向,和物理世界的北极之间的角度。如果手机屏幕发生了旋转,最下方和最上方所在的轴并不是垂直与水平面的话,那么这个值是不准确的。
例如上方的示意图,在手机完全水平(从下往上的轴在水平面的时候),这个时候的trueHeading或者magneticHeading的值,是和正北的夹角,但是当手机不是水平的时候(图例右侧),则这个角度(橙色的角)并不是真的正北朝向,而是蓝色角度,即手机从下往上的轴在水平面的投影与正北的夹角。
解决方案
综上所述,可以按照以下的代码完成最后的正北校准。
using UnityEngine;public class GyroWithCompass : MonoBehaviour {private double _lastCompassUpdateTime = 0;private Quaternion _correction = Quaternion.identity;private Quaternion _targetCorrection = Quaternion.identity;void Start(){Input.location.Start();Input.gyro.enabled = true;Input.compass.enabled = true;}void Update(){//当前陀螺仪的值Quaternion q = Input.gyro.attitude;//转到unity坐标系Quaternion gyro = new(q.x, q.y, -q.z, -q.w);//因为在AR/VR环境,我们希望手机是竖起来的,所以需要x轴旋转90度Quaternion gyroOrientation = Quaternion.Euler(90, 0, 0) * gyro; #if UNITY_ANDROIDtransform.rotation = gyroOrientation; #else//下面需要做正北校准//如果地磁计有新数据的时候if (Input.compass.timestamp > _lastCompassUpdateTime){//存储刷新时间,以便判断是否有更新_lastCompassUpdateTime = Input.compass.timestamp;//根据水平仪(重力传感器,将手机从下网上的轴投影到水平面)Vector3 gravity = Input.gyro.gravity.normalized;Vector3 flatNorth = Input.compass.rawVector -Vector3.Dot(gravity, Input.compass.rawVector) * gravity;Quaternion compassOrientation = Quaternion.Inverse(Quaternion.LookRotation(flatNorth, -gravity));//转换到Unity坐标compassOrientation.z *= -1;compassOrientation.w *= -1;//计算最终正北校准需要的旋转四元数_targetCorrection = compassOrientation * Quaternion.Inverse(gyroOrientation);}//滤波_correction = Quaternion.Slerp(_correction,_targetCorrection,0.1f);//赋值transform.rotation = Quaternion.Euler(0, 180, 0) * _correction * gyroOrientation; #endif} }