公司hololens2刚刚到手 ,开始研究MRTK,写些自己理解的笔记。刚刚开始看没几天 ,会持续更新一段时间。
环境搭建:
先贴两个链接:
https://blog.csdn.net/qq_35649669/article/details/101947533
https://blog.csdn.net/qq_41905133/article/details/88983431
这两个大佬的步骤和遇到问题的解决方案都很详细了。
说说我的情况,最新的win10sdk(10.0.18362.1),vs2019,unity2018.4.7,MRTK2.3.0工具包。先装Win10SDK并指定较短路径,是为了解决Unity打包报错的问题。再装vs2019,我觉得在这步还是要装一遍win10sdk用于vs打包,至于如果碰到找不到SKD和找不到地址的报错的话按上面第二个链接处理就可以。
Unity项目设置:
https://docs.microsoft.com/zh-cn/windows/mixed-reality/mrlearning-base-ch1
微软官方教程配试第一个Hololens2项目,说的很清楚并且有解释每一步操作的原因。
自定义空间扫描功能:
https://docs.microsoft.com/zh-cn/windows/mixed-reality/mrlearning-base-ch2
微软官方教程。大概说清了这个新的工具包的使用方法。想要自定义和修改一些功能大都要通过修改这个配置文件得以实现。
之后我就没有看微软的这个官方文档了,这个文档之后的内容就不是我想要的了,所以我转向了另一篇微软的文档。
之后的内容大部分是从这篇文档里总结出来的了。
开启&隐藏诊断系统
引用头文件Microsoft.MixedReality.Toolkit;
CoreServices.DiagnosticsSystem.ShowDiagnostics = false;
如果你按照上述步骤完成了第一个hololens应用的话,就会在HoloLens中看到一个显示帧率的UI,这个就是诊断系统了,建议在开发过程中可以开着,以便了解项目中哪些地方是比较消耗性能需要优化的。上面这行代码就是隐藏诊断系统的。核心服务层(CoreServices)为全局脚本,所以这句话不需要写在特定的脚本以及挂载在特定的物体上,只需要确保会运行就可以实现功能了。
语音输入
方法一:
场景中找到MixedRealityToolkit物体->MixedRealityToolk脚本 clone配置文件。
input选项卡 clone配置文件。
Input Actions 选项卡 clone配置文件。在这里可以添加自定义事件,以便于映射到后续的输入。点击+add New action,向下滑动会看到action 16,修改名字为Ratote(这个名字随便取),Axis选为Digital,我把这个理解为bool。
speech 选项卡 clone配置文件。在这里就可以添加关键词,并且与上一步定义的事件关连了。点击添加一个新的语音命令,Keyword填写旋转(添加想要识别的关键词),action选择上一步定义的Ratote(这里你有可能找不到刚刚定义的事件,我猜测大概是系统没有编译的原因,新建一个脚本让unity编译一下,或者重启Unity就好了)。
接下来要实现Ratote事件。创建一个Cube添加InputActionHandler脚本。InputActionHandler->Action选择Ratota。这时你就可以用Unity自带的事件系统绑定一个事件了。编写脚本如下,挂载与Cube上。在InputActionHandler->OnInputActionEnded点击“+”,填入cube,并选择事件为rotate函数。
如果你电脑有话筒的话就可以测试了。(如果没效果的话,先往下看一下方法二后的备注)
方法二:
编写如下脚本,并挂载与cube上。
这个方法是继承并实现IMixedRealitySpeechHandler接口。再将关键词“变色”添加再MixedRealityToolk->input->speech选项卡中就可以了。
备注:这两个方法有一个相同的条件,就是在Cube被注视时,才会在识别到关键词时触发相应事件。在编辑器中可使用“wasd”控制主摄像机移动,来测试是否注视cube。
视线追踪
开启视线追踪功能。MixedRealityToolkit物体->MixedRealityToolk->input->input data providers->windows mixed reality eye gaze provider->Type选为windows.mixedreality.toolkit.windowsmixedreality.input->WindowsMixedMealityEyeGazeDataProvider 碰到灰色不能选的clone配置文件就好了。
勾选MixedRealityToolkit物体->MixedRealityToolk->input->Input Simulation Service->Simulate eye position。这两步是在开启视线追踪,具体什么作用我也不太清楚。
之后就可以在任意脚本中编写CoreServices.InputSystem.EyeGazeProvider.HitPosition,这句代码是获取视线看到物体上的坐标。(也可以使用CoreServices.InputSystem.GazeProvider.HitPosition 这句话是启用注视提供者,支持视线追踪的情况下使用视线追踪,不支持时用头部注视代替)
编辑如下脚本,并挂载与有碰撞体的物体上,这个脚本继承BaseEyeFocushandler后,并可重写函数OnEyeFocusStart,这个函数在开始注视本物体时调用。相关函数如下图二。
这样就可以打包vs项目了,因为unity暂时不支持开启视线追踪功能,所以我们要在vs中开启。打开vs项目,右侧解决方案管理器点开“项目名+(Universal windows)”下拉选项,双击Package.appxmanifest。在左侧打开的视图中找到功能选项卡,勾选注视输入选项就可以了。(友情提示vs最大化窗口)
事件绑定
上边语音输入中提到在InputAction选项卡中声明的事件,同样也可以绑定到控制输入。控制输入是一个统称,包括hololens的手势、PC的键鼠已经一下手柄控制器。绑定方法选取MixedRealityToolkit->Input->controllers->ControllerDefinitionscon 下点击控制器种类的按钮就会弹出绑定的窗口了。至于如何实现,就和语音输入一样了,给物体添加InputActionHandler脚本,然后使用这个脚本上的unity自带的事件系统添加就可以了(可以把事件绑定在手势控制进行测试,ControllerDefinitions默认就好,给一物体添加InputActionHandler脚本,实现Select函数就可以测试了)。InputActionHandler脚本有一个IsFocusRequired属性,我的测试结果是:勾选这个bool属性表示在有指针注视该物体的情况下才会触发事件。如果要使指针与物体发生交互,物体首先需要有碰撞体。
虚拟物体交互(指针)
指针是MRTK工具包新引入的概念,系统会根据不同的情况创建相应的指针用于完成交互。
创建一个测试物体添加下列组件:
首先物体需要有碰撞体。
抓取近处物体使用SpheregaizaiPointer指针,需要给物体添加NearInteractionGrabbable脚本。无需设置
抓取远处物体使用ShellHandRayPointer指针。
触摸物体使用PokePointer指针。触摸比较繁琐一些,分为三种情况:1.任意形状触摸(例如球体)添加NearInteractionTouchableVolume,保持默认。2.Unity UI添加NearInteractionTouchableUnityUI,注意UI也是需要有碰撞体才可以触摸的。3.单面触摸(最好使用cube测试),添加NearInteractionTouchable脚本,添加后会看到脚本面板有两个按钮和警告提示,分别是FixBounds和FixCenter,分别点击后,在编辑器窗口就可以看到一个白色的线框和箭头,这就是可以触摸的面了。也可以调整脚本上的属性自定调节。
编写如下脚本并挂载:
这个脚本是继承IMixedRealityPointerHandler接口,并实现4个接口成员,它们都是什么时候被调用看名字就可以理解了。到这还没实现抓取的功能,只是测试了不同情况下指针的使用情况。
实现近处抓取的功能,只需修改上述脚本OnPointerDown成员即可
public void OnPointerDown(MixedRealityPointerEventData eventData)
{
//抓取实现方法一:在指针按下时 将本物体设为指针子物体
if (eventData.Pointer is SpherePointer)
{ //在指针按下时 将物体父节点设为指针物体
transform.parent = ((SpherePointer)(eventData.Pointer)).transform;
}
//禁用指针 SetGazePointerBehavior SetPokePointerBehavior
//PointerUtils.SetHandRayPointerBehavior(PointerBehavior.AlwaysOff);
}
public void OnPointerUp(MixedRealityPointerEventData eventData)
{ //在指针按松开时 将物体父节点设为空
if (eventData.Pointer is SpherePointer)
{
transform.parent = null;
}
}
当然也有别的方法 :修改OnPointerDragged成员
public void OnPointerDragged(MixedRealityPointerEventData eventData)
{
//抓取实现方法二:在指针拖动时 位移 坐标
if (eventData.Pointer is SpherePointer)
{
//指针结果 内涵指针坐标已经法线
var result = eventData.Pointer.Result;
transform.position = Vector3.Lerp(transform.position, ((SpherePointer)(eventData.Pointer)).transform.position, 5*Time.deltaTime);
}
}
具体的怎么实现都可以,这些都时我自己实现的,最终效果不是那么好,大家也可以看看官方案例是怎么实现。至于抓取远处物体的代码就不贴出来了,写的太乱,如果你先看看,私信我就好。
关节追踪
编写如下脚本即可,想要指定关节的话 右击TrackedHandJoint转定义就好 里面下的很详细
至于官方给的关节识别接口,多次测试不起作用,我也还没弄清是什么问题,如果之后找到原因了会再更新一下。
空间锚(worldanchor)
以下关于锚点的结论都是我根据测试得来的经验总结,姑且只能当个参考。
首先使用空间锚得再导入三个unitypackage,下载地址在下面这个网页的中间部分,这个链接是官方空间锚教程。
https://docs.microsoft.com/zh-cn/windows/mixed-reality/mrlearning-asa-ch1
worldanchor大致有三个用途:
- 1.稳定虚拟物体在空间中的位置,这个没什么多说的,就是给物体添加worldanchor就起作用。只有一点要说的就是当物体添加了worldanchor后就不可以移动了,所以想要给可以抓取的对象添加worldanchor时的一般操作就是在抓取开始(OnPointerDown)时删除物体锚,在松手(OnPointerUp)时添加锚。详细说说接下来的两个用法。
- 2.用于存储空间位置,使物体的空间位置可以保持在多次程序运行中。也就时说这次运行程序我把一个物体移动到了房间中的桌子上,关闭程序。再次打开程序时,该物体的位置还在桌子上。实现这个功能用两种方法:
方法一:使用一个WorldAnchorStore的类保存空间锚和一个自定义的该空间锚ID,每次开启程序的时在WorldAnchorStore中按ID查找空间锚再添加给物体就可以了。需要编写两个脚本:WorldAnchorManager赋予场景空物体和WorldAnchorObject赋予需要添加空间锚的物体。
WorldAnchorManager脚本:
声明脚本单例、空间锚仓库、空间锚仓库加载完成回调。需要引入头文件 UnityEngine.XR.WSA和UnityEngine.XR.WSA.Persistence;
在Awake中填充单例,创建回调委托、注册当空间锚仓库加载完成时的回调:
实现空间锚仓库的几个功能:保存空间锚、读取空间锚、删除空间锚。因为不可以添加ID相同的空间锚,在读取和删除时,我们也要先确保空间锚仓库中有要操作的ID,所以还有要一个检查ID是否存在的功能。
检测ID是否存在:
保存空间锚:
读取空间锚:如果找到了会把找到的空间锚绑定给传入参数go
删除空间锚:
WorldAnchorObject脚本:基本思路就是,当物体没有被抓取并且自身没有WorldAnchor时,用自己的名字当作ID值尝试从空间锚仓库中读取空间锚。当物体被抓取时并且自己身上有WorldAnchor,就删除。同时用自己的名字当作ID值删除空间锚仓库中储存的对应空间锚信息。当物体被放置时给物体添加WorldAnchor,同时用自己的名字当作ID值把该WorldAnchor存入空间锚仓库。
声明ID、空间锚对象、WorldAnchor状态变化 响应委托、是否在抓取中的bool变量:
在Awake中填充ID、注册WorldAnchor状态变化回调
在lateupdate中在满足条件的情况下尝试读取空间锚
实现两个函数:OnSelec和OnDeselect,在抓取时(上文有提到的OnPointerDown)调用OnSelec,放置时(上文有提到的OnPointerUp)调用OnDeselect:
根据上文说到的,添加一个可抓取的物体,然后添加WorldAnchorObject脚本,就可以在hololens中测试了。
方法二:使用微软的云服务Azure:SpatialAnchor。这个方法大致的原理就是在给物体添加WorldAnchor后,用WorldAnchor生成一个用于SpatialAnchor识别的字符串,并上传至云。将这个字符串保存到本地硬盘就可以随时读取位置了。我最后也没亲手实现,只是根据官方的一个只教了实现步骤的教程(这个教程的用处几乎可以忽略不及)复现了一下,具体原因呢等下说明,在这里先给你两个链接:
这个链接是在教云服务的用法,包括如何申请账户ID和密钥、获取本地地位点、上传至云等一系列操作。这个教程只写一个脚本,学习成本也不高 所以非常值得一看。做完这个教程就可以获得用于SpatialAnchor识别的字符串(教程代码中的cloudSpatialAnchorId)字符串了。但是这个教程中只有获得cloudSpatialAnchorId,并没有教在hololens中怎么把这个码保存到本地硬盘中。
下面这个链接就是刚刚说的那个用处几乎可以忽略不及(对我这个菜鸡来说)的教程了。它虽然实现了两个很重要的功能,一个是把获得的cloudSpatialAnchorId保存到hololnes本地硬盘用于多次启动程序的位置同步。另一个就是worldanchor的第三个用途,通过网络同步cloudSpatialAnchorId值,以使得一个物体在空间中的位置信息可以同步到同处这一空间的多台设备中。但是呢这个教程只是用他自己写好的脚本教你怎么拖来拖去的实现这两个功能。对于抱着是要自己会实现的目的,来看教程的我来说,是毫无意义(当然啦 我弄明白了以后 可能也还是会用他的脚本 很方便/狗头保命)。所以我决定这块就先这么放着,对网络一点也不会的我要先去看看Photon Unity Network,然后再回来实现这个功能。(可能也看不会 就不更新了 - -)
看完这个教程,我觉得最少会使用教程中的脚本来实现把cloudSpatialAnchorId字符串存入hololens硬盘的功能,至于网络同步cloudSpatialAnchorId字符串的功能实现方法有很多,就看自己的选择了。
https://docs.microsoft.com/zh-cn/windows/mixed-reality/mrlearning-sharing(photon)-ch1
最后要说的是在上面自定义空间扫描功能中提到的第二个链接里还用很多hololnes功能的用法,所有功能都写的很清楚,非常推荐仔细阅读。