在 Unity 中,有兩種主要方式可以對 視線進行操作:手 勢 和 動態控制器 ,分別是 HoloLens 和 Immersive HMD。 你透過 Unity 中的相同 API 存取兩個空間輸入來源的資料。
Unity 提供兩種主要方式來存取 Windows Mixed Reality 的空間輸入資料。 常見的 Input.GetButton/Input.GetAxis API 跨多個 Unity XR SDK 運作,而 Windows Mixed Reality 專用的 InteractionManager/GestureRecognizer API 則會暴露完整的空間輸入資料集合。
Unity XR 輸入 API
對於新專案,我們建議從一開始就使用新的 XR 輸入 API。
你可以在這裡找到更多關於 XR API 的資訊。
Unity 按鈕/軸映射表
Unity 的 Windows Mixed Reality 動作控制器 Input Manager 透過 Input.GetButton/GetAxis API 支援以下列出的按鈕與軸 ID。 「Windows MR-specific」欄位指的是 InteractionSourceState 類型中可用的屬性。 以下章節將詳細說明這些 API。
Windows Mixed Reality 的按鈕/軸 ID 映射通常與 Oculus 按鈕/軸 ID 相符。
Windows Mixed Reality 的按鈕/軸 ID 映射與 OpenVR 有兩個不同之處:
- 映射使用與搖桿不同的觸控板 ID,以支援同時擁有搖桿與觸控板的控制器。
- 這種映射避免了 A 和 X 鍵 ID 在選單鍵上過載,讓它們能用於實體 ABXY 鍵。
| Input |
常見的 Unity API (輸入.GetButton/GetAxis) |
Windows MR 專用輸入 API (XR。WSA。輸入) |
|
|---|---|---|---|
| 左手 | 右手 | ||
| 選擇扳機按下 | 軸 9 = 1.0 | 軸 10 = 1.0 | 選擇按下 |
| 選擇觸發類比值 | 軸9 | 軸 10 | selectPressedAmount |
| 選擇扳機,半按 | 按鈕 14 (手把相容) | 按鈕15 (手把相容) | selectPressedAmount > 0.0 |
| 選單按鈕按下 | 按鈕6* | 按鈕7* | menuPressed |
| 握把按鈕按下 | 軸 11 = 1.0 (無類比值) 按鈕4 (手把相容) | 軸 12 = 1.0 (無類比值) 按鈕 5 (手把相容) | 被抓住 |
| 搖桿 X (左:-1.0,右:1.0) | 軸 1 | 軸4 | 拇指棒位置.x |
| 搖桿 Y (上方:-1.0,下方:1.0) | 軸心二 | 軸5 | 拇指搖桿位置.y |
| 搖桿按下 | 按鈕8 | 按鈕9 | 拇指搖桿按下 |
| 觸控板 X (左:-1.0,右:1.0) | 軸17* | 軸心19* | touchpadPosition.x |
| 觸控板 Y (上:-1.0,下:1.0) | 軸18* | 軸20* | 觸控板位置.y |
| 觸控板被觸碰 | 按鈕18* | 按鈕19* | 觸控板被觸碰 |
| 觸控板按下 | 按鈕16* | 按鈕17* | 觸控板按下 |
| 6 DoF 握法姿勢或指示式 | 握法姿勢:XR。InputTracking.GetLocalPosition XR。輸入追蹤。取得LocalRotation | 傳遞 握法 或 指標 作為參數:sourceState.sourcePose.TryGetPosition 來源州.源 Pose.TryGetRotation |
|
| 追蹤狀態 | 位置精度與來源損耗風險僅透過磁共振專用 API 提供 |
來源狀態。來源姿勢。位置準確度 來源State.properties.sourceLossRisk |
|
注意事項
這些按鈕/軸 ID 與 Unity 用於 OpenVR 的 ID 不同,因為遊戲手把、Oculus Touch 和 OpenVR 所使用的映射存在碰撞。
OpenXR
想了解 Unity 中混合實境互動的基礎,請造訪 Unity XR 輸入手冊。 這份 Unity 文件涵蓋了從控制器特定輸入到更通用的 InputFeatureUsage的映射、如何識別與分類可用的 XR 輸入、如何從這些輸入讀取資料,以及更多內容。
Mixed Reality OpenXR 外掛提供額外的輸入互動配置檔,並映射到標準的 InputFeatureUsages,詳述如下:
| 輸入功能使用法 | HP Reverb G2 控制器 (OpenXR) | HoloLens 手推 OpenXR () |
|---|---|---|
| primary2DAxis | 搖桿 | |
| primary2DAxisClick | 搖桿 - 點擊 | |
| 觸發 | 觸發程序 | |
| 握法 | 握法 | 空氣抽壓或擠壓 |
| 主按鈕 | [X/A] - 媒體 | 空氣抽取 |
| 次要按鈕 | [Y/B] - 媒體 | |
| 握把按鈕 | 握法 - 壓制 | |
| 觸發按鈕 | 扳機 - 按鍵 | |
| menuButton | 功能表 |
握法姿勢與指向式
Windows Mixed Reality 支援各種形式的動作控制器。 每個控制器的設計在使用者手部位置與應用程式在渲染控制器時應使用的「前方」指向之間的關係上有所不同。
為了更好地呈現這些控制器,你可以針對每個互動來源研究兩種姿勢: 握持姿勢 和 指標姿勢。 握持姿勢與指標姿勢座標皆由所有 Unity API 在全域 Unity 世界座標中表達。
握法姿勢
握法姿勢代表使用者手掌的位置,可能是被全息鏡頭偵測到,或是手持動作控制器。
在沉浸式頭戴裝置上,握持姿勢最適合用來渲染 使用者的手 或手 中持有的物體。 握法姿勢也用於視覺化動作控制器。 Windows 提供的 可渲染模型 是動作控制器的起點與旋轉中心。
握法的定義具體如下:
- 握法位置:自然握持控制器時,掌心會向左或向右調整,使握把位置居中。 在 Windows Mixed Reality 的動作控制器中,這個位置通常對齊於抓握按鈕。
- 握法方向的右軸:當你完全打開手掌,形成平坦的五指姿勢時,手掌的光線從左手向前 (,從右手掌向後)
- 握把方向的前軸:當你握手部分 (,像是握住控制器) 時,指向「前方」的光線會穿過你非拇指手指形成的管子。
- 握把方向的上軸:右軸與前軸定義所暗示的上軸。
你可以透過 Unity 的跨廠商輸入 API (XR 來存取握法 姿勢。輸入追蹤。GetLocalPosition/Rotation) 或透過 Windows MR 專用 API (sourceState.sourcePose.TryGetPosition/Rotation,請求 Grip 節點的姿勢資料) 。
指標姿勢
指標姿勢代表控制器尖端朝前。
系統提供的指標姿勢最適合用來渲染 控制器模型本身時進行射線投射。 如果你用其他虛擬物件來取代控制器,例如虛擬槍械,你應該用最自然的光線指向該虛擬物件,例如沿著應用程式定義槍械模型槍管移動的光線。 因為使用者可以看到虛擬物件而非實體控制器,使用虛擬物件指向使用者會更自然。
目前,指標姿勢僅能透過 Windows MR 專用 API sourceState.sourcePose.TryGetPosition/Rotation 在 Unity 中取得,並以 InteractionSourceNode.Pointer 作為參數傳遞。
OpenXR
你可以透過 OpenXR 輸入互動存取兩組姿勢:
- 握把姿勢用於呈現手中的物體
- 這個目標象徵指向世界。
關於此設計及兩種姿勢差異的更多資訊,請參閱 OpenXR 規範 - 輸入子路徑。
由 InputFeatureUsages 提供的 DevicePosition、 DeviceRotation、 DeviceVelocity 和 DeviceAngularVelocity 提供的姿勢,都代表 OpenXR 握把 姿勢。 與握握姿勢相關的輸入功能使用法定義於 Unity 的 CommonUsages。
InputFeatureUsages 提供的姿勢是 PointerPosition、 PointerRotation、 PointerVelocity 和 PointerAngularVelocity 所提供的,都代表 OpenXR 的瞄準 姿勢。 這些 InputFeatureUsage 並未在任何附帶的 C# 檔案中定義,因此你需要自行定義以下 InputFeatureUsages:
public static readonly InputFeatureUsage<Vector3> PointerPosition = new InputFeatureUsage<Vector3>("PointerPosition");
觸覺
關於在 Unity XR 輸入系統中使用觸覺的資訊,可參考 Unity XR 輸入觸覺手冊的文件。
控制器追蹤狀態
和頭戴裝置一樣,Windows Mixed Reality 動作控制器不需要設置外部追蹤感應器。 感測器則是追蹤頭戴裝置內的控制器。
如果使用者將控制器移出頭戴裝置的視野範圍,Windows 在大多數情況下仍會推斷控制器位置。 當控制器長時間失去視覺追蹤時,控制器的位置會降至近似準確度。
此時,系統會將控制器鎖定在使用者身上,追蹤使用者移動時的位置,同時透過內部方向感測器顯示控制器的真實方向。 許多使用控制器指向並啟動 UI 元素的應用程式,能正常運作,且準確度接近使用者察覺。
關於明確追蹤狀態的推理
希望根據追蹤狀態不同處理位置的應用程式,可能會進一步檢查控制器狀態上的屬性,例如 SourceLossRisk 和 PositionAccuracy:
| 追蹤狀態 | 來源LossRisk | 位置精度 | 嘗試獲取位置 |
|---|---|---|---|
| 高精度 | < 1.0 | 高 | 沒錯 |
| 高精度 (有失去) 的風險 | == 1.0 | 高 | 沒錯 |
| 近似準確度 | == 1.0 | 近似 | 沒錯 |
| 沒有立場 | == 1.0 | 近似 | 錯誤 |
這些動作控制器追蹤狀態定義如下:
- 高精度: 雖然動作控制器位於頭戴裝置的視野範圍內,但通常會根據視覺追蹤提供高精度位置。 例如,一個移動的控制器暫時離開視野,或被使用者另一隻手暫時遮蔽於頭戴裝置感應器 () 會根據控制器本身的慣性追蹤,短暫地返回高精度姿勢。
- 高精度 (有失去) 的風險: 當使用者將動作控制器移過頭戴裝置視野邊緣時,頭戴裝置無法視覺追蹤控制器的位置。 應用程式會透過看到 SourceLossRisk 達到 1.0 來判斷控制器何時達到這個視野範圍。 此時,應用程式可能會選擇暫停需要持續高品質姿勢的手把手勢。
- 近似準確度: 當控制器長時間失去視覺追蹤時,控制器的位置會降至近似準確度。 此時,系統會將控制器鎖定在使用者身上,追蹤使用者移動時的位置,同時透過內部方向感測器顯示控制器的真實方向。 許多使用控制器指向並啟動 UI 元素的應用程式,可以正常運作,且準確度大致相當,使用者不會察覺。 輸入需求較高的應用程式可透過檢查 PositionAccuracy 屬性來感知從高準確率降至近似準確度,例如在此期間給予使用者更寬鬆的螢幕外目標碰撞箱。
- 無立場: 雖然控制器可以長時間以大致準確度運作,但有時系統會知道即使是身體鎖定的位置,當下也沒有意義。 例如,一個開機的控制器可能從未被目睹,或使用者放下控制器後被他人撿起。 在這些時候,系統不會向應用程式提供任何位置, TryGetPosition 也不會回傳 false。
常見的 Unity API (Input.GetButton/GetAxis)
命名空間:UnityEngine, UnityEngine.XR
類型:輸入、XR。輸入追蹤
Unity 目前使用其通用的 Input.GetButton/Input.GetAxis API 來公開 Oculus SDK、OpenVR SDK 和 Windows Mixed Reality 的輸入,包括手部和動作控制器。 如果你的應用程式使用這些 API 作為輸入,就能輕鬆支援多個 XR SDK 的動態控制器,包括 Windows Mixed Reality。
取得邏輯按鈕被按下狀態
要使用一般的 Unity 輸入 API,通常先在 Unity 輸入管理器中將按鈕和軸線接到邏輯名稱,並為每個名稱綁定按鈕或軸 ID。 接著你可以寫程式碼來參考那個邏輯按鈕/軸名稱。
例如,要將左側動作控制器的觸發按鈕對應到提交動作,請在 Unity 的「 編輯 > 專案設定 > 輸入 」中,展開「提交」區塊的屬性,在軸下展開。 將正向按鈕或替代正向按鈕屬性改成讀取搖桿按鈕 14,如下所示:
Unity InputManager
你的腳本接著可以用 Input.GetButton 檢查提交動作:
if (Input.GetButton("Submit"))
{
// ...
}
你可以透過在軸下更改 Size 屬性來增加更多邏輯按鈕。
直接取得實體按鈕被按下的狀態
你也可以用 Input.GetKey 依照完全限定名稱手動存取按鈕:
if (Input.GetKey("joystick button 8"))
{
// ...
}
如何呈現手部或動作控制器的姿勢
你可以用XR來存取控制器的位置和旋轉 。輸入追蹤:
Vector3 leftPosition = InputTracking.GetLocalPosition(XRNode.LeftHand);
Quaternion leftRotation = InputTracking.GetLocalRotation(XRNode.LeftHand);
注意事項
上述程式碼代表控制器的握持姿勢 (使用者握持控制器) 的位置,這對於渲染手中的劍或槍,或控制器模型非常有用。
握把姿勢與指標姿勢之間的關係 (控制器尖端指向的) 可能因控制器而異。 目前,僅能透過MR專用的輸入API存取控制器的指標姿態,該API詳見以下章節。
Windows 專用的 API (XR。WSA。輸入)
注意
如果你的專案正在使用任何 XR 處理器。WSA API 正在逐步淘汰,未來 Unity 版本中會被 XR SDK 取代。 對於新專案,我們建議從一開始就使用 XR SDK。 你可以在這裡找到更多關於 XR 輸入系統與 API 的資訊。
命名空間:UnityEngine.XR.WSA.Input。
類型:InteractionManager、InteractionSourceState、InteractionSource、InteractionSourceProperties、InteractionSourceKind、InteractionSourceLocation
若想獲得更詳細Windows Mixed Reality的 HoloLens) 與動作控制器手部輸入 (資訊,您可以選擇使用 Unity Engine.XR.WSA.Input 命名空間下的 Windows 專用空間輸入 API。 它讓你能取得額外資訊,例如位置精度或來源類型,讓你分辨手牌和控制器。
手部與動作控制器狀態的投票
你可以用 GetCurrentReading 方法輪詢每個互動來源的狀態,無論是 (手) 動作控制器。
var interactionSourceStates = InteractionManager.GetCurrentReading();
foreach (var interactionSourceState in interactionSourceStates) {
// ...
}
你收到的每個 InteractionSourceState 代表當前時刻的一個互動來源。 InteractionSourceState 會揭露以下資訊:
選擇/選單/握持/觸控板/搖桿 (發生了哪些 類型的按壓)
if (interactionSourceState.selectPressed) { // ... }其他動作控制器特有的資料,例如觸控板和/或搖桿的 XY 座標及觸控狀態
if (interactionSourceState.touchpadTouched && interactionSourceState.touchpadPosition.x > 0.5) { // ... }InteractionSourceKind 用來判斷來源是手還是動作控制器
if (interactionSourceState.source.kind == InteractionSourceKind.Hand) { // ... }
前向預測渲染姿勢的輪詢
當輪詢手部和控制器的互動來源資料時,你得到的姿勢是當該畫面光子到達使用者眼睛時,前向預測的姿勢。 前向預測姿勢最適合用於 每幀渲染 控制器或持有物體。 如果你用控制器鎖定特定新聞稿或新聞稿,使用以下歷史事件 API 最準確。
var sourcePose = interactionSourceState.sourcePose; Vector3 sourceGripPosition; Quaternion sourceGripRotation; if ((sourcePose.TryGetPosition(out sourceGripPosition, InteractionSourceNode.Grip)) && (sourcePose.TryGetRotation(out sourceGripRotation, InteractionSourceNode.Grip))) { // ... }你也可以取得當前幀的前方預測頭部姿勢。 與原始姿勢相同,它對 渲染 游標很有用,但針對特定新聞或新聞稿的目標,使用以下歷史事件 API 會最為準確。
var headPose = interactionSourceState.headPose; var headRay = new Ray(headPose.position, headPose.forward); RaycastHit raycastHit; if (Physics.Raycast(headPose.position, headPose.forward, out raycastHit, 10)) { var cursorPos = raycastHit.point; // ... }
處理互動源事件
為了處理輸入事件及其準確的歷史姿態資料,你可以處理互動來源事件,而非輪詢。
處理互動源事件:
註冊一個 InteractionManager 輸入事件。 針對你感興趣的每一種互動活動,你都需要訂閱它。
InteractionManager.InteractionSourcePressed += InteractionManager_InteractionSourcePressed;處理這個活動。 一旦你訂閱了互動事件,適當的時候就會收到回電通知。 以 SourcePressed 為例,則是在原始碼被偵測到且尚未釋出或遺失之前。
void InteractionManager_InteractionSourceDetected(InteractionSourceDetectedEventArgs args) var interactionSourceState = args.state; // args.state has information about: // targeting head ray at the time when the event was triggered // whether the source is pressed or not // properties like position, velocity, source loss risk // source id (which hand id for example) and source kind like hand, voice, controller or other }
如何停止處理事件
當你不再對事件感興趣,或是在破壞訂閱該事件的物件時,就該停止處理該事件。 要停止處理該活動,你必須取消訂閱該活動。
InteractionManager.InteractionSourcePressed -= InteractionManager_InteractionSourcePressed;
互動源事件列表
可用的互動來源事件包括:
- InteractionSource偵測到 (來源會變成主動)
- InteractionSourceLost (變成非活躍)
- 互動來源按下 (點擊、按鈕或「選擇」)
- InteractionSource () 按下、按下按鈕或「選擇」結束時發布
- InteractionSourceUpdated (移動或以其他方式更改某些狀態)
歷史目標定位姿勢的事件,最準確地符合新聞稿或新聞稿
前面提到的輪詢 API 會給你的應用程式前向預測的姿勢。 雖然這些預測的姿勢最適合渲染控制器或虛擬手持物件,但未來的姿勢則不適合目標鎖定,原因有兩個:
- 當使用者按下控制器上的按鈕時,系統接收到按鍵前,藍牙上的無線延遲大約會有 20 毫秒。
- 接著,如果你使用前向預測姿勢,還會再施加10到20毫秒的前向預測,目標時間是當前畫面的光子抵達使用者眼睛的時間點。
這表示輪詢會給你一個來源姿勢或頭部姿勢,比使用者在按下或釋放時頭部和手的位置往前 30-40 毫秒前方。 對於 HoloLens 的手部輸入,雖然沒有無線傳輸延遲,但有類似的處理延遲來偵測壓迫器。
為了準確根據使用者原本的手部或控制器按壓意圖來鎖定目標,你應該使用該 InteractionSourcePressed 或 InteractionSourceReleased 輸入事件的歷史來源姿勢或頭部姿勢。
你可以用使用者頭部或控制器的歷史姿勢資料來鎖定新聞稿或新聞稿:
當手勢或控制器按下時的頭部姿勢,可用於 鎖定 目標,判斷使用者正 注視 的目標:
void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs args) { var interactionSourceState = args.state; var headPose = interactionSourceState.headPose; RaycastHit raycastHit; if (Physics.Raycast(headPose.position, headPose.forward, out raycastHit, 10)) { var targetObject = raycastHit.collider.gameObject; // ... } }動作控制器按下時的來源姿勢,可用於 鎖定 使用者指向的目標。 而是控制者經歷了新聞報導的狀態。 如果你是渲染控制器本身,你可以請求指標姿勢而非握把姿勢,從使用者認為該控制器的自然尖端發射目標射線:
void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs args) { var interactionSourceState = args.state; var sourcePose = interactionSourceState.sourcePose; Vector3 sourceGripPosition; Quaternion sourceGripRotation; if ((sourcePose.TryGetPosition(out sourceGripPosition, InteractionSourceNode.Pointer)) && (sourcePose.TryGetRotation(out sourceGripRotation, InteractionSourceNode.Pointer))) { RaycastHit raycastHit; if (Physics.Raycast(sourceGripPosition, sourceGripRotation * Vector3.forward, out raycastHit, 10)) { var targetObject = raycastHit.collider.gameObject; // ... } } }
事件處理器範例
using UnityEngine.XR.WSA.Input;
void Start()
{
InteractionManager.InteractionSourceDetected += InteractionManager_InteractionSourceDetected;
InteractionManager.InteractionSourceLost += InteractionManager_InteractionSourceLost;
InteractionManager.InteractionSourcePressed += InteractionManager_InteractionSourcePressed;
InteractionManager.InteractionSourceReleased += InteractionManager_InteractionSourceReleased;
InteractionManager.InteractionSourceUpdated += InteractionManager_InteractionSourceUpdated;
}
void OnDestroy()
{
InteractionManager.InteractionSourceDetected -= InteractionManager_InteractionSourceDetected;
InteractionManager.InteractionSourceLost -= InteractionManager_InteractionSourceLost;
InteractionManager.InteractionSourcePressed -= InteractionManager_InteractionSourcePressed;
InteractionManager.InteractionSourceReleased -= InteractionManager_InteractionSourceReleased;
InteractionManager.InteractionSourceUpdated -= InteractionManager_InteractionSourceUpdated;
}
void InteractionManager_InteractionSourceDetected(InteractionSourceDetectedEventArgs args)
{
// Source was detected
// args.state has the current state of the source including id, position, kind, etc.
}
void InteractionManager_InteractionSourceLost(InteractionSourceLostEventArgs state)
{
// Source was lost. This will be after a SourceDetected event and no other events for this
// source id will occur until it is Detected again
// args.state has the current state of the source including id, position, kind, etc.
}
void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs state)
{
// Source was pressed. This will be after the source was detected and before it is
// released or lost
// args.state has the current state of the source including id, position, kind, etc.
}
void InteractionManager_InteractionSourceReleased(InteractionSourceReleasedEventArgs state)
{
// Source was released. The source would have been detected and pressed before this point.
// This event will not fire if the source is lost
// args.state has the current state of the source including id, position, kind, etc.
}
void InteractionManager_InteractionSourceUpdated(InteractionSourceUpdatedEventArgs state)
{
// Source was updated. The source would have been detected before this point
// args.state has the current state of the source including id, position, kind, etc.
}
MRTK 中的動作控制器
你可以從輸入管理器存取手 勢和動作控制器 。
跟著教學跟著看
Mixed Reality Academy 提供逐步教學及更詳細的自訂範例:
下一步開發檢查點
如果你正在跟隨我們所規劃的 Unity 開發旅程,你正處於探索 MRTK 核心建構元素的過程中。 從這裡,你可以繼續進入下一個建築區塊:
或者直接跳到Mixed Reality平台的功能與API:
你隨時都可以回到 Unity 開發檢查點 。