Unity での拡張視線追跡

拡張視線追跡サンプルの GitHub リポジトリにアクセスするには:

拡張視線追跡は、HoloLens 2の新機能です。 これは、組み合わされた視線入力データのみを提供する、標準的な視線追跡のスーパーセットです。 拡張視線追跡では、個々の視線入力データも提供され、アプリケーションでは、30、60、90fps などの視線入力データのさまざまなフレーム レートを設定できます。 現時点では、目の開きや目の色など、その他の機能はHoloLens 2ではサポートされていません。

Extended Eye Tracking SDK を使用すると、アプリケーションは拡張視線追跡のデータと機能にアクセスできます。 OpenXR API または従来の WinRT API と共に使用できます。

この記事では、Unity で拡張視線追跡 SDK を Mixed Reality OpenXR プラグインと共に使用する方法について説明します。

プロジェクトの設定

  1. HoloLens 開発用に Unity プロジェクトを設定します。
    • 視線入力機能を選択する
  2. MRTK 機能ツールから Mixed Reality OpenXR プラグインをインポートします。
  3. Eye Tracking SDK NuGet パッケージを Unity プロジェクトにインポートします。
    1. NuGetForUnity パッケージをダウンロードしてインストールします。
    2. Unity エディターで、 に>Manage NuGet Packages移動NuGetし、 を検索します。Microsoft.MixedReality.EyeTracking
    3. [インストール] ボタンをクリックして、最新バージョンの NuGet パッケージをインポートします。
      Eye Tracking SDK Nuget パッケージのスクリーンショット。
  4. Unity ヘルパー スクリプトを追加します。
    1. ExtendedEyeGazeDataProvider.cs ここから Unity プロジェクトにスクリプトを追加します。
    2. シーンを作成し、スクリプトを任意の ExtendedEyeGazeDataProvider.cs GameObject にアタッチします。
  5. の関数 ExtendedEyeGazeDataProvider.cs を使用し、ロジックを実装します。
  6. HoloLens をビルドしてデプロイします

ExtendedEyeGazeDataProvider の関数を使用する

Note

スクリプトはExtendedEyeGazeDataProvider、視線入力データの座標を変換するために、Mixed Reality OpenXR プラグインの一部の API に依存します。 Unity プロジェクトで非推奨の Windows XR プラグインまたは古い Unity バージョンの従来の組み込み XR を使用している場合は機能しません。 拡張視線追跡をこれらのシナリオでも機能させるには、次の手順を実行します。

  • フレーム レート設定にアクセスするだけで済む場合は、Mixed Reality OpenXR プラグインは必要ありません。フレーム レート関連のロジックのみを保持するように をExtendedEyeGazeDataProvider変更できます。
  • それでも個々の視線入力データにアクセスする必要がある場合は、 Unity で WinRT API を使用する必要があります。 WinRT API で拡張視線追跡 SDK を使用する方法については、「関連項目」セクションを参照してください。

クラスは ExtendedEyeGazeDataProvider 、拡張視線追跡 SDK API をラップします。 Unity ワールド空間またはメイン カメラに対する相対的な視線読み取りを取得する関数を提供します。

視線入力データを取得するために使用 ExtendedEyeGazeDataProvider するコード サンプルを次に示します。

ExtendedEyeGazeDataProvider extendedEyeGazeDataProvider;
void Update() {
    timestamp = DateTime.Now;

    var leftGazeReadingInWorldSpace = extendedEyeGazeDataProvider.GetWorldSpaceGazeReading(extendedEyeGazeDataProvider.GazeType.Left, timestamp);
    var rightGazeReadingInWorldSpace = extendedEyeGazeDataProvider.GetWorldSpaceGazeReading(extendedEyeGazeDataProvider.GazeType.Right, timestamp);
    var combinedGazeReadingInWorldSpace = extendedEyeGazeDataProvider.GetWorldSpaceGazeReading(extendedEyeGazeDataProvider.GazeType.Combined, timestamp);

    var combinedGazeReadingInCameraSpace = extendedEyeGazeDataProvider.GetCameraSpaceGazeReading(extendedEyeGazeDataProvider.GazeType.Combined, timestamp);
}

スクリプトを ExtendedEyeGazeDataProvider 実行すると、視線入力データのフレーム レートが最も高いオプション (現在は 90fps) に設定されます。

拡張視線追跡 SDK の API リファレンス

スクリプトの使用とは別に、独自の ExtendedEyeGazeDataProvider スクリプトを作成して、次の SDK API を直接使用することもできます。

namespace Microsoft.MixedReality.EyeTracking
{
    /// <summary>
    /// Allow discovery of Eye Gaze Trackers connected to the system
    /// This is the only class from the Extended Eye Tracking SDK that the application will instantiate, 
    /// other classes' instances will be returned by method calls or properties.
    /// </summary>
    public class EyeGazeTrackerWatcher
    {
        /// <summary>
        /// Constructs an instance of the watcher
        /// </summary>
        public EyeGazeTrackerWatcher();

        /// <summary>
        /// Starts trackers enumeration.
        /// </summary>
        /// <returns>Task representing async action; completes when the initial enumeration is completed</returns>
        public System.Threading.Tasks.Task StartAsync();

        /// <summary>
        /// Stop listening to trackers additions and removal
        /// </summary>
        public void Stop();

        /// <summary>
        /// Raised when an Eye Gaze tracker is connected
        /// </summary>
        public event System.EventHandler<EyeGazeTracker> EyeGazeTrackerAdded;

        /// <summary>
        /// Raised when an Eye Gaze tracker is disconnected
        /// </summary>
        public event System.EventHandler<EyeGazeTracker> EyeGazeTrackerRemoved;        
    }

    /// <summary>
    /// Represents an Eye Tracker device
    /// </summary>
    public class EyeGazeTracker
    {
        /// <summary>
        /// True if Restricted mode is supported, which means the driver supports providing individual 
        /// eye gaze vector and frame rate 
        /// </summary>
        public bool IsRestrictedModeSupported;

        /// <summary>
        /// True if Vergence Distance is supported by tracker
        /// </summary>
        public bool IsVergenceDistanceSupported;

        /// <summary>
        /// True if Eye Openness is supported by the driver
        /// </summary>
        public bool IsEyeOpennessSupported;

        /// <summary>
        /// True if individual gazes are supported
        /// </summary>
        public bool AreLeftAndRightGazesSupported;

        /// <summary>
        /// Get the supported target frame rates of the tracker
        /// </summary>
        public System.Collections.Generic.IReadOnlyList<EyeGazeTrackerFrameRate> SupportedTargetFrameRates;

        /// <summary>
        /// NodeId of the tracker, used to retrieve a SpatialLocator or SpatialGraphNode to locate the tracker in the scene
        /// for the Perception API, use SpatialGraphInteropPreview.CreateLocatorForNode
        /// for the Mixed Reality OpenXR API, use SpatialGraphNode.FromDynamicNodeId
        /// </summary>
        public Guid TrackerSpaceLocatorNodeId;

        /// <summary>
        /// Opens the tracker
        /// </summary>
        /// <param name="restrictedMode">True if restricted mode active</param>
        /// <returns>Task representing async action; completes when the initial enumeration is completed</returns>
        public System.Threading.Tasks.Task OpenAsync(bool restrictedMode);

        /// <summary>
        /// Closes the tracker
        /// </summary>
        public void Close();

        /// <summary>
        /// Changes the target frame rate of the tracker
        /// </summary>
        /// <param name="newFrameRate">Target frame rate</param>
        public void SetTargetFrameRate(EyeGazeTrackerFrameRate newFrameRate);

        /// <summary>
        /// Try to get tracker state at a given timestamp
        /// </summary>
        /// <param name="timestamp">timestamp</param>
        /// <returns>State if available, null otherwise</returns>
        public EyeGazeTrackerReading TryGetReadingAtTimestamp(DateTime timestamp);

        /// <summary>
        /// Try to get tracker state at a system relative time
        /// </summary>
        /// <param name="time">time</param>
        /// <returns>State if available, null otherwise</returns>
        public EyeGazeTrackerReading TryGetReadingAtSystemRelativeTime(TimeSpan time);

        /// <summary>
        /// Try to get first first tracker state after a given timestamp
        /// </summary>
        /// <param name="timestamp">timestamp</param>
        /// <returns>State if available, null otherwise</returns>
        public EyeGazeTrackerReading TryGetReadingAfterTimestamp(DateTime timestamp);

        /// <summary>
        /// Try to get the first tracker state after a system relative time
        /// </summary>
        /// <param name="time">time</param>
        /// <returns>State if available, null otherwise</returns>
        public EyeGazeTrackerReading TryGetReadingAfterSystemRelativeTime(TimeSpan time);
    }

    /// <summary>
    /// Represents a frame rate supported by an Eye Tracker
    /// </summary>
    public class EyeGazeTrackerFrameRate
    {
        /// <summary>
        /// Frames per second of the frame rate
        /// </summary>
        public UInt32 FramesPerSecond;
    }

    /// <summary>
    /// Snapshot of Gaze Tracker state
    /// </summary>
    public class EyeGazeTrackerReading
    {
        /// <summary>
        /// Timestamp of state
        /// </summary>
        public DateTime Timestamp;

        /// <summary>
        /// Timestamp of state as system relative time
        /// Its SystemRelativeTime.Ticks could provide the QPC time to locate tracker pose 
        /// </summary>
        public TimeSpan SystemRelativeTime;

        /// <summary>
        /// Indicates of user calibration is valid
        /// </summary>
        public bool IsCalibrationValid;

        /// <summary>
        /// Tries to get a vector representing the combined gaze related to the tracker's node
        /// </summary>
        /// <param name="origin">Origin of the gaze vector</param>
        /// <param name="direction">Direction of the gaze vector</param>
        /// <returns></returns>
        public bool TryGetCombinedEyeGazeInTrackerSpace(out System.Numerics.Vector3 origin, out System.Numerics.Vector3 direction);

        /// <summary>
        /// Tries to get a vector representing the left eye gaze related to the tracker's node
        /// </summary>
        /// <param name="origin">Origin of the gaze vector</param>
        /// <param name="direction">Direction of the gaze vector</param>
        /// <returns></returns>
        public bool TryGetLeftEyeGazeInTrackerSpace(out System.Numerics.Vector3 origin, out System.Numerics.Vector3 direction);

        /// <summary>
        /// Tries to get a vector representing the right eye gaze related to the tracker's node position
        /// </summary>
        /// <param name="origin">Origin of the gaze vector</param>
        /// <param name="direction">Direction of the gaze vector</param>
        /// <returns></returns>
        public bool TryGetRightEyeGazeInTrackerSpace(out System.Numerics.Vector3 origin, out System.Numerics.Vector3 direction);

        /// <summary>
        /// Tries to read vergence distance
        /// </summary>
        /// <param name="value">Vergence distance if available</param>
        /// <returns>bool if value is valid</returns>
        public bool TryGetVergenceDistance(out float value);

        /// <summary>
        /// Tries to get left Eye openness information
        /// </summary>
        /// <param name="value">Eye Openness if valid</param>
        /// <returns>bool if value is valid</returns>
        public bool TryGetLeftEyeOpenness(out float value);

        /// <summary>
        /// Tries to get right Eye openness information
        /// </summary>
        /// <param name="value">Eye openness if valid</param>
        /// <returns>bool if value is valid</returns>
        public bool TryGetRightEyeOpenness(out float value);
    }
}

関連項目