手部跟踪 - 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.GetObjectForIUnknownIInspectable 指针从本机封送处理到托管代码。 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;
    }
}