支持眼动的目标选择 — MRTK2

MRTK

本页讨论访问眼睛凝视数据和眼睛凝视特定事件以在 MRTK 中选择目标的不同选项。 眼动追踪允许使用有关用户正在查看的内容的信息以及其他输入(例如手部追踪语音命令)的组合,从而快速轻松地选择目标:

  • 注视然后说出“选择”(默认语音命令)
  • 注视然后说出“探索”或“弹出”(自定义语音命令)
  • 注视和蓝牙按钮
  • 注视和夹取(例如,举起自己的手放在面前,并将拇指和食指并拢在一起)

要通过眼睛凝视来选择全息内容,有几个选项:

1. 使用主焦点指针:

这可以理解为优先处理的光标。 默认情况下,如果手出现在视野中,则这将是手部射线。 如果手没有出现在视野中,那么优先的指针将是头部或眼睛凝视。 因此,请注意,如果使用手部射线,则基于当前设计的头部或眼睛凝视被抑制为光标输入。

例如:

用户想要选择远处的全息按钮。 作为开发人员,你希望提供一种灵活的解决方案,允许用户在各种条件下完成此任务:

  • 走到按钮前戳一下
  • 从远处看到按钮并说“选择”
  • 使用手部射线瞄准按钮并执行捏合手势。在这种情况下,最灵活的解决方案是使用主焦点处理程序,因为它会在当前优先级的主焦点指针触发事件时通知你。 请注意,如果启用手部射线,则一旦手部进入视野,头部或眼睛注视焦点指针将被禁用。

重要

请注意,如果启用手部射线,则一旦手部进入视野,头部或眼睛注视焦点指针将被禁用。 如果你想支持“观看和捏合”交互,则需要禁用手部射线。 在我们的眼动跟踪示例场景中,我们禁用了手部射线以允许使用眼睛 + 手部动作展示更丰富的交互 - 有关示例,请参阅目视支持的定位

2. 同时使用眼部焦点和手部射线:

在某些情况下,你可能希望更具体地说明哪种类型的焦点指针可以触发某些事件并允许同时使用多种远程交互技术。

例如:在你的应用中,用户可以使用远端的手部射线来操纵一些全息机械设置 - 例如,抓取并握住一些远处的全息引擎零件并将这些零件固定到位。 这样做时,用户必须完成一系列指令,并通过标记一些复选框来记录自己的进度。 如果用户的手不是很忙,那么用户会本能地触摸复选框或使用手部射线选中复选框。 但是,如果用户的手上很忙,例如在我们的情况下是将一些全息引擎零件固定到位,则你会希望用户能够通过眼睛凝视而无缝滚动浏览说明,只需盯住复选框并说“查看!”。

要实现此功能,需要使用独立于核心 MRTK FocusHandlers 的眼部专用 EyeTrackingTarget 脚本,将在下文中进一步讨论该脚本。

1. 使用通用焦点和指针处理程序

如果眼动追踪设置正确(请参阅基本 MRTK 设置以使用眼动跟踪),使用户能够使用眼睛选择全息图像与任何其他焦点输入(例如,头部凝视或手部射线)相同。这提供了很大的优势,即通过根据用户需要在 MRTK 输入指针配置文件中定义主要焦点类型,同时保持代码不变,从而以灵活的方式与全息图像进行交互。 这样可以在不改变任何代码的情况下在头部或眼睛凝视之间切换,或者用眼睛瞄准替换手部射线以进行远距离交互。

聚焦于全息图像

要检测全息图像何时聚焦,请使用“IMixedRealityFocusHandler”接口,该接口为您提供两个接口成员:OnFocusEnter 和 OnFocusExit

这是 ColorTap.cs 中的一个简单示例,作用是在被用户凝视时更改全息图像的颜色。

public class ColorTap : MonoBehaviour, IMixedRealityFocusHandler
{
    void IMixedRealityFocusHandler.OnFocusEnter(FocusEventData eventData)
    {
        material.color = color_OnHover;
    }

    void IMixedRealityFocusHandler.OnFocusExit(FocusEventData eventData)
    {
        material.color = color_IdleState;
    }
    ...
}

选择一个聚焦的全息图像

若要选择具聚焦的全息图像,请使用 PointerHandler 侦听输入事件以确认所做的选择。 例如,添加 ImixedRealityPointerHandler 将使输入事件对简单的指针输入做出反应。 ImixedRealityPointerHandler 接口需要实现以下三个接口成员:OnPointerUp、OnPointerDown 和 OnPointerClicked

在下面的示例中,我们通过凝视全息图像并捏合或说“选择”来更改全息图像的颜色。 触发事件所需的操作是由 eventData.MixedRealityInputAction == selectAction 来定义的,我们也可以使用它在 Unity Editor 中设置 selectAction 的类型,默认为“选择”操作。 可以通过“MRTK 配置文件”->“输入”->“输入操作”在 MRTK 配置文件中配置可用 MixedRealityInputActions 的类型

public class ColorTap : MonoBehaviour, IMixedRealityFocusHandler, IMixedRealityPointerHandler
{
    // Allow for editing the type of select action in the Unity Editor.
    [SerializeField]
    private MixedRealityInputAction selectAction = MixedRealityInputAction.None;
    ...

    void IMixedRealityPointerHandler.OnPointerUp(MixedRealityPointerEventData eventData)
    {
        if (eventData.MixedRealityInputAction == selectAction)
        {
            material.color = color_OnHover;
        }
    }

    void IMixedRealityPointerHandler.OnPointerDown(MixedRealityPointerEventData eventData)
    {
        if (eventData.MixedRealityInputAction == selectAction)
        {
            material.color = color_OnSelect;
        }
    }

    void IMixedRealityPointerHandler.OnPointerClicked(MixedRealityPointerEventData eventData) { }
}

眼睛凝视专用的 BaseEyeFocusHandler

鉴于眼睛凝视可能与其他指针输入有很大区别,你可能需要确保仅当是眼睛凝视并且目前是主要输入指针时才对焦点输入做出反应。 为此,你将使用 BaseEyeFocusHandler,它特定于眼动跟踪,并且是派生自 BaseFocusHandler。 如前所述,它只会在眼睛注视目标当前是主要指针输入时触发(即没有手部射线处于活动状态)。 有关详细信息,请参阅如何支持眼睛凝视 + 手势

下面是 EyeTrackingDemo-03-Navigation 中的一个例子 (Assets/MRTK/Examples/Demos/EyeTracking/Scenes)。 在这个演示中,有两个 3D 全息图像会根据用户观察对象的哪个部分而转动:如果用户观看全息图像的左侧,那么该部分将慢慢移向面向用户的正面。 如果用户观看右侧,那么右侧将慢慢向前移。 你可能不想一直激活这种行为,还有某些你可能不想通过手部射线或头部凝视意外触发的游戏对象。 附加了 OnLookAtRotateByEyeGaze 后,如果用户观看 GameObject,则 GameObject 将旋转。

public class OnLookAtRotateByEyeGaze : BaseEyeFocusHandler
{
    ...

    protected override void OnEyeFocusStay()
    {
        // Update target rotation
        RotateHitTarget();
    }

    ...

    ///
    /// This function computes the rotation of the target to move the currently
    /// looked at aspect slowly to the front.
    ///
    private void RotateHitTarget()
    {
        // Example for querying the hit position of the eye gaze ray using EyeGazeProvider
        Vector3 TargetToHit = (this.gameObject.transform.position - InputSystem.EyeGazeProvider.HitPosition).normalized;

        ...
    }
}

查看 API 文档以获取 BaseEyeFocusHandler 的以下可用事件的完整列表:

  • OnEyeFocusStart:在眼睛凝视射线开始与此目标的碰撞体相交后,便会触发
  • OnEyeFocusStay:当眼睛凝视射线与目标的碰撞体相交时触发
  • OnEyeFocusStop:当眼睛凝视射线停止与此目标的碰撞体相交后触发
  • OnEyeFocusDwell:在眼睛凝视射线与此目标的碰撞体相交了指定时间长度后触发。

2. 独立的眼睛凝视专用 EyeTrackingTarget

最后,我们为你提供了一个解决方案,让你可以通过 EyeTrackingTarget 脚本处理完全独立于其他焦点指针的基于眼睛的输入。

这有三个优势

  • 你可以确保全息图像仅对用户的眼睛凝视做出反应。
  • 这与当前活动的主要输入无关。 因此,你可以一次处理多个输入。例如,将快速眼睛瞄准与手势相结合。
  • 已经设置了多个 Unity 事件,以便在 Unity Editor 中或通过代码快速方便地处理和重复使用现有行为。

也有一些缺点

  • 需要更多的工作量来分别处理单独的输入。
  • 不能优雅的降级:只支持眼睛定位。 如果眼动跟踪无效,你需要一些额外的后备。

与 BaseFocusHandler 类似,EyeTrackingTarget 准备了几个特定于眼睛凝视的 Unity 事件,你可以通过 Unity 编辑器(参见下面的示例)或在代码中使用 AddListener() 方便地侦听这些事件

  • OnLookAtStart()
  • WhileLookingAtTarget()
  • OnLookAway()
  • OnDwell()
  • OnSelected()

在下文中,我们将向你介绍如何使用 EyeTrackingTarget 的几个示例

示例 1:目视支持的智能通知

EyeTrackingDemo-02-TargetSelection (Assets/MRTK/Examples/Demos/EyeTracking/Scenes) 中,你可以找到能够回应你的眼睛凝视的“聪明周到通知”的例子。 它们是一些可以放置在场景中的 3D 文本框,当用户查看时,这些文本框会平滑地放大并转向用户以便于阅读。 当用户阅读通知时,信息会一直清晰地显示出来。 在用户读完通知并且将目光移开后,通知将自动消除并逐渐消失。为实现所有这些功能,有几个完全不是特定于目视跟踪的通用行为脚本,例如:

这种方法的优点是相同的脚本可以被各种事件重复使用。 例如,全息图像可以基于语音命令或在按下虚拟按钮后开始朝向用户。 要触发这些事件,只需要引用应在你的 GameObject 附加的 EyeTrackingTarget 脚本中执行的方法。

对于“聪明周到的通知”这个例子,将发生以下情况:

  • OnLookAtStart():通知开始。

    • FaceUser.Engage:转向用户。
    • ChangeSize.Engage:大小增加(直至达到指定的最大比例)
    • BlendOut.Engage:开始更多地混入(在处于更微妙的空闲状态后)
  • OnDwell():通知 BlendOut 脚本,用户凝视通知的时间已经足够长

  • OnLookAway():通知开始

    • FaceUser.Disengage:回到其原始方向
    • ChangeSize.Disengage:缩小到原来的大小
    • BlendOut.Disengage:开始混出 - 如果触发了 OnDwell(),则完全混出并销毁,否则将回到其空闲状态

设计注意事项:这里获得愉快体验的关键是仔细调整任何这些行为的速度,以避免对用户的眼睛凝视行为反应过快而引起不适。 否则,这很快就会让用户感到完全不知所措。

Target Notification

示例 2:在用户看向全息图像宝石时宝石缓慢旋转

与示例 1 类似,我们可以轻松地为 EyeTrackingDemo-02-TargetSelection (Assets/MRTK/Examples/Demos/EyeTracking/Scenes) 场景中的全息图像宝石创建悬停反馈,当用户看向这些宝石时,这些宝石将以恒定方向和恒定速度缓慢旋转(与上面的旋转示例形成对照)。 你只需要从 EyeTrackingTarget 的 WhileLookingAtTarget() 事件中触发全息图形学的旋转。 以下是更多详细信息:

  1. 创建一个包含公共函数的通用脚本,用于旋转该脚本附加到的 GameObject。 下面是 RotateWithConstSpeedDir.cs 中的一个示例,在这个示例中,我们可以通过 Unity Editor 来调整旋转方向和速度

    using UnityEngine;
    
    namespace Microsoft.MixedReality.Toolkit.Examples.Demos.EyeTracking
    {
        /// <summary>
        /// The associated GameObject will rotate when RotateTarget() is called based on a given direction and speed.
        /// </summary>
        public class RotateWithConstSpeedDir : MonoBehaviour
        {
            [Tooltip("Euler angles by which the object should be rotated by.")]
            [SerializeField]
            private Vector3 RotateByEulerAngles = Vector3.zero;
    
            [Tooltip("Rotation speed factor.")]
            [SerializeField]
            private float speed = 1f;
    
            /// <summary>
            /// Rotate game object based on specified rotation speed and Euler angles.
            /// </summary>
            public void RotateTarget()
            {
                transform.eulerAngles = transform.eulerAngles + RotateByEulerAngles * speed;
            }
        }
    }
    
  2. EyeTrackingTarget 脚本添加到你的目标 GameObject 并在 UnityEvent 触发器中引用 RotateTarget() 函数,如下面的屏幕截图所示

    EyeTrackingTarget sample

示例 3:弹出宝石,也称为多模式眼睛凝视支持的目标选择

在前面的示例中,我们已经展示了可以非常轻松地检测出用户是否在看向目标以及如何触发对此行为的反应。 接下来,让我们使用 EyeTrackingTarget 中的 OnSelected() 事件让宝石爆炸。 有趣的部分是如何触发选择EyeTrackingTarget 允许快速分配不同的方式来调用选择:

  • 捏合手势:如果将“选择操作”设置为“选择”,则会使用默认手势触发选择。 这意味着,用户只需抬手,将拇指和食指捏合在一起便可确认选择。

  • 说出“选择”:使用默认语音命令“选择”来选择全息图像

  • 说出“爆炸”或“弹出”:要使用自定义的语音命令,需要遵循以下两个步骤

    1. 设置一个自定义操作,比如“DestroyTarget”

      • 导航到 MRTK -> 输入 -> 输入操作
      • 单击“添加新操作”
    2. 设置触发此操作的语音命令,例如“爆炸”或“弹出”

      • 导航到 MRTK -> 输入 -> 语音
      • 单击“添加新的语音命令”
        • 关联刚刚创建的操作
        • 分配一个 KeyCode 以允许通过按下某个按钮来触发操作

Voice commands EyeTrackingTarget sample

选择宝石后,宝石就会爆炸,发出声音并消失。 这是由 HitBehaviorDestroyOnSelect 脚本处理的。 可以使用两个选项:

  • 在 Unity Editor 中:你只需要将附加到我们每个宝石模板的脚本链接到 Unity Editor 中的 OnSelected() Unity 事件。
  • 在代码中:如果不想拖放 GameObject,也可以直接将侦听器直接添加到你的脚本中。
    下面是我们如何在 HitBehaviorDestroyOnSelect 脚本中执行此操作的示例:
/// <summary>
/// Destroys the game object when selected and optionally plays a sound or animation when destroyed.
/// </summary>
[RequireComponent(typeof(EyeTrackingTarget))] // This helps to ensure that the EyeTrackingTarget is attached
public class HitBehaviorDestroyOnSelect : MonoBehaviour
{
    ...
    private EyeTrackingTarget myEyeTrackingTarget = null;

    private void Start()
    {
        myEyeTrackingTarget = this.GetComponent<EyeTrackingTarget>();

        if (myEyeTrackingTarget != null)
        {
            myEyeTrackingTarget.OnSelected.AddListener(TargetSelected);
        }
    }

    ...

    ///
    /// This is called once the EyeTrackingTarget detected a selection.
    ///
    public void TargetSelected()
    {
        // Play some animation
        // Play some audio effect
        // Handle destroying the target appropriately
    }
}

示例 4:同时使用手部射线和眼睛凝视输入

手部射线优先于头部和眼睛凝视目标。 这意味着,如果启用手部射线,则在手部进入视野的那一刻,手部射线将充当主要指针。 但是,在某些情况下,你可能希望使用手部射线,同时仍要检测用户是否正在看向某个全息图像。 简单! 实质上,需要两个步骤:

1. 启用手部射线:要启用手部射线,请转到“混合现实工具包”->“输入”->“指针”。 在 EyeTrackingDemo-00-RootScene 中,混合现实工具包针对所有眼动跟踪演示场景配置一次,因此你应该会看到 EyeTrackingDemoPointerProfile。 你可以从头开始创建一个新的输入配置文件,也可以调整当前的眼动跟踪

  • 从头开始:在“指针”选项卡中,从上下文菜单中选择“DefaultMixedRealityInputPointerProfile”。 这是已启用手部射线的默认指针配置文件! 要更改默认光标(一个不透明的白点),只需克隆配置文件并创建自己的自定义指针配置文件。 然后在“凝视光标预制件”下面,用 EyeGazeCursor 替换 DefaultCursor
  • 基于现有的 EyeTrackingDemoPointerProfile:双击 EyeTrackingDemoPointerProfile 并在“指针选项”下添加以下条目
    • 控制器类型:“Articulated Hand”和“Windows Mixed Reality”
    • 惯用手:任何
    • 指针预制件:DefaultControllerPointer

2. 检测用户是否在看向全息图像:使用 EyeTrackingTarget 脚本可以检测用户是否在看向全息图像,如上文中所述。 还可以查看 FollowEyeGaze 示例脚本以获取灵感,因为无论是否启用手部射线,这都会显示跟随你的眼睛注视(例如,光标)的全息图像。

现在,当你开始眼动跟踪演示场景时,你应该会看到一道光线从你的手中射出。 例如,在眼动跟踪目标选择演示中,半透明圆圈仍然跟随你的眼睛凝视,宝石会响应是否被注视,而顶部场景菜单按钮使用主要输入指针(你的手)。


返回“MixedRealityToolkit 中的眼动跟踪”