HoloLens开发手记 - Unity之Gaze凝视射线

时间:2022-12-30 15:57:13

凝视是HoloLens首要输入方式,形式功能类似于桌面系统的光标,用于选择操作全息对象。然而在Unity中并没有明确的Gaze API或者组件。

实现Gaze Implementing Gaze


概念上来说,Gaze是通过用户头部两眼之间发出一条向前方的射线来实现的,射线可以识别它所碰撞的物体。在Unity中,使用Main Camera来表示用户头部的位置和朝向。准确的说,是指 UnityEngine.Camera.main.transform.forward 和 UnityEngine.Camera.main.transform.position.

调用 Physics.RayCast 发出射线后可以得到 RaycastHit 结果,该结果包含了碰撞点的3D位置参数和碰撞对象。

实现Gaze的例子

void Update()
{
RaycastHit hitInfo;
if (Physics.Raycast(
Camera.main.transform.position,
Camera.main.transform.forward,
out hitInfo,
20.0f,
Physics.DefaultRaycastLayers))
{
// 如果射线成功击中物体
// hitInfo.point代表了射线碰撞的位置
// hitInfo.collider.gameObject代表了射线注视的全息对象
}
}

最佳做法

在使用Gaze的时候,尽量避免每个物体都发出凝视射线,而是使用单例对象来管理凝视射线和其结果。

可视化凝视 Visualizing Gaze


就像PC使用鼠标来选中和交互图标一样,你可以为凝视也实现一个指针来更好的代表用户的凝视。

可视化凝视的例子

可以参考或直接使用HoloToolkit-Unity项目中的GazeManager.cs和预制的各种指针资源,包括Cursor.prefab 和 CursorWithFeedback.prefab 等。

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information. using UnityEngine;
using UnityEngine.VR.WSA; namespace HoloToolkit.Unity
{
/// <summary>
/// GazeManager determines the location of the user's gaze, hit position and normals.
/// </summary>
public partial class GazeManager : Singleton<GazeManager>
{
[Tooltip("Maximum gaze distance, in meters, for calculating a hit.")]
public float MaxGazeDistance = 15.0f; [Tooltip("Select the layers raycast should target.")]
public LayerMask RaycastLayerMask = Physics.DefaultRaycastLayers; /// <summary>
/// Physics.Raycast result is true if it hits a hologram.
/// </summary>
public bool Hit { get; private set; } /// <summary>
/// HitInfo property gives access
/// to RaycastHit public members.
/// </summary>
public RaycastHit HitInfo { get; private set; } /// <summary>
/// Position of the intersection of the user's gaze and the holograms in the scene.
/// </summary>
public Vector3 Position { get; private set; } /// <summary>
/// RaycastHit Normal direction.
/// </summary>
public Vector3 Normal { get; private set; } [Tooltip("Checking enables SetFocusPointForFrame to set the stabilization plane.")]
public bool SetStabilizationPlane = true;
[Tooltip("Lerp speed when moving focus point closer.")]
public float LerpStabilizationPlanePowerCloser = 4.0f;
[Tooltip("Lerp speed when moving focus point farther away.")]
public float LerpStabilizationPlanePowerFarther = 7.0f; private Vector3 gazeOrigin;
private Vector3 gazeDirection;
private float lastHitDistance = 15.0f;
private GameObject focusedObject; private void Update()
{
gazeOrigin = Camera.main.transform.position;
gazeDirection = Camera.main.transform.forward; UpdateRaycast(); UpdateStabilizationPlane();
} /// <summary>
/// Calculates the Raycast hit position and normal.
/// </summary>
private void UpdateRaycast()
{
// Get the raycast hit information from Unity's physics system.
RaycastHit hitInfo;
Hit = Physics.Raycast(gazeOrigin,
gazeDirection,
out hitInfo,
MaxGazeDistance,
RaycastLayerMask); GameObject oldFocusedObject = focusedObject;
// Update the HitInfo property so other classes can use this hit information.
HitInfo = hitInfo; if (Hit)
{
// If the raycast hits a hologram, set the position and normal to match the intersection point.
Position = hitInfo.point;
Normal = hitInfo.normal;
lastHitDistance = hitInfo.distance;
focusedObject = hitInfo.collider.gameObject;
}
else
{
// If the raycast does not hit a hologram, default the position to last hit distance in front of the user,
// and the normal to face the user.
Position = gazeOrigin + (gazeDirection * lastHitDistance);
Normal = gazeDirection;
focusedObject = null;
} // Check if the currently hit object has changed
if (oldFocusedObject != focusedObject)
{
if (oldFocusedObject != null)
{
oldFocusedObject.SendMessage("OnGazeLeave", SendMessageOptions.DontRequireReceiver);
}
if (focusedObject != null)
{
focusedObject.SendMessage("OnGazeEnter", SendMessageOptions.DontRequireReceiver);
}
}
} /// <summary>
/// Updates the focus point for every frame.
/// </summary>
private void UpdateStabilizationPlane()
{
if (SetStabilizationPlane)
{
// Calculate the delta between camera's position and current hit position.
float focusPointDistance = (gazeOrigin - Position).magnitude;
float lerpPower = focusPointDistance > lastHitDistance
? LerpStabilizationPlanePowerFarther
: LerpStabilizationPlanePowerCloser; // Smoothly move the focus point from previous hit position to new position.
lastHitDistance = Mathf.Lerp(lastHitDistance, focusPointDistance, lerpPower * Time.deltaTime); Vector3 newFocusPointPosition = gazeOrigin + (gazeDirection * lastHitDistance); HolographicSettings.SetFocusPointForFrame(newFocusPointPosition, -gazeDirection);
}
}
}
}