Handen en bewegingscontrollers in DirectX

Notitie

Dit artikel heeft betrekking op de verouderde systeemeigen WinRT-API's. Voor nieuwe systeemeigen app-projecten raden we u aan de OpenXR-API te gebruiken.

In Windows Mixed Reality wordt invoer van zowel hand- als bewegingscontroller verwerkt via de api's voor ruimtelijke invoer, die te vinden zijn in de naamruimte Windows.UI.Input.Spatial. Hiermee kunt u eenvoudig algemene acties afhandelen, zoals selecteren drukt op dezelfde manier op beide handen en bewegingscontrollers.

Aan de slag

Als u toegang wilt krijgen tot ruimtelijke invoer in Windows Mixed Reality, begint u met de interface SpatialInteractionManager. U kunt deze interface openen door SpatialInteractionManager::GetForCurrentView aan te roepen, meestal ergens tijdens het opstarten van de app.

using namespace winrt::Windows::UI::Input::Spatial;

SpatialInteractionManager interactionManager = SpatialInteractionManager::GetForCurrentView();

De taak van SpatialInteractionManager is om toegang te bieden tot SpatialInteractionSources, die een bron van invoer vertegenwoordigen. Er zijn drie soorten SpatialInteractionSources beschikbaar in het systeem.

  • Hand vertegenwoordigt de gedetecteerde hand van een gebruiker. Handbronnen bieden verschillende functies op basis van het apparaat, variërend van basisbewegingen op HoloLens tot volledig gearticuleerd handtracering op HoloLens 2.
  • Controller vertegenwoordigt een gekoppelde bewegingscontroller. Bewegingscontrollers kunnen verschillende mogelijkheden bieden, bijvoorbeeld Triggers selecteren, Menuknoppen, Grijpknoppen, touchpads en duimsticks.
  • Spraak vertegenwoordigt de trefwoorden die door het spraaksysteem van de gebruiker worden gedetecteerd. Met deze bron wordt bijvoorbeeld een select press and release toegevoegd wanneer de gebruiker 'Selecteren' zegt.

Gegevens per frame voor een bron worden vertegenwoordigd door de interface SpatialInteractionSourceState . Er zijn twee verschillende manieren om toegang te krijgen tot deze gegevens, afhankelijk van of u een gebeurtenisgestuurd of pollingmodel in uw toepassing wilt gebruiken.

Gebeurtenisgestuurde invoer

SpatialInteractionManager biedt een aantal gebeurtenissen waarnaar uw app kan luisteren. Enkele voorbeelden zijn SourcePressed, [SourceReleased en SourceUpdated.

Met de volgende code wordt bijvoorbeeld een gebeurtenis-handler met de naam MyApp::OnSourcePressed gekoppeld aan de gebeurtenis SourcePressed. Hierdoor kan uw app drukken op elk type interactiebron detecteren.

using namespace winrt::Windows::UI::Input::Spatial;

auto interactionManager = SpatialInteractionManager::GetForCurrentView();
interactionManager.SourcePressed({ this, &MyApp::OnSourcePressed });

Deze ingedrukt gebeurtenis wordt asynchroon naar uw app verzonden, samen met de bijbehorende SpatialInteractionSourceState op het moment dat de pers plaatsvond. Uw app of game-engine wil mogelijk meteen beginnen met verwerken of de gebeurtenisgegevens in de wachtrij plaatsen in uw invoerverwerkingsroutine. Hier volgt een gebeurtenis-handler-functie voor de gebeurtenis SourcePressed, waarmee wordt gecontroleerd of op de selectieknop is gedrukt.

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
	}
}

Met de bovenstaande code wordt alleen gecontroleerd op de 'Selecteren'-druk, die overeenkomt met de primaire actie op het apparaat. Voorbeelden hiervan zijn het uitvoeren van een AirTap op HoloLens of het ophalen van de trigger op een bewegingscontroller. Selecteer-drukken geven de intentie van de gebruiker aan om het hologram te activeren waarop de gebruiker zich richt. De gebeurtenis SourcePressed wordt geactiveerd voor een aantal verschillende knoppen en bewegingen, en u kunt andere eigenschappen op de SpatialInteractionSource inspecteren om deze gevallen te testen.

Op polling gebaseerde invoer

U kunt SpatialInteractionManager ook gebruiken om de huidige status van invoer voor elk frame te peilen. Hiervoor roept u GetDetectedSourcesAtTimestamp elk frame aan. Deze functie retourneert een matrix met één SpatialInteractionSourceState voor elke actieve SpatialInteractionSource. Dit betekent één voor elke actieve bewegingscontroller, één voor elke tracked hand en één voor spraak als er onlangs een 'select'-opdracht is uitgeschreven. Vervolgens kunt u de eigenschappen op elke SpatialInteractionSourceState inspecteren om invoer in uw toepassing te stimuleren.

Hier volgt een voorbeeld van hoe u kunt controleren op de actie 'selecteren' met behulp van de polling-methode. De voorspellingsvariabele vertegenwoordigt een HolographicFramePrediction-object , dat kan worden verkregen uit het 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
	}
}

Elke SpatialInteractionSource heeft een id, die u kunt gebruiken om nieuwe bronnen te identificeren en bestaande bronnen van frame tot frame te correleren. Handen krijgen een nieuwe id telkens wanneer ze vertrekken en de FOV invoeren, maar controller-id's blijven statisch voor de duur van de sessie. U kunt de gebeurtenissen op SpatialInteractionManager, zoals SourceDetected en SourceLost, gebruiken om te reageren wanneer handen de weergave van het apparaat binnenkomen of verlaten, of wanneer bewegingscontrollers zijn in- of uitgeschakeld of gekoppeld/losgemaakt.

Voorspelde versus historische poses

GetDetectedSourcesAtTimestamp heeft een tijdstempelparameter. Hiermee kunt u status- en posegegevens aanvragen die voorspeld of historisch zijn, zodat u ruimtelijke interacties kunt correleren met andere invoerbronnen. Als u bijvoorbeeld de positie van de hand weergeeft in het huidige frame, kunt u het voorspelde tijdstempel doorgeven dat door het HolographicFrame is opgegeven. Hierdoor kan het systeem de positie van de hand vooruit voorspellen, zodat deze nauw wordt uitgelijnd met de uitvoer van het gerenderde frame, waardoor de waargenomen latentie wordt geminimaliseerd.

Een dergelijke voorspelde houding produceert echter geen ideale aanwijsstraal voor het richten op een interactiebron. Als er bijvoorbeeld op een bewegingscontrollerknop wordt gedrukt, kan het tot 20 ms duren voordat die gebeurtenis via Bluetooth naar het besturingssysteem opborrelt. Op dezelfde manier kan, nadat een gebruiker een handbeweging heeft uitgevoerd, enige tijd verstrijken voordat het systeem de beweging detecteert en uw app er vervolgens naar peilt. Tegen de tijd dat uw app een statuswijziging opvraagt, hebben de hoofd- en handhoudingen die zijn gebruikt om die interactie te richten, in het verleden plaatsgevonden. Als u zich richt door de tijdstempel van uw huidige HolographicFrame door te geven aan GetDetectedSourcesAtTimestamp, wordt de pose in plaats daarvan voorspeld aan de doelstraal op het moment dat het frame wordt weergegeven, wat in de toekomst meer dan 20 ms kan zijn. Deze toekomstige houding is goed voor het weergeven van de interactiebron, maar vergroot ons tijdsprobleem voor het richten van de interactie, omdat het doel van de gebruiker in het verleden is opgetreden.

Gelukkig bieden de gebeurtenissen SourcePressed, [SourceReleased en SourceUpdated ] de historische status die aan elke invoergebeurtenis is gekoppeld. Dit omvat rechtstreeks de historische hoofd- en handhoudingen die beschikbaar zijn via TryGetPointerPose, samen met een historische tijdstempel die u kunt doorgeven aan andere API's om te correleren met deze gebeurtenis.

Dit leidt tot de volgende best practices bij het weergeven en richten met handen en controllers per frame:

  • Voor hand-/controllerweergave van elk frame moet uw app peilen naar de voorwaarts voorspelde houding van elke interactiebron op de fotontijd van het huidige frame. U kunt alle interactiebronnen peilen door GetDetectedSourcesAtTimestamp elk frame aan te roepen en het voorspelde tijdstempel van HolographicFrame::CurrentPrediction door te geven.
  • Voor hand-/controller gericht op een druk of release, moet uw app ingedrukt/vrijgegeven gebeurtenissen verwerken, raycasting op basis van het historische hoofd of de handhouding voor die gebeurtenis. U krijgt deze doelstraal door de gebeurtenis SourcePressed of SourceReleased te verwerken, de eigenschap State op te halen uit de gebeurtenisargumenten en vervolgens de methode TryGetPointerPose aan te roepen.

Invoereigenschappen voor meerdere apparaten

De SpatialInteractionSource-API ondersteunt controllers en systemen voor handtracering met een breed scala aan mogelijkheden. Een aantal van deze mogelijkheden zijn gebruikelijk voor verschillende apparaattypen. Handtracering en bewegingscontrollers bieden bijvoorbeeld beide een 'select'-actie en een 3D-positie. Waar mogelijk wijst de API deze algemene mogelijkheden toe aan dezelfde eigenschappen op de SpatialInteractionSource. Hierdoor kunnen toepassingen eenvoudiger een breed scala aan invoertypen ondersteunen. In de volgende tabel worden de eigenschappen beschreven die worden ondersteund en hoe deze zich verhouden tot verschillende invoertypen.

Eigenschap Beschrijving HoloLens-bewegingen (1e generatie) Bewegingscontrollers Gelede handen
SpatialInteractionSource::Handness Rechter- of linkerhand / controller. Niet ondersteund Ondersteund Ondersteund
SpatialInteractionSourceState::IsSelectPressed Huidige status van de primaire knop. Luchtkraan Trigger Ontspannen luchtkraan (rechtop knijpen)
SpatialInteractionSourceState::IsGrasped Huidige status van de grijpknop. Niet ondersteund Knop Vastpakken Knijpen of gesloten hand
SpatialInteractionSourceState::IsMenuPressed Huidige status van de menuknop. Niet ondersteund Menuknop Niet ondersteund
SpatialInteractionSourceLocation::Position XYZ-locatie van de hand- of grippositie op de controller. Palmlocatie Positie van griphouding Palmlocatie
SpatialInteractionSourceLocation::Orientation Quaternion die de richting van de hand of greeppositie op de controller vertegenwoordigt. Niet ondersteund Stand van greeppositie Palmstand
SpatialPointerInteractionSourcePose::Position Oorsprong van de aanwijsstraal. Niet ondersteund Ondersteund Ondersteund
SpatialPointerInteractionSourcePose::ForwardDirection Richting van de aanwijzende straal. Niet ondersteund Ondersteund Ondersteund

Sommige van de bovenstaande eigenschappen zijn niet op alle apparaten beschikbaar en de API biedt een methode om dit te testen. U kunt bijvoorbeeld de eigenschap SpatialInteractionSource::IsGraspSupported inspecteren om te bepalen of de bron een grijpactie biedt.

Griphouding versus wijzende houding

Windows Mixed Reality ondersteunt bewegingscontrollers in verschillende vormfactoren. Het ondersteunt ook gearticuleerde handtraceringssystemen. Al deze systemen hebben verschillende relaties tussen de positie van de hand en de natuurlijke 'voorwaartse' richting die apps moeten gebruiken voor het aanwijzen of weergeven van objecten in de hand van de gebruiker. Ter ondersteuning van dit alles zijn er twee soorten 3D-poses beschikbaar voor zowel handtracering als bewegingscontrollers. De eerste is de greephouding, die de positie van de hand van de gebruiker aangeeft. De tweede is wijzende pose, die een aanwijsstraal vertegenwoordigt die afkomstig is van de hand of controller van de gebruiker. Dus als u de hand van de gebruiker of een object in de hand van de gebruiker wilt weergeven, zoals een zwaard of pistool, gebruikt u de greephouding. Als u wilt raycasten vanaf de controller of hand, bijvoorbeeld wanneer de gebruiker **wijst naar de gebruikersinterface, gebruikt u de aanwijzende houding.

U hebt toegang tot de greeppositie via SpatialInteractionSourceState::P roperties::TryGetLocation(...). Dit wordt als volgt gedefinieerd:

  • De grippositie: De palmcentid bij het natuurlijk vasthouden van de controller, links of rechts aangepast om de positie binnen de grip te centreren.
  • De rechteras van de gripstand: Wanneer u uw hand volledig opent om een platte houding van 5 vingers te vormen, de straal die normaal is voor uw palm (vooruit van linkerpalm, achteruit van rechterpalm)
  • De as Vooruit van de grip: Wanneer u uw hand gedeeltelijk sluit (alsof u de controller vasthoudt), wordt de straal die "naar voren" wijst door de buis gevormd door uw niet-duimvingers.
  • De as Omhoog van de gripstand: de as Omhoog die wordt geïmpliceerd door de definities Rechts en Vooruit.

U kunt de aanwijzerpositie openen via SpatialInteractionSourceState::P roperties::TryGetLocation(...)::SourcePointerPose of SpatialInteractionSourceState::TryGetPointerPose(...)::TryGetInteractionSourcePose.

Controllerspecifieke invoereigenschappen

Voor controllers heeft de SpatialInteractionSource een controllereigenschap met extra mogelijkheden.

  • HasThumbstick: Indien waar, heeft de controller een duimstick. Inspecteer de eigenschap ControllerProperties van de SpatialInteractionSourceState om de x- en y-waarden van de duimstick (ThumbstickX en ThumbstickY) te verkrijgen, evenals de ingedrukt status (IsThumbstickPressed).
  • HasTouchpad: Als dit waar is, heeft de controller een touchpad. Inspecteer de eigenschap ControllerProperties van de SpatialInteractionSourceState om de touchpad x- en y-waarden (TouchpadX en TouchpadY) te verkrijgen en om te zien of de gebruiker het pad aanraakt (IsTouchpadTouched) en of ze het touchpad omlaag drukken (IsTouchpadPressed).
  • SimpleHapticsController: Met de SimpleHapticsController-API voor de controller kunt u de haptische mogelijkheden van de controller inspecteren en kunt u ook haptische feedback beheren.

Het bereik voor touchpad en duimstick is -1 tot 1 voor beide assen (van onder naar boven en van links naar rechts). Het bereik voor de analoge trigger, dat wordt geopend met de eigenschap SpatialInteractionSourceState::SelectPressedValue, heeft een bereik van 0 tot 1. Een waarde van 1 correleert met IsSelectPressed dat gelijk is aan waar; een andere waarde correleert met IsSelectPressed die gelijk is aan onwaar.

Gearticuleerde handtracering

De Windows Mixed Reality-API biedt volledige ondersteuning voor het volgen van de hand, bijvoorbeeld op HoloLens 2. Articulated handtracering kan worden gebruikt voor het implementeren van directe manipulatie en point-and-commit-invoermodellen in uw toepassingen. Het kan ook worden gebruikt om volledig aangepaste interacties te maken.

Handskelet

Gelede handtracering biedt een 25 gewrichtsskelet dat veel verschillende soorten interacties mogelijk maakt. Het skelet biedt vijf gewrichten voor de index/midden/ring/kleine vingers, vier gewrichten voor de duim en één polsverbinding. Het polsgewricht dient als basis van de hiërarchie. In de volgende afbeelding ziet u de indeling van het skelet.

Handskelet

In de meeste gevallen wordt elk gewricht benoemd op basis van het bot dat het vertegenwoordigt. Omdat er twee botten in elk gewricht zijn, gebruiken we een conventie voor het benoemen van elk gewricht op basis van het onderliggende bot op die locatie. Het onderliggende bot wordt gedefinieerd als het bot verder van de pols. Het gewricht "Index Proximal" bevat bijvoorbeeld de beginpositie van het proximal indexbeen en de oriëntatie van dat bot. Het bevat niet de eindpositie van het bot. Als u dat nodig hebt, krijgt u deze uit de volgende joint in de hiërarchie, de joint 'Tussenliggende index'.

Naast de 25 hiërarchische gewrichten biedt het systeem een palmverbinding. De palm wordt doorgaans niet beschouwd als onderdeel van de skeletstructuur. Het wordt alleen geleverd als een handige manier om de algemene positie en oriëntatie van de hand te krijgen.

De volgende informatie wordt verstrekt voor elke joint:

Naam Beschrijving
Positie 3D-positie van de joint, beschikbaar in elk gewenst coördinatensysteem.
Afdrukstand 3D-richting van het bot, beschikbaar in elk aangevraagd coördinatensysteem.
Radius Afstand tot oppervlak van de huid in de gewrichtspositie. Handig voor het afstemmen van directe interacties of visualisaties die afhankelijk zijn van vingerbreedte.
Nauwkeurigheid Geeft een hint over hoe zeker het systeem is van de informatie van deze joint.

U hebt toegang tot de handskeletgegevens via een functie op de SpatialInteractionSourceState. De functie heet TryGetHandPose en retourneert een object met de naam HandPose. Als de bron geen ondersteuning biedt voor gearticuleerde handen, retourneert deze functie null. Zodra u een HandPose hebt, kunt u huidige gezamenlijke gegevens ophalen door TryGetJoint aan te roepen met de naam van de joint waarin u geïnteresseerd bent. De gegevens worden geretourneerd als een JointPose-structuur . De volgende code haalt de positie van de wijsvingerpunt op. De variabele currentState vertegenwoordigt een exemplaar van 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
	}
}

Handgaas

De api voor het volgen van gelede hand maakt een volledig vervormbaar driehoekig handnet mogelijk. Dit gaas kan samen met het handskelet in realtime vervormen en is handig voor visualisatie en geavanceerde natuurkundetechnieken. Voor toegang tot de hand mesh moet u eerst een HandMeshObserver-object maken door TryCreateHandMeshObserverAsync aan te roepen op de SpatialInteractionSource. Dit hoeft slechts eenmaal per bron te worden gedaan, meestal de eerste keer dat u het ziet. Dit betekent dat u deze functie aanroept om een HandMeshObserver-object te maken wanneer een hand de FOV binnenkomt. Dit is een asynchrone functie, dus u hebt hier te maken met een beetje gelijktijdigheid. Zodra deze beschikbaar is, kunt u het object HandMeshObserver vragen om de driehoekindexbuffer door GetTriangleIndices aan te roepen. Indexen veranderen niet van frame over frame, dus u kunt deze eenmaal ophalen en ze opslaan voor de levensduur van de bron. Indexen worden weergegeven in wikkelingsvolgorde met de klok mee.

Met de volgende code wordt een losgekoppelde std::thread gemaakt om de mesh-waarnemer te maken en wordt de indexbuffer geëxtraheerd zodra de mesh-waarnemer beschikbaar is. Het begint met een variabele met de naam currentState. Dit is een exemplaar van SpatialInteractionSourceState dat een bijgehouden hand vertegenwoordigt.

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();

Het starten van een losgekoppelde thread is slechts één optie voor het afhandelen van asynchrone aanroepen. U kunt ook de nieuwe co_await-functionaliteit gebruiken die wordt ondersteund door C++/WinRT.

Zodra u een HandMeshObserver-object hebt, moet u dit vasthouden voor de duur dat de bijbehorende SpatialInteractionSource actief is. Vervolgens kunt u elk frame vragen om de meest recente hoekpuntbuffer die de hand vertegenwoordigt door GetVertexStateForPose aan te roepen en door te geven in een HandPose-exemplaar dat de pose vertegenwoordigt waarvoor u hoekpunten wilt gebruiken. Elk hoekpunt in de buffer heeft een positie en een normale. Hier volgt een voorbeeld van hoe u de huidige set hoekpunten voor een handgaas kunt ophalen. Net als voorheen vertegenwoordigt de variabele currentState een exemplaar van 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
    }
}

In tegenstelling tot skeletverbindingen kunt u met de hand mesh-API geen coördinatensysteem opgeven voor de hoekpunten. In plaats daarvan geeft de HandMeshVertexState het coördinatensysteem op waarin de hoekpunten worden geleverd. U kunt vervolgens een mesh-transformatie krijgen door TryGetTransformTo aan te roepen en het gewenste coördinatensysteem op te geven. U moet deze mesh-transformatie gebruiken wanneer u met de hoekpunten werkt. Deze aanpak vermindert de CPU-overhead, met name als u de mesh alleen gebruikt voor renderingdoeleinden.

Samengestelde bewegingen staren en doorvoeren

Voor toepassingen die gebruikmaken van het gaze-and-commit-invoermodel, met name op HoloLens (eerste generatie), biedt de Spatial Input-API een optionele SpatialGestureRecognizer die kan worden gebruikt om samengestelde bewegingen in te schakelen die zijn gebouwd op de gebeurtenis 'selecteren'. Door interacties van de SpatialInteractionManager te routeren naar de SpatialGestureRecognizer van een hologram, kunnen apps gebeurtenissen voor tikken, vasthouden, manipuleren en navigatie op uniforme wijze detecteren tussen handen, spraak en ruimtelijke invoerapparaten, zonder dat ze drukken en releases handmatig hoeven te verwerken.

SpatialGestureRecognizer doet alleen de minimale ondubbelzinnigheid tussen de reeks bewegingen die u aanvraagt. Als u bijvoorbeeld alleen Tik aanvraagt, kan de gebruiker zijn of haar vinger zo lang als hij of zij wil ingedrukt houden en wordt er nog steeds getikt. Als u zowel Tikken als Vasthouden aanvraagt, wordt na ongeveer een seconde het ingedrukt houden van de vinger de beweging bevorderd tot een Wachtstand en wordt er niet meer getikt.

Als u SpatialGestureRecognizer wilt gebruiken, verwerkt u de gebeurtenis InteractionDetected van SpatialInteractionManager en pakt u de SpatialPointerPose op die locatie. Gebruik de blikstraal van de gebruiker vanuit deze houding om de hologrammen en oppervlaktegaas in de omgeving van de gebruiker te snijden om te bepalen waarmee de gebruiker van plan is te communiceren. Vervolgens routeert u de SpatialInteraction in de gebeurtenisargumenten naar de SpatialGestureRecognizer van het doel hologram met behulp van de methode CaptureInteraction . Hiermee begint de interpretatie van die interactie volgens de SpatialGestureSettings die is ingesteld op die recognizer tijdens het maken - of door TrySetGestureSettings.

Op HoloLens (eerste generatie) moeten interacties en gebaren hun gerichtheid afleiden van de blik van de gebruiker, in plaats van het weergeven of communiceren op de locatie van de hand. Zodra een interactie is gestart, kunnen relatieve bewegingen van de hand worden gebruikt om het gebaar te besturen, zoals bij de manipulatie- of navigatiebeweging.

Zie ook