MRTK3의 손 추적

개요

Unity 입력 시스템에서 아직 기본적으로 처리하지 않는 입력 데이터의 유일한 부분 중 하나인 연결된 손 관절 데이터는 하위 시스템에서 처리합니다.

참고

MRTK3 하위 시스템에 익숙하지 않은 경우 MRTK 2.x 서비스와의 차이점에 대해 자세히 알아보려면 MRTK3 하위 시스템 아키텍처 설명서를 통해 철학과 디자인에 대해 자세히 알아보세요.

하위 시스템은 여러 원본의 손 관절 데이터를 수집하여 디바이스 및 시뮬레이션 컨텍스트에서 작동하는 중앙 API로 집계합니다. 아래 하위 시스템은 HandsSubsystem의 구현입니다.

  • OpenXRHandsSubsystem은 OpenXR 플러그 인에서 직접 손 데이터를 받습니다.
  • XRSDKHandsSubsystem은 Unity의 XR SDK 추상화 레이어에서 손 데이터를 받습니다(즉, OpenXR 또는 다른 원본에서 데이터를 소싱할 수 있음).
  • SyntheticHandsSubsystem은 시스템에서 들어오는 입력 작업(예: devicePosition, deviceRotation 등)에 따라 가짜 손 관절을 합성합니다. 이 하위 시스템은 편집기에서 입력 시뮬레이션을 사용할 때 표시되는 관절을 제공합니다.

HandsAggregatorSubsystem에서 이러한 HandsSubsystems를 쿼리하여 모든 손 데이터 원본을 중앙 API로 결합합니다.

중요

손 관절 데이터를 직접 쿼리할 때마다 항상 개별 HandsSubsystem이 아닌 Aggregator에서 쿼리하는 것이 좋습니다. 이렇게 하면 시뮬레이션된 데이터를 비롯한 모든 손 데이터 원본에 대해 코드가 작동합니다.

집계 및 손 하위 시스템은 들어오는 손 데이터 요청을 지연적으로 평가합니다. 손 데이터는 "클라이언트" 스크립트가 요청할 때까지 쿼리되지 않습니다. 앱이 개별 관절만 요청하는 경우 손 하위 시스템은 지연적으로 평가하며 기본 API에서 단일 관절만 쿼리합니다. 또한 "클라이언트"가 전체 손에 해당하는 관절 데이터를 요청하는 경우 동일한 프레임 내의 후속 호출은 동일한 데이터를 재사용하여 동일한 프레임 내에서 많은 관절을 쿼리하는 데 드는 비용을 줄입니다. 새 프레임마다 캐시가 변경되고 플러시되며 후속 호출이 캐시를 다시 채우기 시작합니다.

따라서 애플리케이션을 프로파일링할 때 프레임의 첫 번째 관절 쿼리가 후속 쿼리보다 더 많은 시간이 소요될 수 있습니다. 이는 첫 번째 쿼리와 관련된 분할상환 비용과 후속 "캐시 적중"의 상대적 성능 때문입니다.

손가락 모으기 특징

집계는 각 특정 손 하위 시스템에서 쿼리하는 관절 데이터를 기반으로 손가락 모으기 제스처와 관련된 여러 측정값을 계산합니다. 이러한 측정값은 집계 하위 시스템 구성에서 구성됩니다.

Hands aggregator subsystem configuration

Pinch Open ThresholdPinch Closed Threshold는 손가락 모으기 진행률을 정규화하는 데 사용되는 엄지손가락과 집게손가락 사이의 절대 세계 거리를 제어합니다. 거리가 닫힌 임계값과 같으면 손가락 모으기 진행률이 1.0이고 거리가 열린 임계값과 같으면 0.0이 됩니다. (이러한 임계값은 현재 세계 단위로 되어 있지만 곧 사용자의 손 크기로 정규화될 예정입니다.)

Hand Raise Camera FOV는 사용자 보기의 중앙과 얼마나 가까이 있을 때 손가락 모으기에 유효한 것으로 간주되어야 하는지 제어합니다. Hand Facing Away Tolerance는 사용자의 손 회전을 측정하기 위한 허용 오차를 제어하여 사용자의 손이 뒤집어지는 시기를 결정합니다.

// Get a reference to the aggregator.
var aggregator = XRSubsystemHelpers.GetFirstRunningSubsystem<HandsAggregatorSubsystem>();
// Wait until an aggregator is available.
IEnumerator EnableWhenSubsystemAvailable()
{
    yield return new WaitUntil(() => XRSubsystemHelpers.GetFirstRunningSubsystem<HandsAggregatorSubsystem>() != null);
    GoAhead();
}
// Get a single joint (Index tip, on left hand, for example)
bool jointIsValid = aggregator.TryGetJoint(TrackedHandJoint.IndexTip, XRNode.LeftHand, out HandJointPose jointPose);
// Get an entire hand's worth of joints from the left hand.
bool allJointsAreValid = aggregator.TryGetEntireHand(XRNode.LeftHand, out IReadOnlyList<HandJointPose> joints)
// Check whether the user's left hand is facing away (commonly used to check "aim" intent)
// This is adjustable with the HandFacingAwayTolerance option in the aggregator configuration.
// handIsValid represents whether there was valid hand data in the first place!
bool handIsValid = aggregator.TryGetPalmFacingAway(XRNode.LeftHand, out bool isLeftPalmFacingAway)
// Query pinch characteristics from the left hand.
// pinchAmount is [0,1], normalized to the open/closed thresholds specified in the aggregator configuration.
// isReadyToPinch is adjusted with the HandRaiseCameraFOV and HandFacingAwayTolerance settings in the configuration.
bool handIsValid = aggregator.TryGetPinchProgress(XRNode.LeftHand, out bool isReadyToPinch, out bool isPinching, out float pinchAmount)