DirectX 中的手部和運動控制器
注意
本文與舊版 WinRT 原生 API 相關。 針對新的原生應用程式專案,我們建議使用 OpenXR API。
在Windows Mixed Reality中,手和動作控制器輸入都是透過Windows.UI.Input.Spatial命名空間中找到的空間輸入 API 來處理。 這可讓您輕鬆地處理一般動作,例如 Select 在雙手和動作控制器上按下相同方式。
開始使用
若要存取 Windows Mixed Reality 中的空間輸入,請從 SpatialInteractionManager 介面開始。 您可以呼叫 SpatialInteractionManager::GetForCurrentView,通常是在應用程式啟動時存取此介面。
using namespace winrt::Windows::UI::Input::Spatial;
SpatialInteractionManager interactionManager = SpatialInteractionManager::GetForCurrentView();
SpatialInteractionManager 的工作是提供 SpatialInteractionSources的存取權,代表輸入的來源。 系統中有三種 SpatialInteractionSources 可供使用。
- 手 代表使用者偵測到的手部。 手部來源會根據裝置提供不同的功能,範圍從 HoloLens 上的基本手勢到HoloLens 2上完整表達的手部追蹤。
- 控制器 代表配對的動作控制器。 動作控制器可以提供不同的功能,例如,選取觸發程式、功能表按鈕、掌握按鈕、觸控板和指紋。
- 語音 代表使用者的語音說話系統偵測到關鍵字。 例如,每當使用者說出 「選取」時,此來源就會插入 Select 按下並放開。
來源的每個框架資料會以 SpatialInteractionSourceState 介面表示。 根據您想要在應用程式中使用事件驅動或輪詢型模型,有兩種不同的方式可以存取此資料。
事件驅動輸入
SpatialInteractionManager 提供一些應用程式可以接聽的事件。 一些範例包括 SourcePressed、[SourceReleased 和 SourceUpdated。
例如,下列程式碼會將名為 MyApp::OnSourcePressed 的事件處理常式連結至 SourcePressed 事件。 這可讓您的應用程式偵測任何類型的互動來源上的按下。
using namespace winrt::Windows::UI::Input::Spatial;
auto interactionManager = SpatialInteractionManager::GetForCurrentView();
interactionManager.SourcePressed({ this, &MyApp::OnSourcePressed });
這個按下的事件會以非同步方式傳送至您的應用程式,以及在按下時對應的 SpatialInteractionSourceState。 您的應用程式或遊戲引擎可能想要立即開始處理,或在輸入處理常式中排入事件資料佇列。 以下是 SourcePressed 事件的事件處理常式函式,它會檢查選取按鈕是否已按下。
using namespace winrt::Windows::UI::Input::Spatial;
void MyApp::OnSourcePressed(SpatialInteractionManager const& sender, SpatialInteractionSourceEventArgs const& args)
{
if (args.PressKind() == SpatialInteractionPressKind::Select)
{
// Select button was pressed, update app state
}
}
上述程式碼只會檢查 'Select' 按下,這對應到裝置上的主要動作。 範例包括在 HoloLens 上執行 AirTap,或在動作控制器上提取觸發程式。 'Select' 按下代表使用者想要啟用其目標全像投影的意圖。 SourcePressed 事件會針對許多不同的按鈕和手勢引發,而且您可以在 SpatialInteractionSource 上檢查其他屬性,以測試這些案例。
輪詢型輸入
您也可以使用 SpatialInteractionManager 來輪詢每個畫面的輸入目前狀態。 若要這樣做,請呼叫 GetDetectedSourcesAtTimestamp 每個畫面。 此函式會針對每個作用中的SpatialInteractionSourceSource傳回包含一個SpatialInteractionSourceState的陣列。 這表示每個作用中的動作控制器各有一個,每個追蹤的手部各一個,一個用於語音,一個用於最近說出 'select' 命令。 然後,您可以檢查每個 SpatialInteractionSourceState 上的屬性,以驅動應用程式輸入。
以下是如何使用輪詢方法來檢查 'select' 動作的範例。 預測變數代表HolographicFramePrediction物件,可從HolographicFrame取得。
using namespace winrt::Windows::UI::Input::Spatial;
auto interactionManager = SpatialInteractionManager::GetForCurrentView();
auto sourceStates = m_spatialInteractionManager.GetDetectedSourcesAtTimestamp(prediction.Timestamp());
for (auto& sourceState : sourceStates)
{
if (sourceState.IsSelectPressed())
{
// Select button is down, update app state
}
}
每個 SpatialInteractionSource 都有一個識別碼,您可以用來識別新的來源,並將現有的來源從框架相互關聯。 每次離開並輸入 FOV 時,都會取得新的識別碼,但控制器識別碼在會話期間維持靜態狀態。 您可以使用 SpatialInteractionManager 上的事件,例如 SourceDetected 和 SourceLost、在手部進入或離開裝置的檢視時,或當動作控制器開啟/關閉或配對/未配對時做出反應。
預測與歷史姿勢
GetDetectedSourcesAtTimestamp 具有時間戳記參數。 這可讓您要求狀態並提出預測或歷史資料,讓您將空間互動與其他輸入來源相互關聯。 例如,在呈現目前畫面格中的手部位置時,您可以傳入 HolographicFrame所提供的預測時間戳記。 這可讓系統向前預測手部位置,以與轉譯的畫面輸出緊密對齊,將感知的延遲降至最低。
不過,這類預測的姿勢不會產生理想的指向光線,以互動來源為目標。 例如,按下動作控制器按鈕時,最多可能需要 20 毫秒的時間,該事件就會透過藍牙升升至作業系統。 同樣地,在使用者執行手勢之後,系統可能會經過一些時間,然後偵測手勢,然後您的應用程式會輪詢它。 在應用程式輪詢狀態變更時,頭部和手部姿勢會用來鎖定該互動實際上在過去發生。 如果您將目前 HolographicFrame 的時間戳記傳遞至 GetDetectedSourcesAtTimestamp,則會改為將姿勢向前預測到畫面顯示時的目標光線,未來可能超過 20 毫秒。 這個未來姿勢很適合轉譯互動來源,但隨著使用者過去的目標發生,將時間問題複合在一起。
幸運的是, SourcePressed、[SourceReleased 和 SourceUpdated 事件提供與每個輸入事件相關聯的歷程 記錄狀態 。 這直接包含透過 TryGetPointerPose取得的歷史頭部和手部姿勢,以及您可以傳遞至其他 API 以與此事件相互關聯的歷程 時間戳記 。
這會導致使用手部和控制器每一個畫面呈現和目標時的下列最佳做法:
- 針對 轉譯每個畫面的手/控制器 ,您的應用程式應該 輪詢 目前畫面的相片時間每個互動來源的向前 預測 姿勢。 您可以呼叫 GetDetectedSourcesAtTimestamp 每個畫面來輪詢所有互動來源,並傳入 HolographicFrame::CurrentPrediction所提供的預測時間戳記。
- 針對以按下或放開 為目標的手/控制器 ,您的應用程式應該處理已按下/放開 的事件,並根據該事件 的歷史 頭部或手部姿勢進行光線廣播。 您可以藉由處理 SourcePressed 或SourceReleased 事件、從事件引數取得 State 屬性,然後呼叫其 TryGetPointerPose 方法,以取得此目標光線。
跨裝置輸入屬性
SpatialInteractionSource API 支援具有各種功能的控制器和手部追蹤系統。 在裝置類型之間,有一些功能很常見。 例如,手部追蹤和動作控制器都提供「選取」動作和 3D 位置。 在可能的情況下,API 會將這些通用功能對應至 SpatialInteractionSource 上的相同屬性。 這可讓應用程式更輕鬆地支援各種不同的輸入類型。 下表描述支援的屬性,以及它們如何跨輸入類型進行比較。
屬性 | 描述 | HoloLens (第 1 代) 手勢 | 運動控制器 | 表達手部 |
---|---|---|---|---|
SpatialInteractionSource::Handedness | 右或左手 / 控制器。 | 不支援 | 支援 | 支援 |
SpatialInteractionSourceState::IsSelectPressed | 主要按鈕的目前狀態。 | 空中點選 | 觸發程序 | 寬鬆的空中點選 (直立捏合) |
SpatialInteractionSourceState::IsGrasped | 抓取按鈕的目前狀態。 | 不支援 | 抓取按鈕 | 捏合或封閉手部 |
SpatialInteractionSourceState::IsMenuPressed | 功能表按鈕的目前狀態。 | 不支援 | 功能表按鈕 | 不支援 |
SpatialInteractionSourceLocation::Position | 控制器上手部或底框位置的 XYZ 位置。 | 手掌位置 | 底框姿勢位置 | 手掌位置 |
SpatialInteractionSourceLocation::Orientation | 四元數,代表控制器上手部或底框姿勢的方向。 | 不支援 | 底框姿勢方向 | 手掌方向 |
SpatialPointerInteractionSourcePose::Position | 指向光線的原點。 | 不支援 | 支援 | 支援 |
SpatialPointerInteractionSourcePose::ForwardDirection | 指向光線的方向。 | 不支援 | 支援 | 支援 |
上述部分屬性在所有裝置上都無法使用,而 API 提供測試此方法。 例如,您可以檢查 SpatialInteractionSource::IsGraspSupported 屬性,以判斷來源是否提供擷取動作。
底框姿勢與指向姿勢
Windows Mixed Reality支援不同尺寸的動作控制器。 它也支援清楚的手部追蹤系統。 所有這些系統在手部位置與自然「向前」方向之間都有不同的關聯性,應用程式應該用來指向或轉譯使用者手上的物件。 為了支援這一切,手部追蹤和運動控制器都有兩種類型的 3D 姿勢。 第一個是手部姿勢,代表使用者的手部位置。 第二個是指向姿勢,代表源自使用者手部或控制器的指向光線。 因此,如果您想要轉譯 使用者手 部或 使用者手上持有的物件,例如箭號或射擊,請使用底框姿勢。 如果您想要從控制器或手部進行光線廣播,例如當使用者是 **指向 UI 時,請使用指向姿勢。
您可以透過SpatialInteractionSourceState::P roperties::TryGetLocation (...) 存取底框姿勢。其定義如下:
- 控點位置:在自然地按住控制器時,將手掌心置中,靠左或向右調整,以置中控夾內的位置。
- 底框方向的右軸:當您完全開啟手部以形成平面的 5 指姿勢時,從左手 (往前的光線,從右手部向後向後)
- 控點方向的正向軸:當您關閉手部部分 (就像按住控制器) 一樣,透過非指指形成之管的光線會指向「向前」。
- 底框方向的向上軸:右方和正向定義所隱含的向上軸。
您可以透過SpatialInteractionSourceState::P roperties::TryGetLocation (...) ::SourcePointerPose或SpatialInteractionSourceState::TryGetPointerPose (...) ::TryGetInteractionSourcePose來存取指標姿勢。
控制器特定的輸入屬性
對於控制器,SpatialInteractionSource 具有具有其他功能的 Controller 屬性。
- HasThumbstick: 如果為 true,則控制器具有搖桿。 檢查 SpatialInteractionSourceState 的 ControllerProperties 屬性,以取得 ThumbstickX 和 ThumbstickY) (ThumbstickX 和 ThumbstickY) 的指紋 x 和 y 值,以及其按下的狀態 (IsThumbstickPressed) 。
- HasTouchpad: 如果為 true,控制器就會有觸控板。 檢查 SpatialInteractionSourceState 的 ControllerProperties 屬性,以取得 touchpad x 和 y 值 (TouchpadX 和 TouchpadY) ,並知道使用者是否在 IsTouchpadTouched (IsTouchpadTouched) ,以及是否按下觸控板向下鍵 (IsTouchpadPressed) 。
- SimpleHapticsController: 控制器的 SimpleHapticsController API 可讓您檢查控制器的觸覺功能,也可讓您控制觸覺回饋。
觸控板和搖桿的範圍是 -1 到 1,兩個軸 (從下到上,以及從左至右) 。 使用 SpatialInteractionSourceState::SelectPressedValue 屬性存取的類比觸發程式範圍有 0 到 1 的範圍。 值 1 與 IsSelectPressed 相互關聯,等於 true;任何其他值都與 IsSelectPressed 相互關聯,其等於 false。
清楚的手部追蹤
Windows Mixed Reality API 提供完整的手部追蹤支援,例如HoloLens 2。 清楚的手部追蹤可用來在應用程式中實作直接操作和點和認可輸入模型。 它也可以用來撰寫完全自訂的互動。
手部基本架構
清楚的手部追蹤提供 25 個聯合基本架構,可啟用許多不同類型的互動。 基本架構為索引/中間/環形/小手指提供五個聯合、四個指指的聯合,以及一個手部接合。 手部聯合可作為階層的基底。 下圖說明基本架構的配置。
在大部分情況下,每個聯結都會根據它所代表的骨頭來命名。 由於每個聯合都有兩個骨頭,因此我們會根據該位置的子骨來命名每個聯合的慣例。 子骨會定義為從手部進一步的骨頭。 例如,「Index Proximal」 聯合包含索引同質骨的開頭位置,以及該骨的方向。 它不包含骨頭的結束位置。 如果您需要的話,您會從階層中的下一個聯合取得它,也就是「索引中繼」聯合。
除了 25 個階層式接合之外,系統還提供手部接合。 手部通常不會被視為基本架構結構的一部分。 它只提供方便的方式來取得手部的整體位置和方向。
每個聯結都會提供下列資訊:
名稱 | 描述 |
---|---|
Position | 接合的 3D 位置,可在任何要求的座標系統中使用。 |
方向 | 3D 方向,可在任何要求的座標系統中取得。 |
半徑 | 在聯合位置的面板表面距離。 適用于調整依賴手指寬度的直接互動或視覺效果。 |
精確度 | 提供系統對於這個聯合資訊感到信心的提示。 |
您可以透過 SpatialInteractionSourceState上的函式來存取手部基本架構資料。 函式稱為 TryGetHandPose,並傳回名為 HandPose的物件。 如果來源不支援表達的手部,則此函式會傳回 null。 一旦您擁有 HandPose,您可以藉由呼叫 TryGetJoint來取得目前的聯合資料,其中包含您感興趣的聯合名稱。 資料會以 JointPose 結構的形式傳回。 下列程式碼會取得索引指尖的位置。 變數 currentState代表SpatialInteractionSourceState的實例。
using namespace winrt::Windows::Perception::People;
using namespace winrt::Windows::Foundation::Numerics;
auto handPose = currentState.TryGetHandPose();
if (handPose)
{
JointPose joint;
if (handPose.TryGetJoint(desiredCoordinateSystem, HandJointKind::IndexTip, joint))
{
float3 indexTipPosition = joint.Position;
// Do something with the index tip position
}
}
手部網格
清楚的手部追蹤 API 允許完全可變的三角形手部網格。 此網格可以即時與手部基本架構一起變形,並適用于視覺效果和進階物理技術。 若要存取手部網格,您必須先在SpatialInteractionSource上呼叫TryCreateHandMeshObserverAsync來建立HandMeshObserver物件。 這只需要為每個來源完成一次,通常是您第一次看到它。 這表示每當手部進入 FOV 時,您都會呼叫此函式來建立 HandMeshObserver 物件。 這是非同步函式,因此您必須在這裡處理一些並行處理。 一旦可供使用,您可以呼叫 GetTriangleIndices,向 HandMeshObserver 物件詢問三角形索引緩衝區。 索引不會在畫面上變更畫面格,因此您可以取得一次,並在來源的存留期快取它們。 索引會以順時針針線順序提供。
下列程式碼會啟動中斷連結的 std::thread,以建立網格觀察者,並在網格觀察者可用後擷取索引緩衝區。 它會從稱為 currentState的變數開始,這是代表追蹤手部 的 SpatialInteractionSourceState 實例。
using namespace Windows::Perception::People;
std::thread createObserverThread([this, currentState]()
{
HandMeshObserver newHandMeshObserver = currentState.Source().TryCreateHandMeshObserverAsync().get();
if (newHandMeshObserver)
{
unsigned indexCount = newHandMeshObserver.TriangleIndexCount();
vector<unsigned short> indices(indexCount);
newHandMeshObserver.GetTriangleIndices(indices);
// Save the indices and handMeshObserver for later use - and use a mutex to synchronize access if needed!
}
});
createObserverThread.detach();
啟動中斷連結的執行緒只是處理非同步呼叫的一個選項。 或者,您可以使用 C++/WinRT 支援的新 co_await 功能。
一旦您有 HandMeshObserver 物件,您應該保留其對應的 SpatialInteractionSource 作用中期間。 然後,您可以呼叫 GetVertexStateForPose ,並傳入代表您要頂點之姿勢的 HandPose 實例,要求它提供代表手部的最新頂點緩衝區。 緩衝區中的每個頂點都有一個位置和一般。 以下是如何取得手部網格目前頂點集的範例。 如同之前, currentState 變數代表 SpatialInteractionSourceState的實例。
using namespace winrt::Windows::Perception::People;
auto handPose = currentState.TryGetHandPose();
if (handPose)
{
std::vector<HandMeshVertex> vertices(handMeshObserver.VertexCount());
auto vertexState = handMeshObserver.GetVertexStateForPose(handPose);
vertexState.GetVertices(vertices);
auto meshTransform = vertexState.CoordinateSystem().TryGetTransformTo(desiredCoordinateSystem);
if (meshTransform != nullptr)
{
// Do something with the vertices and mesh transform, along with the indices that you saved earlier
}
}
相較于基本架構聯結,手部網格 API 不允許您指定頂點的座標系統。 相反地, HandMeshVertexState 會指定提供的頂點座標系統。 接著,您可以呼叫 TryGetTransformTo 並指定您想要的座標系統,以取得網格轉換。 每當使用頂點時,您將需要使用這個網格轉換。 這種方法可減少 CPU 額外負荷,特別是當您只使用網格進行轉譯時。
注視和認可複合手勢
對於使用注視和認可輸入模型的應用程式,特別是在 HoloLens (第一代) 上,空間輸入 API 會提供選擇性 的 SpatialGestureRecognizer ,可用來啟用以 'select' 事件為基礎建置的複合手勢。 藉由將空間InteractionManager 的互動路由傳送至全像投影的 SpatialGestureRecognizer,應用程式可以跨手部、語音和空間輸入裝置一致地偵測點選、按住、操作和流覽事件,而不需要手動處理按下和放開。
SpatialGestureRecognizer 只會在您要求的一組手勢之間執行最小混淆。 例如,如果您只要求 Tap,只要使用者喜歡,使用者就可以按住手指,而且點選仍會發生。 如果您同時要求點選和按住,在大約一秒按住手指之後,手勢將會升階為 [按住],而點選將不再發生。
若要使用 SpatialGestureRecognizer,請處理 SpatialInteractionManager 的 InteractionDetected 事件,並抓取該處公開的 SpatialPointerPose。 使用來自這個姿勢的使用者頭部注視光線,與使用者周圍全像投影和表面網格交集,以判斷使用者想要與之互動的內容。 然後,使用其 CaptureInteraction 方法,將事件引數中的 SpatialInteraction 路由傳送至目標全像投影的 SpatialGestureRecognizer。 這會開始根據在建立時在該辨識器上設定 的 SpatialGestureSettings 來解譯該互動,或藉由 TrySetGestureSettings來解譯該互動。
在 HoloLens (第一代) 上,互動和手勢應該從使用者的頭部注視衍生其目標,而不是在手部位置轉譯或互動。 一旦開始互動,手部的相對動作就可以用來控制手勢,就像操作或導覽手勢一樣。