Händer och rörelsekontroller i DirectX
Anteckning
Den här artikeln handlar om äldre inbyggda WinRT-API:er. För nya interna appprojekt rekommenderar vi att du använder OpenXR-API:et.
I Windows Mixed Reality hanteras både hand- och rörelsestyrenhetsindata via API:erna för rumslig indata som finns i namnområdet Windows.UI.Input.Spatial. På så sätt kan du enkelt hantera vanliga åtgärder som Select trycker på samma sätt över både händer och rörelsestyrenheter.
Komma igång
Om du vill komma åt rumsliga indata i Windows Mixed Reality börjar du med SpatialInteractionManager-gränssnittet. Du kan komma åt det här gränssnittet genom att anropa SpatialInteractionManager::GetForCurrentView, vanligtvis någon gång under appstarten.
using namespace winrt::Windows::UI::Input::Spatial;
SpatialInteractionManager interactionManager = SpatialInteractionManager::GetForCurrentView();
SpatialInteractionManagers jobb är att ge åtkomst till SpatialInteractionSources, som representerar en indatakälla. Det finns tre typer av SpatialInteractionSources i systemet.
- Hand representerar en användares identifierade hand. Handkällor erbjuder olika funktioner baserat på enheten, allt från grundläggande gester på HoloLens till fullständigt artikulerad handspårning på HoloLens 2.
- Kontrollanten representerar en länkad rörelsekontroll. Rörelsestyrenheter kan erbjuda olika funktioner, till exempel Välj utlösare, Menyknappar, Grip-knappar, pekplattor och tumpinnar.
- Röst representerar användarens rösttalande systemdefinierade nyckelord. Den här källan matar till exempel in en Select-press och -version när användaren säger "Välj".
Data per ram för en källa representeras av SpatialInteractionSourceState-gränssnittet . Det finns två olika sätt att komma åt dessa data, beroende på om du vill använda en händelsedriven eller avsökningsbaserad modell i ditt program.
Händelsedrivna indata
SpatialInteractionManager tillhandahåller ett antal händelser som appen kan lyssna efter. Några exempel är SourcePressed, [SourceReleased och SourceUpdated.
Följande kod kopplar till exempel en händelsehanterare med namnet MyApp::OnSourcePressed till händelsen SourcePressed. På så sätt kan din app identifiera tryck på alla typer av interaktionskällor.
using namespace winrt::Windows::UI::Input::Spatial;
auto interactionManager = SpatialInteractionManager::GetForCurrentView();
interactionManager.SourcePressed({ this, &MyApp::OnSourcePressed });
Den här pressade händelsen skickas till din app asynkront, tillsammans med motsvarande SpatialInteractionSourceState vid tidpunkten då pressen inträffade. Din app eller spelmotor kanske vill börja bearbeta direkt eller köa händelsedata i din indatabearbetningsrutin. Här är en händelsehanterarfunktion för händelsen SourcePressed, som kontrollerar om knappen Välj har tryckts ned.
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
}
}
Koden ovan söker bara efter tryckningen "Välj", vilket motsvarar den primära åtgärden på enheten. Exempel är att göra en AirTap på HoloLens eller att trycka av på en rörelsestyrenhet. "Välj"-tryckning representerar användarens avsikt att aktivera det hologram som de riktar in sig på. Händelsen SourcePressed utlöses för ett antal olika knappar och gester, och du kan granska andra egenskaper på SpatialInteractionSource för att testa dessa fall.
Avsökningsbaserade indata
Du kan också använda SpatialInteractionManager för att söka efter det aktuella indatatillståndet för varje bildruta. Det gör du genom att anropa GetDetectedSourcesAtTimestamp varje bildruta. Den här funktionen returnerar en matris som innehåller en SpatialInteractionSourceState för varje aktiv SpatialInteractionSource. Det innebär en för varje aktiv rörelsekontrollant, en för varje spårad hand och en för tal om ett select-kommando nyligen yttrades. Du kan sedan granska egenskaperna på varje SpatialInteractionSourceState för att köra indata till ditt program.
Här är ett exempel på hur du söker efter åtgärden "välj" med hjälp av avsökningsmetoden. Förutsägelsevariabeln representerar ett HolographicFramePrediction-objekt som kan hämtas från 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
}
}
Varje SpatialInteractionSource har ett ID som du kan använda för att identifiera nya källor och korrelera befintliga källor från ram till ram. Händerna får ett nytt ID varje gång de lämnar och anger FOV, men kontrollant-ID:n förblir statiska under hela sessionen. Du kan använda händelserna på SpatialInteractionManager, till exempel SourceDetected och SourceLost, för att reagera när händerna kommer in i eller lämnar enhetens vy, eller när rörelsekontrollanter är aktiverade/avstängda eller är kopplade/obetalda.
Förutsagda kontra historiska poser
GetDetectedSourcesAtTimestamp har en tidsstämpelparameter. På så sätt kan du begära tillstånd och ange data som antingen är förutsagda eller historiska, så att du kan korrelera rumsliga interaktioner med andra indatakällor. När du till exempel återger handens position i den aktuella ramen kan du skicka in den förutsagda tidsstämpeln som tillhandahålls av HolographicFrame. Detta gör det möjligt för systemet att framåtförutsäga handpositionen så att den överensstämmer med utdata från den renderade ramen, vilket minimerar upplevd svarstid.
En sådan förutsagd pose ger dock inte en idealisk pekstråle för inriktning med en interaktionskälla. När en rörelsestyrenhetsknapp trycks in kan det till exempel ta upp till 20 ms för händelsen att bubbla upp via Bluetooth till operativsystemet. När en användare gör en handgest kan en viss tid gå innan systemet identifierar gesten och appen sedan söker efter den. När din app söker efter en tillståndsändring, utgör huvudet och handen som används för att rikta interaktionen faktiskt inträffat tidigare. Om du anger målet genom att skicka den aktuella HolographicFrame-tidsstämpeln till GetDetectedSourcesAtTimestamp vidarebefordras ställningen i stället till målstrålen vid den tidpunkt då ramen visas, vilket kan vara mer än 20 ms i framtiden. Den här framtida attityden är bra för att återge interaktionskällan, men förvärrar vårt tidsproblem för att rikta interaktionen, eftersom användarens målinriktning inträffade tidigare.
Lyckligtvis tillhandahåller händelserna SourcePressed, [SourceReleased och SourceUpdated det historiska tillstånd som är associerat med varje indatahändelse. Detta inkluderar direkt historiska huvud- och handställningar som är tillgängliga via TryGetPointerPose, tillsammans med en historisk tidsstämpel som du kan skicka till andra API:er för att korrelera med den här händelsen.
Det leder till följande metodtips vid återgivning och målanpassning med händer och styrenheter varje bildruta:
- För hand-/kontrollantåtergivning av varje bildruta bör appen söka efter den framåtsagda attityden för varje interaktionskälla vid den aktuella ramens fotontid. Du kan söka efter alla interaktionskällor genom att anropa GetDetectedSourcesAtTimestamp varje bildruta och skicka in den förutsagda tidsstämpeln som tillhandahålls av HolographicFrame::CurrentPrediction.
- För hand/kontrollant som riktar in sig på ett pressmeddelande bör din app hantera pressade/utgivna händelser, raycasting baserat på det historiska huvudet eller handställningen för händelsen. Du får den här målstrålen genom att hantera händelsen SourcePressed eller SourceReleased , hämta egenskapen State från händelseargumenten och sedan anropa dess TryGetPointerPose-metod .
Egenskaper för indata mellan enheter
SpatialInteractionSource-API:et stöder kontrollanter och handspårningssystem med en mängd olika funktioner. Ett antal av dessa funktioner är vanliga mellan enhetstyper. Till exempel ger handspårning och rörelsekontrollanter både en "select"-åtgärd och en 3D-position. När det är möjligt mappar API:et dessa vanliga funktioner till samma egenskaper på SpatialInteractionSource. Detta gör det möjligt för program att enklare stödja en mängd olika indatatyper. I följande tabell beskrivs de egenskaper som stöds och hur de jämförs mellan olika indatatyper.
Egenskap | Beskrivning | HoloLens(1:a gen) Gester | Rörelsekontrollanter | Ledade händer |
---|---|---|---|---|
SpatialInteractionSource::Handedness | Höger eller vänster hand/styrenhet. | Stöds inte | Stöds | Stöds |
SpatialInteractionSourceState::IsSelectPressed | Aktuellt tillstånd för den primära knappen. | Lufttryck | Utlösare | Avslappnad luftkran (upprätt nypa) |
SpatialInteractionSourceState::IsGrasped | Aktuellt tillstånd för greppsknappen. | Stöds inte | Knappen Ta tag i | Dra ihop eller sluten hand |
SpatialInteractionSourceState::IsMenuPressed | Menyknappens aktuella tillstånd. | Stöds inte | Menyknapp | Stöds inte |
SpatialInteractionSourceLocation::Position | XYZ plats för hand- eller grepppositionen på styrenheten. | Palmplats | Position för gripposition | Palmplats |
SpatialInteractionSourceLocation::Orientation | Quaternion representerar hand- eller grepppositionens orientering på styrenheten. | Stöds inte | Orientering för greppposition | Palmorientering |
SpatialPointerInteractionSourcePose::Position | Pekstrålens ursprung. | Stöds inte | Stöds | Stöds |
SpatialPointerInteractionSourcePose::ForwardDirection | Riktning på pekstrålen. | Stöds inte | Stöds | Stöds |
Vissa av egenskaperna ovan är inte tillgängliga på alla enheter, och API:et tillhandahåller ett sätt att testa detta. Du kan till exempel granska egenskapen SpatialInteractionSource::IsGraspSupported för att avgöra om källan ger en greppåtgärd.
Greppposition jämfört med pekposition
Windows Mixed Reality stöder rörelsekontrollanter i olika formfaktorer. Det stöder också ledade handspårningssystem. Alla dessa system har olika relationer mellan handpositionen och den naturliga framåtriktningen som appar ska använda för att peka eller återge objekt i användarens hand. För att stödja allt detta finns det två typer av 3D-poser för både handspårning och rörelsekontrollanter. Den första är grepppositionen, som representerar användarens handposition. Den andra är pekpositionen, som representerar en pekstråle som kommer från användarens hand eller kontrollant. Så om du vill återge användarens hand eller ett föremål som hålls i användarens hand, till exempel ett svärd eller en pistol, använder du greppet. Om du vill raycasta från kontrollanten eller handen, till exempel när användaren **pekar på användargränssnittet, använder du pekpositionen.
Du kan komma åt greppet viaSpatialInteractionSourceState::P roperties::TryGetLocation(...). Den definieras på följande sätt:
- Grippositionen: Handflatans centroid när styrenheten hålls naturligt, justerad åt vänster eller höger för att centrera positionen i greppet.
- Grepporienteringens högra axel: När du öppnar handen helt för att bilda en platt 5-fingerställning, strålen som är normal för handflatan (framåt från vänster handflata, bakåt från höger handflata)
- Greppets orienteringsaxel Framåt: När du stänger handen delvis (som om du höll styrenheten), strålen som pekar "framåt" genom röret som bildas av dina icke-tumme fingrar.
- Handtagsorienteringens Uppåt-axel: Upp-axeln som antyds av definitionerna Höger och Framåt.
Du kan komma åt pekaren via SpatialInteractionSourceState::P roperties::TryGetLocation(...)::SourcePointerPose eller SpatialInteractionSourceState::TryGetPointerPose(...)::TryGetInteractionSourcePose.
Egenskaper för kontrollantspecifika indata
För kontrollanter har SpatialInteractionSource en Controller-egenskap med ytterligare funktioner.
- HasThumbstick: Om det är sant har kontrollanten en tumsticka. Granska egenskapen ControllerProperties för SpatialInteractionSourceState för att hämta värdena för tumstick x och y (ThumbstickX och ThumbstickY), samt dess nedtryckta tillstånd (IsThumbstickPressed).
- HasTouchpad: Om det är sant har kontrollanten en pekplatta. Inspektera egenskapen ControllerProperties för SpatialInteractionSourceState för att hämta värdena för pekplattan x och y (TouchpadX och TouchpadY), och för att veta om användaren trycker på plattan (IsTouchpadTouched) och om de trycker ned pekplattan (IsTouchpadPressed).
- SimpleHapticsController: Med SimpleHapticsController-API:et för kontrollanten kan du inspektera kontrollantens haptics-funktioner, och du kan också styra haptisk feedback.
Området för pekplattan och tumstickan är -1 till 1 för båda axlarna (från nederkant till överkant och från vänster till höger). Intervallet för den analoga utlösaren, som nås med egenskapen SpatialInteractionSourceState::SelectPressedValue, har intervallet 0 till 1. Värdet 1 korrelerar med att IsSelectPressed är lika med true. alla andra värden korrelerar med att IsSelectPressed är lika med false.
Ledad handspårning
API:et Windows Mixed Reality ger fullständigt stöd för ledad handspårning, till exempel på HoloLens 2. Ledad handspårning kan användas för att implementera direkt manipulation och indatamodeller för punkt-och-incheckning i dina program. Den kan också användas för att skapa helt anpassade interaktioner.
Handskelett
Ledad handspårning ger ett 25 ledskelett som möjliggör många olika typer av interaktioner. Stommen ger fem leder för index/mitten/ring/små fingrar, fyra leder för tummen och en handledsled. Handledsleden fungerar som bas i hierarkin. Följande bild illustrerar skelettets layout.
I de flesta fall namnges varje led baserat på det ben som det representerar. Eftersom det finns två ben vid varje led använder vi en konvention för att namnge varje led baserat på barnbenet på den platsen. Barnet ben definieras som benet längre från handleden. Till exempel innehåller "Index Proximal"-leden startpositionen för indexproximalt ben och orienteringen för det benet. Den innehåller inte benets slutposition. Om du behöver det får du det från nästa gemensamma i hierarkin, "Index Intermediate"-jointen.
Förutom de 25 hierarkiska lederna tillhandahåller systemet en palmled. Handflatan anses vanligtvis inte vara en del av skelettstrukturen. Den tillhandahålls endast som ett bekvämt sätt att få handens övergripande position och orientering.
Följande information tillhandahålls för varje led:
Name | Beskrivning |
---|---|
Position | 3D-positionen för leden, tillgänglig i alla begärda koordinatsystem. |
Orientering | 3D-orientering av benet, tillgänglig i alla begärda koordinatsystem. |
Radius | Avstånd till hudens yta vid ledpositionen. Användbart för att justera direkta interaktioner eller visualiseringar som förlitar sig på fingerbredd. |
Noggrannhet | Ger en ledtråd om hur säkert systemet känner sig kring den här gemensamma informationen. |
Du kan komma åt handskelettdata via en funktion på SpatialInteractionSourceState. Funktionen kallas TryGetHandPose och returnerar ett objekt med namnet HandPose. Om källan inte stöder ledade händer returnerar den här funktionen null. När du har en HandPose kan du hämta aktuella gemensamma data genom att anropa TryGetJoint, med namnet på det gemensamma du är intresserad av. Data returneras som en JointPose-struktur . Följande kod hämtar pekfingerspetsens position. Variabeln currentState representerar en instans av 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
}
}
Handnät
Det ledade HANDspårnings-API:et möjliggör ett helt deformerbart triangelhandnät. Det här nätet kan deformeras i realtid tillsammans med handskelettet och är användbart för visualisering och avancerade fysiktekniker. För att komma åt handnätet måste du först skapa ett HandMeshObserver-objekt genom att anropa TryCreateHandMeshObserverAsync på SpatialInteractionSource. Detta behöver bara göras en gång per källa, vanligtvis första gången du ser det. Det innebär att du anropar den här funktionen för att skapa ett HandMeshObserver-objekt när en hand kommer in i FOV. Det här är en asynkron funktion, så du måste hantera lite samtidighet här. När det är tillgängligt kan du be HandMeshObserver-objektet om triangelindexbufferten genom att anropa GetTriangleIndices. Index ändrar inte ram över ram, så du kan hämta dem en gång och cachelagrade dem under källans livslängd. Index tillhandahålls i medsols lindningsordning.
Följande kod startar en frånkopplad std::thread för att skapa nätövervakaren och extraherar indexbufferten när nätövervakaren är tillgänglig. Den startar från en variabel som kallas currentState, som är en instans av SpatialInteractionSourceState som representerar en spårad hand.
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();
Att starta en frånkopplad tråd är bara ett alternativ för att hantera asynkrona anrop. Du kan också använda de nya co_await funktioner som stöds av C++/WinRT.
När du har ett HandMeshObserver-objekt bör du hålla fast vid det så länge dess motsvarande SpatialInteractionSource är aktiv. Sedan kan du be den om den senaste hörnbufferten som representerar handen genom att anropa GetVertexStateForPose och skicka in en HandPose-instans som representerar den pose som du vill ha hörn för. Varje hörn i bufferten har en position och en normal. Här är ett exempel på hur du hämtar den aktuella uppsättningen hörn för ett handnät. Som tidigare representerar variabeln currentState en instans av 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
}
}
Till skillnad från skelettfogar kan du inte ange ett koordinatsystem för hörnen i handnäts-API:et. I stället anger HandMeshVertexState det koordinatsystem som hörnen anges i. Du kan sedan hämta en mesh-transformering genom att anropa TryGetTransformTo och ange önskat koordinatsystem. Du måste använda den här nättransformen när du arbetar med hörnen. Den här metoden minskar cpu-omkostnaderna, särskilt om du bara använder nätet i renderingssyfte.
Blick och Checka in sammansatta gester
För program som använder indatamodellen gaze-and-commit, särskilt på HoloLens (första generationen), tillhandahåller Spatial Input-API:et en valfri SpatialGestureRecognizer som kan användas för att aktivera sammansatta gester som bygger på "select"-händelsen. Genom att dirigera interaktioner från SpatialInteractionManager till ett holograms SpatialGestureRecognizer kan appar identifiera tap-, hold-, manipulations- och navigeringshändelser enhetligt över händer, röstenheter och rumsliga indataenheter, utan att behöva hantera pressar och versioner manuellt.
SpatialGestureRecognizer gör bara den minimala tvetydigheten mellan den uppsättning gester som du begär. Om du till exempel bara begär Tryck kan användaren hålla fingret nere så länge de vill och en tryckning inträffar fortfarande. Om du begär både Tryck och Håll, kommer gesten efter ungefär en sekund att hålla ned fingret att höjas till ett undantag och en tryckning kommer inte längre att ske.
Om du vill använda SpatialGestureRecognizer hanterar du SpatialInteractionManagers InteractionDetected-händelse och hämtar SpatialPointerPose som exponeras där. Använd användarens huvudgasstråle från denna pose för att korsa med hologram och ytnät i användarens omgivning för att avgöra vad användaren tänker interagera med. Dirigera sedan SpatialInteraction i händelseargumenten till målhologrammets SpatialGestureRecognizer med hjälp av dess CaptureInteraction-metod . Detta börjar tolka interaktionen enligt SpatialGestureSettings som angetts för den identifieraren vid skapandetillfället – eller av TrySetGestureSettings.
På HoloLens (första generationen) bör interaktioner och gester härleda sin inriktning från användarens huvud blick, i stället för att återge eller interagera på handens plats. När en interaktion har startat kan relativa rörelser i handen användas för att styra gesten, som med manipulations- eller navigeringsgesten.