AR/VR的到来,让我们越来越离不开处理各种sensor数据的工作。尤其是来自手机IMU的数据,都可以使用API获取到。我们所要做的就是认识这些数据,正确的应用给物体。本文主要通过一个简单式例,介绍一下基于slam的ar应用中,常见的地面检测的标志物的制作。如图,该标志物辅助用户在现实中,指定创建三维物体的位置。
Setup
1. Camera设置
所有AR应用中,至少有两个摄像机,用来渲染Webcam视频的"背景Camera"和用来渲染前景三维物体的"ARCamera"。
背景Camera设置Clear Flags=Solid Color,CullingMask=UI,Project=Orthographic,Size=5,Depth=0;
ARCamera设置Clear Flags=Don't Clear,CullingMask=Everything,Project=Perspective,Depth=1。
ARCamera受陀螺仪控制旋转,添加脚本GyroManager。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GyroManager : MonoBehaviour
{
private Quaternion rotFix;
void Start()
{
Transform currentParent = transform.parent;
GameObject camParent = new GameObject("GyroCamParent");
camParent.transform.position = transform.position;
transform.parent = camParent.transform;
GameObject camGrandparent = new GameObject("GyroCamGrandParent");
camGrandparent.transform.position = transform.position;
camParent.transform.parent = camGrandparent.transform;
camGrandparent.transform.parent = currentParent;
if (SystemInfo.supportsGyroscope) //如果系统支持陀螺仪
{
Input.gyro.enabled = true;
switch (Screen.orientation)
{
case ScreenOrientation.LandscapeLeft:
camParent.transform.eulerAngles = new Vector3(90, 90, 0);
rotFix = new Quaternion(0, 0, 0.7071f, 0.7071f);
break;
case ScreenOrientation.Portrait:
camParent.transform.eulerAngles = new Vector3(90, 180, 0);
rotFix = new Quaternion(0, 0, 1, 0);
break;
case ScreenOrientation.PortraitUpsideDown:
camParent.transform.eulerAngles = new Vector3(90, 180, 0);
rotFix = new Quaternion(0, 0, 1, 0);
break;
case ScreenOrientation.LandscapeRight:
camParent.transform.eulerAngles = new Vector3(90, 180, 0);
rotFix = new Quaternion(0, 0, 1, 0);
break;
default:
camParent.transform.eulerAngles = new Vector3(90, 180, 0);
rotFix = new Quaternion(0, 0, 1, 0);
break;
}
}
}
void Update()
{
if (SystemInfo.supportsGyroscope)
{
Quaternion quatMap = Input.gyro.attitude;
transform.localRotation = Quaternion.Slerp(transform.localRotation, quatMap * rotFix, 0.05f);
}
}
}
2. Canvas设置
创建一个RawImage,用来播放WebcamTexture。
添加Aspect Ratio Fitter组件,Height Control Width,使视频铺满画布。
再添加播放视频的脚本WebCamManager。
using UnityEngine;
using UnityEngine.UI;
public class WebCamManager : MonoBehaviour
{
private WebCamTexture webcamTexture;
private RawImage rawImage;
void Start()
{
webcamTexture = new WebCamTexture();
webcamTexture.deviceName = WebCamTexture.devices[0].name;
rawImage.texture = webcamTexture;
//Debug.Log(webcamTexture.width + "*" + webcamTexture.height); //16*16
if (!webcamTexture.isPlaying)
webcamTexture.Play();
}
private void Update()
{
float cwNeeded = -webcamTexture.videoRotationAngle;
if (webcamTexture.videoVerticallyMirrored)
{
cwNeeded += 180;
}
rawImage.rectTransform.localEulerAngles = new Vector3(0, 0, cwNeeded);
float ratio = (float)webcamTexture.width / (float)webcamTexture.height;
}
}
3. 标志物设置
这里特地用三维软件制作了一个模型,作为标志物。
将模型到场景中作为ARCamera子物体,位置移到(0, 0, 5),相机裁剪平面有足够距离。这里注意!由于前面已经给ARCamera添加了由陀螺仪控制旋转的脚本,标志物作为子物体,被动受到多个方向的旋转控制,千万不能再使用Euler控制自身的旋转。将标志物作为ARCamera子物体,是为了让它永远能位于手机屏幕中央。再在代码中通过
transform.localRotation = Quaternion.Inverse(Camera.main.transform.rotation);
消除父物体旋转的影响。然后还加入了缩放,产生对目标位置近大远小的感觉。当手机屏幕朝下时(即摄像头朝天),自然看不到地面了,故隐藏标志物体。通过手机屏幕重力分量来判断即可。
using UnityEngine;
public class demo : MonoBehaviour
{
private float temp;
private MeshRenderer render;
void Awake()
{
render = GetComponent<MeshRenderer>();
}
void Update()
{
transform.localRotation = Quaternion.Inverse(Camera.main.transform.rotation);
temp = Mathf.Abs(Mathf.Tan(Camera.main.transform.eulerAngles.x * Mathf.PI / 360));
transform.localScale = new Vector3(1f, 1f, 1f) * temp;
//屏幕朝下的分量>0,就隐藏
if (SystemInfo.supportsGyroscope)
{
render.enabled = Input.gyro.gravity.z < 0;
}
}
}
最后,无论手机处于什么姿态,都能通过标志物找到地面,并且让其他三位物体以它为基准在AR中创建出来。