有时在识别目标丢失后我们仍希望虚拟物体能够出现在摄像机前,或者到一个特定的位置,我们能对其进行操作,这就是脱卡功能。
自带的脱卡功能应该是ExtendedTracking:允许模型在识别图丢失的时候还存在,位置不变(在丢失的时候的位置,这样也就允许了可以近距离观看模型,就算是丢是识别图也不要紧。
如果要使用ExtendedTracKing,在ImageTarget下的ImageTargetBehaviour 勾选Enable Extended Tracking
这次介绍的脱卡功能和ExtendedTracking有区别,这次的脱卡功能是,识别图丢失后,模型到摄像机前面一个固定的位置,可以对模型做一些操作,单击旋转等。
注:下文介绍的脱卡功能和ExtendedTracking 最好不要同时使用。
下文介绍的脱卡实现的原理,是在识别丢失后,将3D模型从ImageTarget的子物体变成ARCamera的子物体,在识别图识别到后再将3D模型的父物体变成ImageTarget。
这里使用的Vuforia6.2的版本,Unity2017.1.0f3。
1.设置ARCamera和ImageTarget,到能够合适的显示3D模型的位置,如果识别图是白色的,则需要在
Assets-Editor-Vuforia-ImageTargetTextures-xx(DatabaseName) 中,找到识别图,将其TextureShape改成2D即可。
2.修改脚本,将ImageTarget自身的DefaultTrackableEventHandler移除,并复制成一个新的脚本,然后将新的脚本拖到ImageTarget下替代原来的DefaultTrackableEventHandler。(我这里的名字叫My_DefaultTrackableEventHandler)
3.void OnTrackingFound() 和 void OnTrackingLost()
这两个函数就是,在识别图识别出来时调用的函数 和 在识别图丢失时调用的函数。
有好几中方法去实现脱卡功能,可以在识别出目标时销毁之前的内容,重新加载,然后再目标丢失后用另一个值渲染一个层的摄像机去渲染脱卡之后的模型;也可以记录位置,然后进行移动。这里用后一种方法。
最终实现:脱卡后模型移动到一个位置并朝着用户,移动到特定位置后,单击模型会将模型散开,再次单击模型会拼起来。
核心函数:OnTrackingLost() 在识别图丢失后的执行函数(执行一次)
OnTrackingFound() 在识别图找到后执行的函数(执行一次)
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
(射线检测函数,需要碰撞)
由于需要获得模型的初始和结束位置,以及要判断是否是第一次识别出来买模型,需要添加以下代码:
#region PUBLIC_MEMBER_VARIABLES
public GameObject Robot; //机器人
public GameObject AimObject; // 脱卡之后的模型位置的空物体
#endregion
#region PRIVATE_MEMBER_VARIABLES
private TrackableBehaviour mTrackableBehaviour;
private bool hasFirstLoad = false; //是否是第一次识别到物体
#endregion // PRIVATE_MEMBER_VARIABLES
void Update()
{
//Update中执行一些逻辑
}
由于在启动以后OnTrackingLost()会调用(由于一开始没有找到识别图),而且这时是没有模型的,所以在OnTrackingLost()修改成如下代码:
private void OnTrackingLost()
{
if (!hasFirstLoad)
{
Renderer[] rendererComponents = GetComponentsInChildren<Renderer>(true);
Collider[] colliderComponents = GetComponentsInChildren<Collider>(true);
// Disable rendering:
foreach (Renderer component in rendererComponents)
{
component.enabled = false;
}
// Disable colliders:
foreach (Collider component in colliderComponents)
{
component.enabled = false;
}
Debug.Log("Trackable " + mTrackableBehaviour.TrackableName + " lost");
}
else
{
//在第一次渲染出图形以后的操作
AimPos = AimObject.transform.position; //脱卡后的位置
robotTransform.SetParent(AimObject.transform); //设置父物体AimObject
robotTransform.localEulerAngles = new Vector3(0, 180, 0); //模型旋转
}
}
而在找到识别图后极为第一次识别到了,将OnTrackingFound()修改成下面代码:
在这里要将Robot的父物体设为ImageTarget,然后调整至合适位置
private void OnTrackingFound()
{
Renderer[] rendererComponents = GetComponentsInChildren<Renderer>(true);
Collider[] colliderComponents = GetComponentsInChildren<Collider>(true);
//将Robot的父物体设为ImgTarget 并调整
robotTransform.SetParent(transform);
robotTransform.localEulerAngles = new Vector3(0, 180, 0);
robotTransform.transform.position = this.transform.position;
// Enable rendering:
foreach (Renderer component in rendererComponents)
{
component.enabled = true;
}
// Enable colliders:
foreach (Collider component in colliderComponents)
{
component.enabled = true;
}
Debug.Log("Trackable " + mTrackableBehaviour.TrackableName + " found");
hasFirstLoad = true; //第一次找到了 渲染出来
}
射线选择函数:
if (Physics.Raycast(ray, out _hit, 10f))
{
if (_hit.collider.gameObject.name == Robot.name)
{
//操作
}
}
其他还有一些细节,比如机器人的子物体的移动,怎么移动,细节优化等等,都在代码里。
完整的修改的代码如下:
初始变量的定义:
#region PUBLIC_MEMBER_VARIABLES
public GameObject Robot; //机器人
public GameObject AimObject; // 脱卡之后的模型位置的空物体
public bool canMoveTo = false; //是否脱卡
public bool canChange = false; //是否可以散开
public bool hasSetPos = false; //是否设置了部件该去的位置
#endregion
#region PRIVATE_MEMBER_VARIABLES
private TrackableBehaviour mTrackableBehaviour;
private bool hasFirstLoad = false; //是否是第一次识别到物体
private Vector3 AimPos; //脱卡之后的模型位置
private Transform robotTransform; //机器人Transform的引用
private float timer = 0f; //timer递减看机器人子物体的移动次数
private int childCount; //机器人的子物体个数
private Vector3 centerPos; //要移动到的位置
private Transform[] childTransform; //机器人子物体的Transform
private Vector3[] moveTo; //移动的方向(没有单位化)
private Vector3[] localPos; //机器人子物体的本地坐标,为了在分散开后归位时正常位置
#endregion // PRIVATE_MEMBER_VARIABLES
void Start()
{
//原始代码保留
//添加的代码
//初始化一堆变量
robotTransform = Robot.transform;
childCount = robotTransform.childCount;
childTransform = new Transform[childCount];
moveTo = new Vector3[childCount];
localPos = new Vector3[childCount];
for (int i = 0; i < childCount; i++)
{
childTransform[i] = robotTransform.GetChild(i);
localPos[i] = childTransform[i].localPosition;
//记录本地坐标位置,以便还原
}
}
自定的Update函数
void Update()
{
if (canMoveTo) //如果可以移动了 也就是脱卡后
{
robotTransform.position = Vector3.MoveTowards(robotTransform.position, AimPos, 1.0f); //移动到相应位置
if (Vector3.Distance(robotTransform.position, AimPos) < 0.01f) //基本以已经到位置了 就可以单击散开
{
if (!hasSetPos)
{
//获得机器人的子物体引用 并设置方向
centerPos = robotTransform.position;
for (int i = 0; i < childCount; i++)
{
moveTo[i] = centerPos - childTransform[i].position; //获得机器人的每个部件该去的位置的反方向(由于后面有个单击一次就反向一次的操作)
}
hasSetPos = true;
}
if (timer > 0f) //如果能移动
{
if (!canChange) //移动
{
for (int i = 0; i < childCount; i++)
{
childTransform[i].position += moveTo[i] * timer;
timer -= 0.08f;//循环次数
}
}
}
else
{
canChange = true; //移动到位置了 也即是timer<=0 就可以再移动
}
//单击散开或者合拢
if (Input.GetMouseButtonDown(0))//按下左键 如果手机就改成单机
{
if (timer <= 0) //如果能移动 就重置Timer=1
{
RaycastHit _hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out _hit, 10f))
{
if (_hit.collider.gameObject.name == Robot.name)
{
timer = 1f;
canChange = false;
for (int i = 0; i < childCount; i++)
{
moveTo[i] *= -1; //向相反方向移动
}
}
}
}
}
}
}
}
修改后的 OnTrackingFound() 和 OnTrackingLost();
private void OnTrackingFound()
{
Renderer[] rendererComponents = GetComponentsInChildren<Renderer>(true);
Collider[] colliderComponents = GetComponentsInChildren<Collider>(true);
//将Robot的父物体设为ImgTarget 并调整
robotTransform.SetParent(transform);
robotTransform.localEulerAngles = new Vector3(0, 180, 0);
robotTransform.transform.position = this.transform.position;
for (int i = 0; i < childCount; i++)
{
childTransform[i].localPosition = localPos[i];
}
// Enable rendering:
foreach (Renderer component in rendererComponents)
{
component.enabled = true;
}
// Enable colliders:
foreach (Collider component in colliderComponents)
{
component.enabled = true;
}
canMoveTo = false;
hasSetPos = false;
Debug.Log("Trackable " + mTrackableBehaviour.TrackableName + " found");
hasFirstLoad = true; //第一次找到了 渲染出来
}
private void OnTrackingLost()
{
if (!hasFirstLoad)
{
Renderer[] rendererComponents = GetComponentsInChildren<Renderer>(true);
Collider[] colliderComponents = GetComponentsInChildren<Collider>(true);
// Disable rendering:
foreach (Renderer component in rendererComponents)
{
component.enabled = false;
}
// Disable colliders:
foreach (Collider component in colliderComponents)
{
component.enabled = false;
}
Debug.Log("Trackable " + mTrackableBehaviour.TrackableName + " lost");
}
else
{
//在第一次渲染出图形以后的操作
canMoveTo = true;
AimPos = AimObject.transform.position; //脱卡后的位置
robotTransform.SetParent(AimObject.transform); //设置父物体为AimObject
robotTransform.localEulerAngles = new Vector3(0, 180, 0);
}
}
结果:
脱卡
单击
)
本文内容部分参考Think加速想象力出版的《AR与VR开发实战》教程。