HoloLens开发手记 - Unity之Gaze凝视射线
凝视是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); } } } }