Händer och rörelsekontrollanter 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 spatial indata som finns i Windows. UI. Input.Spatial namespace. 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örelsekontrollanter kan erbjuda olika funktioner, till exempel Välj utlösare, Menyknappar, Grip-knappar, pekplattor och tumpinnar.
  • Röst representerar användarens rösttalande system-identifierade 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 den renderade bildrutans utdata, 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 genom Bluetooth till operativsystemet. När en användare gör en handgest kan en viss tid passera 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 in sig på den interaktionen som faktiskt har skett 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ålbilden 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 som representerar hand- eller grepppositionens orientering på styrenheten. Stöds inte Orientering av greppposition Palmorientering
SpatialPointerInteractionSourcePose::Position Ursprunget till den pekande strålen. 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 är 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.

Greppställning jämfört med pekposition

Windows Mixed Reality stöder rörelsestyrenheter i olika formfaktorer. Det stöder också ledade handspårningssystem. Alla dessa system har olika relationer mellan handpositionen och den naturliga "framåtriktade" riktning som appar bör 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 rendera användarens hand eller ett objekt som hålls i användarens hand, till exempel ett svärd eller en pistol, använder du greppställningen. Om du vill strålkasta från styrenheten eller handen, till exempel när användaren är **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:

  • Greppposition: Handflatan centroid när du håller styrenheten naturligt, justeras vänster eller höger för att centrera positionen inom greppet.
  • Grepporienteringens högra axel: När du helt öppnar handen för att bilda en platt 5-finger pose, strålen som är normal för handflatan (framåt från vänster handflata, bakåt från höger handflata)
  • Grepporienteringens framåtaxel: När du stänger handen delvis (som om du håller styrenheten), strålen som pekar "framåt" genom röret som bildas av dina icke-tumme fingrar.
  • Grepporienteringens Upp-axel: Uppåtaxeln som definieras av definitionerna Höger och Framåt.

Du kan komma åt pekareställningen via SpatialInteractionSourceState::P roperties::TryGetLocation(...)::SourcePointerPose eller SpatialInteractionSourceState::TryGetPointerPose(...)::TryGetInteractionSourcePose.

Egenskaper för styrenhetsspecifika 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 pressade tillstånd (IsThumbstickPressed).
  • HasTouchpad: Om det är sant har kontrollanten en pekplatta. Granska egenskapen ControllerProperties för SpatialInteractionSourceState för att hämta värdena för touchpad x och y (TouchpadX och TouchpadY) och för att veta om användaren rör vid plattan (IsTouchpadTouched) och om de trycker ned pekplattan (IsTouchpadPressed).
  • SimpleHapticsController: Med SimpleHapticsController-API:et för kontrollanten kan du inspektera haptics-funktionerna i kontrollanten, och du kan även styra haptisk feedback.

Intervallet för pekplatta och tumsticka ä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 ett intervall på 0 till 1. Värdet 1 korrelerar med att IsSelectPressed är lika med sant. andra värden korrelerar med att IsSelectPressed är lika med false.

Ledad handspårning

WINDOWS MIXED REALITY-API:et 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 manipulering 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.

Hand Skeleton

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. Det underordnade benet definieras som benet längre från handleden. Till exempel innehåller "Index Proximal"-leden startpositionen för indexet proximalt ben och orienteringen av det benet. Den innehåller inte benets slutposition. Om du behöver det får du det från nästa joint i hierarkin, "Index Intermediate"-jointen.

Förutom de 25 hierarkiska lederna tillhandahåller systemet en palmfog. Handflatan anses vanligtvis inte vara en del av skelettstrukturen. Det tillhandahålls endast som ett bekvämt sätt att få handens övergripande position och orientering.

Följande information ges för varje led:

Name Beskrivning
Position 3D-position för leden, tillgänglig i alla begärda koordinatsystem.
Orientering 3D-orientering av benet, tillgängligt 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 om den här jointens information.

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å den 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. Detta nät 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 TryCreateHandMeshObserverAsyncSpatialInteractionSource. 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 anger 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 cachelagrat dem under källans livslängd. Index tillhandahålls i medsols lindningsordning.

Följande kod snurrar upp en fristående std::thread för att skapa mesh-övervakaren och extraherar indexbufferten när nätobservatören är tillgänglig. Den utgår 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 varje bildruta 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 använda 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. Precis 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 tillåter inte HAND Mesh-API:et att du anger ett koordinatsystem för hörnen. I stället anger HandMeshVertexState det koordinatsystem som hörnen finns 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ättransformeringen när du arbetar med hörnen. Den här metoden minskar cpu-belastningen, särskilt om du bara använder nätet i renderingssyfte.

Blick och checka in sammansatta gester

För program som använder gaze-and-commit-indatamodellen, 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 Tryck kommer fortfarande att ske. Om du begär både Tryck och Håll upp, efter ungefär en sekund av att hålla ned fingret, höjs gesten upp till ett undantag och en tryckning sker inte längre.

Om du vill använda SpatialGestureRecognizer hanterar du SpatialInteractionManagers InteractionDetected-händelse och hämtar SpatialPointerPose som exponeras där. Använd användarens huvudblixtstråle från den här posen för att korsa med hologram och ytnät i användarens omgivning för att avgöra vad användaren avser att 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 på den identifieraren när den skapades – eller av TrySetGestureSettings.

På HoloLens (första generationen) bör interaktioner och gester härleda deras mål från användarens huvud blick, i stället för att återge eller interagera på handens plats. När en interaktion har startats kan relativa rörelser i handen användas för att styra gesten, som med gesten Manipulation eller Navigering.

Se även