凝視是HoloLens首要輸入方式,形式功能類似於桌面系統的光標,用於選擇操作全息對象。然而在Unity中並沒有明確的Gaze API或者組件。
概念上來說,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的時候,盡量避免每個物體都發出凝視射線,而是使用單例對象來管理凝視射線和其結果。
可視化凝視的具體例子
可以直接使用HoloToolkit中的GazeManager.cs腳本來實現凝視射線。
可以參考或直接使用HoloToolkit-Unity項目中的GazeManager.cs和預制的各種指針資源,包括Cursor.prefab 和 CursorWithFeedback.prefab 等。
1、添加GazeManager.cs
點擊“ Create Empty” 創建一個空游戲對象,並將其命名為 Manager,為 Manager對象添加核心腳本組件GazeManager.cs
// 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; } /// <summary> /// Object currently being focused on. /// </summary> public GameObject FocusedObject { 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 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> /// Adds the stabilization plane modifier if it's enabled and if it doesn't exist yet. /// </summary> private void UpdateStabilizationPlane() { // We want to use the stabilization logic. if (SetStabilizationPlane) { // Check if it exists in the scene. if (StabilizationPlaneModifier.Instance == null) { // If not, add it to us. gameObject.AddComponent<StabilizationPlaneModifier>(); } } if (StabilizationPlaneModifier.Instance) { StabilizationPlaneModifier.Instance.SetStabilizationPlane = SetStabilizationPlane; } } } }
2、創建一個新的游戲對象Cube,用來測試凝視效果
3、添加Cursor
就像PC使用鼠標來選中和交互圖標一樣,你可以為凝視也實現一個指針來更好的代表用戶的凝視。
從 HoloToolkit/Input/Prefabs/ 目錄下拖拽 Cursor Prefab 組件到場景中。這樣當凝視在全息對象時,其表面會出現一個藍色的光圈,表示當前凝視該對象,當射線離開該游戲對象時,Cursor變成一個點光源,以此來區分是否凝視游戲對象。
可以查看到Cursor中存在兩個光標對象,分別是凝視在對象上及離開光息對象時分別顯示的光標
CursorManager.cs
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using HoloToolkit.Unity; using UnityEngine; /// <summary> /// CursorManager class takes Cursor GameObjects. /// One that is on Holograms and another off Holograms. /// 1. Shows the appropriate Cursor when a Hologram is hit. /// 2. Places the appropriate Cursor at the hit position. /// 3. Matches the Cursor normal to the hit surface. /// </summary> public partial class CursorManager : Singleton<CursorManager> { //凝視射線在全息對象上時顯示的光標對象 [Tooltip("Drag the Cursor object to show when it hits a hologram.")] public GameObject CursorOnHolograms; //凝視射線離開全息對象時顯示的光標對象 [Tooltip("Drag the Cursor object to show when it does not hit a hologram.")] public GameObject CursorOffHolograms; [Tooltip("Distance, in meters, to offset the cursor from the collision point.")] public float DistanceFromCollision = 0.01f; void Awake() { //當未設定光標對象時直接返回 if (CursorOnHolograms == null || CursorOffHolograms == null) { return; } // Hide the Cursors to begin with. CursorOnHolograms.SetActive(false); CursorOffHolograms.SetActive(false); } void LateUpdate() { if (GazeManager.Instance == null || CursorOnHolograms == null || CursorOffHolograms == null) { return; } //當凝視射線在全息對象上及離開全息對象時,分別顯示不同的光標對象,以此來進行區分 if (GazeManager.Instance.Hit) { CursorOnHolograms.SetActive(true); CursorOffHolograms.SetActive(false); } else { CursorOffHolograms.SetActive(true); CursorOnHolograms.SetActive(false); } //計算並安置光標 // Place the cursor at the calculated position. this.gameObject.transform.position = GazeManager.Instance.Position + GazeManager.Instance.Normal * DistanceFromCollision; // Orient the cursor to match the surface being gazed at. gameObject.transform.up = GazeManager.Instance.Normal; } }
4、運行測試
當凝視射線在Cube上時,出現藍色的光圈,表示當前凝視的點在該位置
當凝視射線離開Cube時,光標顯示為一個點光源