QRCodeSever开发
- 参考代码以及博客:
- https://localjoost.github.io/Reading-QR-codes-with-an-MRTK2-Extension-Service/
- https://github.com/LocalJoost/QRCodeService
- https://github.com/LocalJoost/QRCodeService/tree/openxr
一、下载源代码
https://github.com/LocalJoost/QRCodeService/tree/openxr
- 安装指定版本的
Unity
二、项目场景的层次结构
-
HandMenu
是一个Reset
按钮,用于重置二维码追踪
-
QRCodeDisplayer
是二维码追踪的显示面板
-
Trackers
是识别到二维码后的标记
-
JetHolder
是识别到二维码用于显示的模型
三、组件挂载脚本情况
Tracker1
挂载QRTrackerController
脚本TrackerHolder
挂载OpenXRSpatialGraphCoordinateSystemSetter
,改脚本中实现的类继承自SpatialGraphCoordinateSystemSetter
脚本中实现的类
四、二维码识别显示物体流程
QRCodeTrackerCotroller.cs
- 实例化
IQRCodeTrackingService
对象,以下代码段仅显示了初始化代码
using System;
using Microsoft.MixedReality.Toolkit;
using UnityEngine;
namespace MRTKExtensions.QRCodes
{
public class QRTrackerController : MonoBehaviour
{
// 获取到SpatialGraphCoordinateSystemSetter 的引用
[SerializeField]
private SpatialGraphCoordinateSystemSetter spatialGraphCoordinateSystemSetter;
// 检查二维码的值是否合法,可通过Unity界面手动输入值
// 就是用来判断摄像机识别到的二维码得到的字符串信息与这个值是否一致,后面会有方法注册在识别到二维码这个事件上
[SerializeField]
private string locationQrValue = string.Empty;
private Transform markerHolder;
private AudioSource audioSource;
private GameObject markerDisplay;
private QRInfo lastMessage;
public bool IsTrackingActive { get; private set; } = true;
private IQRCodeTrackingService qrCodeTrackingService;
// 一旦MixedRealityToolkit.IsInitialized初始化成功就实例化IQRCodeTrackingService对象,即QRCodeTrackingService,
// IQRCodeTrackingService 对象就是定义了一些事件,其中就包括我们关心的二维码被检测到这个事件
private IQRCodeTrackingService QRCodeTrackingService
{
get
{
while (!MixedRealityToolkit.IsInitialized && Time.time < 5) ;
return qrCodeTrackingService ??
(qrCodeTrackingService = MixedRealityToolkit.Instance.GetService<IQRCodeTrackingService>());
}
}
}
}
- 开启二维码追踪,以下代码段仅显示了非初始化代码
using System;
using Microsoft.MixedReality.Toolkit;
using UnityEngine;
namespace MRTKExtensions.QRCodes
{
private void Start()
{
// 如果不支持追踪,直接返回
if (!QRCodeTrackingService.IsSupported)
{
return;
}
// 追踪标志,也就是挂载OpenXRSpatialGraphCoordinateSystemSetter脚本的物体
markerHolder = spatialGraphCoordinateSystemSetter.gameObject.transform;
// 追踪标志的显示实体
markerDisplay = markerHolder.GetChild(0).gameObject;
// 默认不显示实体
markerDisplay.SetActive(false);
audioSource = markerHolder.gameObject.GetComponent<AudioSource>();
// 将ProcessTrackingFound方法注册到QRCodeFound(二维码被识别到)事件上
// 即一旦触发QRCodeFound事件,就执行该方法
QRCodeTrackingService.QRCodeFound += ProcessTrackingFound;
// 将SetPosition方法注册到PositionAcquired(成功获取二维码位置)事件上
// 即一旦触发PositionAcquired事件,就执行该方法
spatialGraphCoordinateSystemSetter.PositionAcquired += SetPosition;
// 将ResetTracking方法注册到PositionAcquisitionFailed(未成功获取二维码位置)事件上
// 即一旦触发PositionAcquisitionFailed事件,就执行该方法
spatialGraphCoordinateSystemSetter.PositionAcquisitionFailed +=
(s, e) => ResetTracking();
// 只要IQRCodeTrackingService对象实例化成功,也就是QRCodeTrackingService.IsInitialized为true就开启追踪
if (QRCodeTrackingService.IsInitialized)
{
StartTracking();
}
else
{
QRCodeTrackingService.Initialized += QRCodeTrackingService_Initialized;
}
}
private void QRCodeTrackingService_Initialized(object sender, EventArgs e)
{
StartTracking();
}
// 开启二维码追踪
private void StartTracking()
{
QRCodeTrackingService.Enable();
}
// 重置二维码追踪
public void ResetTracking()
{
if (QRCodeTrackingService.IsInitialized)
{
markerDisplay.SetActive(false);
IsTrackingActive = true;
}
}
// 注册在QRCodeFound事件上的方法,判断识别到的二维码值是否是目标值
private void ProcessTrackingFound(object sender, QRInfo msg)
{
if (msg == null || !IsTrackingActive )
{
return;
}
lastMessage = msg;
if (msg.Data == locationQrValue && Math.Abs((DateTimeOffset.UtcNow - msg.LastDetectedTime.UtcDateTime).TotalMilliseconds) < 200)
{
// 如果是目标值,就调用SetLocationIdSize方法,将msg注册到队列中(该队列在SpatialGraphCoordinateSystemSetter中实现)
spatialGraphCoordinateSystemSetter.SetLocationIdSize(msg.SpatialGraphNodeId,
msg.PhysicalSideLength);
}
}
// PositionAcquired事件一旦触发,该函数将被调用,将会触发PositionSet事件
private void SetPosition(object sender, Pose pose)
{
IsTrackingActive = false;
markerHolder.localScale = Vector3.one * lastMessage.PhysicalSideLength;
markerDisplay.SetActive(true);
PositionSet?.Invoke(this, pose);
audioSource.Play();
}
public EventHandler<Pose> PositionSet;
}
}
IQRCodeTrackingService.cs
脚本内容
using System;
using Microsoft.MixedReality.Toolkit;
namespace MRTKExtensions.QRCodes
{
public interface IQRCodeTrackingService : IMixedRealityExtensionService
{
event EventHandler Initialized;
event EventHandler<string> ProgressMessageSent;
// 二维码被检测到的事件
event EventHandler<QRInfo> QRCodeFound;
bool InitializationFailed { get;}
string ErrorMessage { get; }
string ProgressMessages { get; }
bool IsSupported { get; }
bool IsTracking { get; }
bool IsInitialized { get; }
}
}
SpatialGraphCoordinateSystemSetter.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.MixedReality.Toolkit.Utilities;
using UnityEngine;
#if WINDOWS_UWP
#endif
namespace MRTKExtensions.QRCodes
{
public abstract class SpatialGraphCoordinateSystemSetter : MonoBehaviour
{
// 定义两个事件,分别是二维码位置确定以及二维码位置确定失败
public EventHandler<Pose> PositionAcquired;
public EventHandler PositionAcquisitionFailed;
// 二维码定位队列,一旦识别到二维码,就会将二维码id以及标记尺寸添加到这个队列中
private Queue<Tuple<Guid, float>> locationIdSizes = new Queue<Tuple<Guid, float>>();
// 该方法就是将二维码id以及标记尺寸添加到队列中
// 该方法的调用是在QRCodeTrackerCotroller.cs脚本中的ProcessTrackingFound方法
public void SetLocationIdSize(Guid spatialGraphNodeId, float physicalSideLength)
{
locationIdSizes.Enqueue(new Tuple<Guid, float>(spatialGraphNodeId, physicalSideLength));
}
// 每一帧都在调用Update方法
void Update()
{
// 只要队列不空,就取出元素,并调用UpdateLocation方法
if (locationIdSizes.Any())
{
var locationIdSize = locationIdSizes.Dequeue();
// 该方法是在SpatialGraphCoordinateSystemSetter的派生类3. OpenXRSpatialGraphCoordinateSystemSetter中实现的
UpdateLocation(locationIdSize.Item1, locationIdSize.Item2);
}
}
// 该方法是在SpatialGraphCoordinateSystemSetter的派生类OpenXRSpatialGraphCoordinateSystemSetter中实现的
protected abstract void UpdateLocation(Guid spatialGraphNodeId, float physicalSideLength);
protected void CalculatePosition(System.Numerics.Matrix4x4? relativePose, float physicalSideLength)
{
if (relativePose == null)
{
PositionAcquisitionFailed?.Invoke(this, null);
return;
}
System.Numerics.Matrix4x4 newMatrix = relativePose.Value;
// Platform coordinates are all right handed and unity uses left handed matrices. so we convert the matrix
// from rhs-rhs to lhs-lhs
// Convert from right to left coordinate system
newMatrix.M13 = -newMatrix.M13;
newMatrix.M23 = -newMatrix.M23;
newMatrix.M43 = -newMatrix.M43;
newMatrix.M31 = -newMatrix.M31;
newMatrix.M32 = -newMatrix.M32;
newMatrix.M34 = -newMatrix.M34;
System.Numerics.Vector3 scale;
System.Numerics.Quaternion rotation1;
System.Numerics.Vector3 translation1;
System.Numerics.Matrix4x4.Decompose(newMatrix, out scale, out rotation1, out translation1);
var translation = new Vector3(translation1.X, translation1.Y, translation1.Z);
var rotation = new Quaternion(rotation1.X, rotation1.Y, rotation1.Z, rotation1.W);
var pose = new Pose(translation, rotation);
// If there is a parent to the camera that means we are using teleport and we should not apply the teleport
// to these objects so apply the inverse
if (CameraCache.Main.transform.parent != null)
{
pose = pose.GetTransformedBy(CameraCache.Main.transform.parent);
}
MovePoseToCenter(pose, physicalSideLength);
}
protected void MovePoseToCenter(Pose pose, float physicalSideLength)
{
// Rotate 90 degrees 'forward' over 'right' so 'up' is pointing straight up from the QR code
pose.rotation *= Quaternion.Euler(90, 0, 0);
// Move the anchor point to the *center* of the QR code
// To move an object in its own reference frame, simply add (or subtract) the rotation times a world vector
var deltaToCenter = physicalSideLength * 0.5f;
pose.position += (pose.rotation * (deltaToCenter * Vector3.right) -
pose.rotation * (deltaToCenter * Vector3.forward));
CheckPosition(pose);
}
protected void MovePoseToGiven(Pose pose, float physicalSideLength, Pose givenPose)
{
// pose是相对于Unity原点的 QR码左上角的坐标 旋转信息使物体指向纸面外
// 如果给的是需要渲染的物体相对二维码中心的相对坐标,计算会更方便,因此relaPose是相对中心的坐标
// Rotate 90 degrees 'forward' over 'right' so 'up' is pointing straight up from the QR code
pose.rotation *= Quaternion.Euler(90, 0, 0);
// To move an object in its own reference frame, simply add (or subtract) the rotation times a world vector
var deltaToCenter = physicalSideLength * 0.5f;
pose.position += (pose.rotation * (deltaToCenter * Vector3.right) - //沿着right的方向平移半个二维码的大小
pose.rotation * (deltaToCenter * Vector3.forward)); //沿着forword的负方向平移半个二维码的大小
// 物体的渲染位置从二维码的中心旋转、平移到世界坐标的指定位置
Vector3 objectPosToMove = givenPose.position;
Quaternion objectRotToMove = givenPose.rotation;
// Pose relaPose = GetRelaPose(pose, givenPose);
//pose.rotation *= relaPose.rotation;
pose.rotation *= objectRotToMove;
pose.position += pose.rotation * (objectPosToMove.x * Vector3.right) +
pose.rotation * (objectPosToMove.y * Vector3.up) +
pose.rotation * (objectPosToMove.z * Vector3.forward);
// 移动到指定位姿后Check当前pose(位姿)是否到位
CheckPosition(pose);
}
protected Pose GetRelaPose(Pose nowPose, Pose givenPose)
{
Pose relaPose;
// 求当前位置和目标位置之间的rotation
Vector3 nowVector = nowPose.rotation.eulerAngles;
Vector3 givenVector = givenPose.rotation.eulerAngles;
relaPose.rotation = Quaternion.Euler(givenVector - nowVector);
relaPose.position = givenPose.position - nowPose.position;
return relaPose;
}
private Pose? lastPose;
// 检查位姿是否到位
private void CheckPosition(Pose pose)
{
if (lastPose == null)
{
lastPose = pose;
return;
}
// 如果当前位姿与期望位姿差距很小,说明已经到达期望位姿
if (Mathf.Abs(Quaternion.Dot(lastPose.Value.rotation, pose.rotation)) > 0.99f &&
Vector3.Distance(lastPose.Value.position, pose.position) < 0.5f)
{
locationIdSizes.Clear();
lastPose = null;
// 将挂载当前脚本的物体的位姿设置成pose
gameObject.transform.SetPositionAndRotation(pose.position, pose.rotation);
// 同时触发二维码位置确定的事件
// 1. `QRCodeTrackerCotroller.cs`中定义了PositionAcquired事件触发的绑定函数SetPosition方法
// 一旦触发PositionAcquired事件,将调用SetPosition方法
PositionAcquired?.Invoke(this, pose);
}
else
{
lastPose = pose;
}
}
}
}
OpenXRSpatialGraphCoordinateSystemSetter.cs
using System;
using Microsoft.MixedReality.Toolkit.Utilities;
using UnityEngine;
using Microsoft.MixedReality.OpenXR;
namespace MRTKExtensions.QRCodes
{
public class OpenXRSpatialGraphCoordinateSystemSetter : SpatialGraphCoordinateSystemSetter
{
public Pose givenPose;
protected override void UpdateLocation(Guid spatialGraphNodeId, float physicalSideLength)
{
// 只要传入的spatialGraphNodeId不为空,就将其转换为SpatialGraphNode的实例化对象
var node = spatialGraphNodeId != Guid.Empty ? SpatialGraphNode.FromStaticNodeId(spatialGraphNodeId) : null;
if (node != null && node.TryLocate(FrameTime.OnUpdate, out Pose pose))
{
if (CameraCache.Main.transform.parent != null)
{
pose = pose.GetTransformedBy(CameraCache.Main.transform.parent);
}
// 给定的位置以及旋转姿态
givenPose.position = new Vector3(100, 100, 100);
givenPose.rotation = Quaternion.Euler(0, -90, -90);
// 调用该方法实现将物体移动到指定位姿
// 该方法是在2. SpatialGraphCoordinateSystemSetter.cs脚本中实现的
MovePoseToGiven(pose, physicalSideLength, givenPose);
//MovePoseToCenter(pose,physicalSideLength);
}
else
{
PositionAcquisitionFailed?.Invoke(this, null);
}
}
}
}
JetController.cs
using MRTKExtensions.QRCodes;
using UnityEngine;
public class JetController : MonoBehaviour
{
[SerializeField]
private QRTrackerController trackerController;
private void Start()
{
// 将PoseFound方法注册到PositionSet 事件上
trackerController.PositionSet += PoseFound;
}
// 触发PositionSet 事件,将会使得模型放置在指定的位姿上
private void PoseFound(object sender, Pose pose)
{
var childObj = transform.GetChild(0);
// 将模型放置在指定位姿上
childObj.SetPositionAndRotation(pose.position, pose.rotation);
childObj.gameObject.SetActive(true);
}
}
- 整体事件的触发流程
QRCodeFound
(SpatialGraphCoordinateSystemSetter::SetLocationIdSize
—>OpenXRSpatialGraphCoordinateSystemSetter::UpdateLocation
—>SpatialGraphCoordinateSystemSetter::MovePoseToGiven
—>SpatialGraphCoordinateSystemSetter::CheckPosition
) ——>PositionAcquired
(QRTrackerController::SetPosition
) ——>PositionSet
(JetController::PoseFound
)