手部跟踪 — MRTK2
手部跟踪配置文件
手部跟踪配置文件位于输入系统配置文件下。 它包含用于自定义手部表示形式的设置。
关节预制件
使用简单预制件可视化关节预制件。 手掌和食指关节尤其重要,它们具有自己的预制件,而所有其他关节共享同一个预制件。
默认情况下,手部关节预制件是简单的几何基元。 如果需要,可以替换它们。 如果没有指定任何预制件,则改为创建空的“GameObjects”。
警告
关节预制件避免使用复杂的脚本或成本高昂的呈现,因为关节对象会基于每一帧进行转换,导致性能成本显著增加!
默认手部关节表示形式 | 关节标签 |
---|---|
手部网格预制件
如果手部跟踪设备提供了完全定义的网格数据,则使用手部网格。 来自设备的数据会替换预制件中可呈现的网格,因此一个虚拟网格(如立方体)已足够。 将预制件材料用于手部网格。
手部网格显示可能会对性能产生显著影响,因此,可通过取消选中“启用手部网格可视化”选项来完全禁用它。
手部可视化设置
可以通过“手部网格可视化模式”设置和“手部关节可视化模式”分别关闭或打开手部网格和手部关节可视化。 这些设置特定于应用程序模式,这意味着在编辑器中可以打开某些功能(例如采用编辑器中的模拟来查看关节),而在部署到设备上时(在播放器生成中),则要关闭同样的功能。
请注意,通常建议在编辑器中打开手部关节可视化(以便编辑器中的模拟显示手部关节的位置),在播放器中关闭手部关节可视化和手部网格可视化(因为它们会导致性能下降)。
脚本编写
可以从输入系统为每个手部关节请求位置和旋转作为 MixedRealityPose
。
此外,系统还允许访问跟随关节的 GameObject。 如果其他 GameObject 应连续跟踪某个关节,这会非常有用。
TrackedHandJoint
枚举中列出了可用关节。
注意
手部跟踪丢失时,会销毁关节对象! 请确保任何使用关节对象的脚本都能谨慎地处理 null
情况,以免出现错误!
访问给定的手部控制器
通常会提供特定的手部控制器,例如处理输入事件时。 在这种情况下,可以使用 IMixedRealityHand
接口直接从设备请求关节数据。
从控制器轮询关节姿势
如果由于某种原因而导致请求的关节不可用,则 TryGetJoint
函数会返回 false
。 在这种情况下,生成的姿势将为 MixedRealityPose.ZeroIdentity
。
public void OnSourceDetected(SourceStateEventData eventData)
{
var hand = eventData.Controller as IMixedRealityHand;
if (hand != null)
{
if (hand.TryGetJoint(TrackedHandJoint.IndexTip, out MixedRealityPose jointPose)
{
// ...
}
}
}
从手部可视化工具转换关节
可以从控制器可视化工具请求关节对象。
public void OnSourceDetected(SourceStateEventData eventData)
{
var handVisualizer = eventData.Controller.Visualizer as IMixedRealityHandVisualizer;
if (handVisualizer != null)
{
if (handVisualizer.TryGetJointTransform(TrackedHandJoint.IndexTip, out Transform jointTransform)
{
// ...
}
}
}
简化关节数据访问流程
如果未提供特定的控制器,则会提供实用工具类以方便访问手部关节数据。 这些函数会从当前跟踪的第一个可用的手部设备请求关节数据。
从 HandJointUtils 轮询关节姿势
HandJointUtils
是一个静态类,用于查询第一个活动状态的手部设备。
if (HandJointUtils.TryGetJointPose(TrackedHandJoint.IndexTip, Handedness.Right, out MixedRealityPose pose))
{
// ...
}
从手部关节服务转换关节
IMixedRealityHandJointService
保留一个永久的 GameObjects 集,用于跟踪关节。
var handJointService = CoreServices.GetInputSystemDataProvider<IMixedRealityHandJointService>();
if (handJointService != null)
{
Transform jointTransform = handJointService.RequestJointTransform(TrackedHandJoint.IndexTip, Handedness.Right);
// ...
}
手部跟踪事件
如果不想直接从控制器轮询数据,输入系统也可提供事件。
关节事件
IMixedRealityHandJointHandler
可处理关节位置的更新。
public class MyHandJointEventHandler : IMixedRealityHandJointHandler
{
public Handedness myHandedness;
void IMixedRealityHandJointHandler.OnHandJointsUpdated(InputEventData<IDictionary<TrackedHandJoint, MixedRealityPose>> eventData)
{
if (eventData.Handedness == myHandedness)
{
if (eventData.InputData.TryGetValue(TrackedHandJoint.IndexTip, out MixedRealityPose pose))
{
// ...
}
}
}
}
网格事件
IMixedRealityHandMeshHandler
可处理铰接式手部网格的更改。
请注意,手部网格默认为不启用。
public class MyHandMeshEventHandler : IMixedRealityHandMeshHandler
{
public Handedness myHandedness;
public Mesh myMesh;
public void OnHandMeshUpdated(InputEventData<HandMeshInfo> eventData)
{
if (eventData.Handedness == myHandedness)
{
myMesh.vertices = eventData.InputData.vertices;
myMesh.normals = eventData.InputData.normals;
myMesh.triangles = eventData.InputData.triangles;
if (eventData.InputData.uvs != null && eventData.InputData.uvs.Length > 0)
{
myMesh.uv = eventData.InputData.uvs;
}
// ...
}
}
}
已知问题
.NET Native
目前,使用 .NET 后端的主生成存在一个已知问题。 在 .NET Native 中,无法使用 Marshal.GetObjectForIUnknown
将 IInspectable
指针从本机封送处理到托管代码。 MRTK 使用它来获取 SpatialCoordinateSystem
,以便从平台接收手部和眼部数据。
在本机混合现实工具包存储库中,我们提供了 DLL 源作为此问题的解决方法。 请按照该处自述文件中的说明进行操作,将生成的二进制文件复制到 Unity 资产的 Plugins 文件夹中。 之后,MRTK 中提供的 WindowsMixedRealityUtilities 脚本将为你解决此问题。
如果要创建自己的 DLL 或将此解决方法纳入现有 DLL,解决方法的核心是:
extern "C" __declspec(dllexport) void __stdcall MarshalIInspectable(IUnknown* nativePtr, IUnknown** inspectable)
{
*inspectable = nativePtr;
}
其在 C# Unity 代码中的使用:
[DllImport("DotNetNativeWorkaround.dll", EntryPoint = "MarshalIInspectable")]
private static extern void GetSpatialCoordinateSystem(IntPtr nativePtr, out SpatialCoordinateSystem coordinateSystem);
private static SpatialCoordinateSystem GetSpatialCoordinateSystem(IntPtr nativePtr)
{
try
{
GetSpatialCoordinateSystem(nativePtr, out SpatialCoordinateSystem coordinateSystem);
return coordinateSystem;
}
catch
{
UnityEngine.Debug.LogError("Call to the DotNetNativeWorkaround plug-in failed. The plug-in is required for correct behavior when using .NET Native compilation");
return Marshal.GetObjectForIUnknown(nativePtr) as SpatialCoordinateSystem;
}
}