1. 소개
이 API는 사용자의 손 각 골격 관절의 자세를 노출합니다。 이는 제스처 감지 또는 VR 시나리오에서 손 모델을 렌더링하는 데 사용할 수 있습니다。
2. 초기화
애플리케이션이 세션 동안 관절화된 손 자세 정보를 확인하려면, 세션은 적절한 기능 서술자로 요청되어야 합니다。 문자열 "hand-tracking"은 이 모듈에 의해 관절화된 손 추적을 위한 새로운 유효한 기능 서술자로 도입됩니다。
"hand-tracking" 기능 서술자는 해당
XRSession의
XR 장치가 물리적 손 입력 소스를 가지고 있고 그것이 손 추적을 지원할 때에만 부여되어야 합니다。
사용자 에이전트는 이 기능 서술자에 기반하여 손 기반 XRInputSources에
대한 지원을 제한할 수 있습니다。
참고: 이는 XRSession가
"hand-tracking" 기능 서술자를
요청하지 않는 경우, 사용자 에이전트가 손 기반 입력 컨트롤러에 대한 지원을 하지 않기로 선택할 수 있음을 의미합니다。
3. 물리적 손 입력 소스
XRInputSource
는 물리적
손 입력 소스이며, 이는 물리적 손을 추적하는 경우를 말합니다. 물리적 손 입력
소스가 손 추적을 지원한다는 것은 하나 이상의
골격 관절
(이 명세에 정의됨)의 포즈를 보고할 수 있다는 뜻입니다.
물리적
손 입력 소스는 반드시 입력 프로파일 이름으로
"generic-hand-select" 를 자체 profiles에 포함해야 합니다.
많은 물리적 손 입력 소스에서는 주요 동작에 사용되는 제스처와 스퀴즈 동작이 겹칠 수 있습니다。예를 들어, 핀치 제스처는 가까운 객체와 상호 작용할 때는 "select"를, 먼 객체와 상호 작용할 때는 "squeeze" 이벤트를 나타낼 수 있습니다。컨텐츠가 이들을 독립적인 이벤트로 가정할 수 있기 때문에, 사용자 에이전트는 스퀴즈 동작을 기본 스퀴즈 동작으로 노출하는 대신 추가적인 "grasp 버튼"으로 노출하고 "generic-hand-select-grasp" 프로필에서 파생된 입력 프로필을 사용할 수 있습니다。
3.1. XRInputSource
partial interface XRInputSource { [SameObject ]readonly attribute XRHand ?hand ; };
hand 속성은 물리적 손 입력
소스가 손 추적을 지원하는 경우 XRHand 객체이며,
기본 손 추적 기능에 접근할 수 있게 합니다。 hand
의 입력
소스는 this로 설정됩니다。
XRInputSource
가 속한 XRSession
이 다음 "hand-tracking" 기능
설명자로 요청되지 않았다면, hand
는 반드시 null이어야 합니다.
3.2. 골격 관절
물리적 손 입력 소스는 여러 개의 골격 관절로 구성됩니다。
골격 관절은 특정 손에 대해 골격 관절 이름으로 고유하게 식별할
수 있으며, 이는 XRHandJoint
타입의 열거형입니다。
골격 관절 에는 관절 이름의 바탕이 되고 -Z축의 방향을 정하는 데 사용되는 연관 뼈 가 있을 수 있습니다. 골격 관절 의 연관 뼈 는 손끝을 향해 이동할 때 그 관절 다음에 오는 뼈입니다. 끝 관절과 손목 관절에는 연관 뼈 가 없습니다.
골격 관절에는 그 중심에 배치된 구의 반지름인 반지름이 있으며, 이는 손 양측의 피부에 대략적으로 닿도록 합니다。“끝(tip)” 관절은 끝부분과의 충돌이 작동하도록 적절한 0이 아닌 반지름을 가져야 합니다。구현체는 끝 관절이 0이 아닌 반지름의 구 형태를 가질 수 있도록 원점을 오프셋할 수 있습니다。
이 관절 목록은 다음의 골격 관절과 그 순서를 정의합니다:
| 골격 관절 | 골격 관절 이름 | 인덱스 | |
|---|---|---|---|
| 손목 | wrist
| 0 | |
| 엄지 | 중수골 | thumb-metacarpal
| 1 |
| 근위 지골 | thumb-phalanx-proximal
| 2 | |
| 원위 지골 | thumb-phalanx-distal
| 3 | |
| 끝 | thumb-tip
| 4 | |
| 검지 | 중수골 | index-finger-metacarpal
| 5 |
| 근위 지골 | index-finger-phalanx-proximal
| 6 | |
| 중간 지골 | index-finger-phalanx-intermediate
| 7 | |
| 원위 지골 | index-finger-phalanx-distal
| 8 | |
| 끝 | index-finger-tip
| 9 | |
| 중지 | 중수골 | middle-finger-metacarpal
| 10 |
| 근위 지골 | middle-finger-phalanx-proximal
| 11 | |
| 중간 지골 | middle-finger-phalanx-intermediate
| 12 | |
| 원위 지골 | middle-finger-phalanx-distal
| 13 | |
| 끝 | middle-finger-tip
| 14 | |
| 약지 | 중수골 | ring-finger-metacarpal
| 15 |
| 근위 지골 | ring-finger-phalanx-proximal
| 16 | |
| 중간 지골 | ring-finger-phalanx-intermediate
| 17 | |
| 원위 지골 | ring-finger-phalanx-distal
| 18 | |
| 끝 | ring-finger-tip
| 19 | |
| 새끼손가락 | 중수골 | pinky-finger-metacarpal
| 20 |
| 근위 지골 | pinky-finger-phalanx-proximal
| 21 | |
| 중간 지골 | pinky-finger-phalanx-intermediate
| 22 | |
| 원위 지골 | pinky-finger-phalanx-distal
| 23 | |
| 끝 | pinky-finger-tip
| 24 | |
3.3. XRHand
enum {XRHandJoint ,"wrist" ,"thumb-metacarpal" ,"thumb-phalanx-proximal" ,"thumb-phalanx-distal" ,"thumb-tip" ,"index-finger-metacarpal" ,"index-finger-phalanx-proximal" ,"index-finger-phalanx-intermediate" ,"index-finger-phalanx-distal" ,"index-finger-tip" ,"middle-finger-metacarpal" ,"middle-finger-phalanx-proximal" ,"middle-finger-phalanx-intermediate" ,"middle-finger-phalanx-distal" ,"middle-finger-tip" ,"ring-finger-metacarpal" ,"ring-finger-phalanx-proximal" ,"ring-finger-phalanx-intermediate" ,"ring-finger-phalanx-distal" ,"ring-finger-tip" ,"pinky-finger-metacarpal" ,"pinky-finger-phalanx-proximal" ,"pinky-finger-phalanx-intermediate" ,"pinky-finger-phalanx-distal" }; ["pinky-finger-tip" Exposed =Window ]interface {XRHand iterable <XRHandJoint ,XRJointSpace >;readonly attribute unsigned long size ;XRJointSpace (get XRHandJoint ); };key
The XRHandJoint
enum defines the various joints that each XRHand MUST contain。
모든 XRHand에는 그것이 추적하는
입력
소스가 연결되어 있으며, 이는 추적 대상인 물리적 손 입력 소스입니다。
참고: handedness 속성은 XRInputSource가
어떤 손과 연결되어 있는지를 설명합니다(있는 경우)。
XRHand 객체는 [[joints]] 내부 슬롯을 가지며,
이는 키가 XRHandJoint
타입이고 값이 XRJointSpace
타입인 쌍들의 정렬된 맵입니다。
내부 슬롯 [[joints]]의
순서는 골격 관절 아래의 관절 목록에 의해 주어집니다。
[[joints]]는
세션 동안 변경되어서는 안 됩니다。
XRHand 객체에 대해 키가
XRHandJoint이고
값이 해당 XRHandJoint에 대응하는 XRJointSpace인
값 쌍들의
목록이며, 그 순서는 골격 관절 아래의 관절 목록에 따릅니다。
개별 장치가 이 명세서에 정의된 관절을 지원하지 않는 경우에도, 그것을 에뮬레이트해야 합니다。
The size attribute MUST return the number 25。
get(jointName) 메서드는 XRHand의 this에서 호출될 때 다음
단계를 실행해야 합니다:
-
joints를 this의
[[joints]]내부 슬롯의 값으로 둡니다。 -
joints[jointName]를 반환합니다。(알 수 없는 jointName의 경우
undefined를 반환함을 의미합니다。)
3.4. XRJointSpace
[Exposed =Window ]interface :XRJointSpace XRSpace {readonly attribute XRHandJoint ; };jointName
XRJointSpace의
네이티브 원점은 해당 관절의 위치 및 방향이다.
XRJointSpace의
네이티브 원점은 동일 손의 모든 XRJointSpace의
네이티브 원점이 보고되는 경우에만 보고될 수 있다. 손이 부분적으로 가려질 때는, UA는 가려진 관절을 에뮬레이션하거나 모든 관절에 null
포즈를 리포트해야 한다.
참고: 포즈를 얻을 때 손 전체를 얻거나 아무것도 얻지 못하게 됨을 의미한다.
이로 인해 다지/소지 손을 충실히 표현할 수 없으나, 핑거프린팅 우려로 인해 별도 opt-in이 필요할 수 있다. 더 자세한 사항은 Issue 11 참조.
네이티브 원점의 -Y 방향은 손바닥에서 바깥쪽 피부에 수직, -Z 방향은 손목에서 손 끝 방향 뼈를
따라가야 한다.
끝(tip) 골격 관절의 경우 연관 뼈가 없으므로,
-Z 방향은 연결된 원위(distal) 관절과 같아야 한다. 즉, 직전 뼈 방향과 같다. 손목 골격 관절의 -Z 방향은 손바닥 중심을 향해야 한다.
모든 XRJointSpace는
자신을 생성한 XRHand와 연결된
hand를 가진다.
jointName은 추적하는 관절의 이름을 반환한다.
모든 XRJointSpace는
대응되는 골격 관절과 연결된 joint를 가진다. 이 joint는 jointName에 해당하는 골격 관절이다.
4. 프레임 루프
4.1. XR프레임(XRFrame)
partial interface XRFrame {XRJointPose ?getJointPose (XRJointSpace ,joint XRSpace );baseSpace boolean fillJointRadii (sequence <XRJointSpace >,jointSpaces Float32Array );radii boolean fillPoses (sequence <XRSpace >,spaces XRSpace ,baseSpace Float32Array ); };transforms
getJointPose(XRJointSpace joint, XRSpace baseSpace)
메서드는 해당 joint의 baseSpace에 대한 포즈를 XRJointPose로
반환하며, XRFrame의
시간을 기준으로 한다.
이 메서드가 호출될 때, UA는 다음을 실행해야 한다:
-
frame을 this로 한다.
-
session을 frame의
session객체로 한다. -
frame의 active가
false면InvalidStateError발생 후 종료한다. -
baseSpace나 joint의 session이 this
session과 다르면InvalidStateError발생 후 종료한다. -
pose를 session의 relevant realm에 새로운
XRJointPose객체로 생성한다. -
frame이 나타내는 시점에서 joint의 baseSpace에서의 포즈 초기화를 pose에 force emulation=false로 해서 채운다.
-
pose가
null이면null을 리턴한다. -
pose를 반환한다.
fillJointRadii(sequence<XRJointSpace> jointSpaces, Float32Array radii)
메서드는 jointSpaces에 해당하는 radii에 반지름 값을 쓰며, 모든 공간이 유효한 포즈를 갖는지 boolean으로 반환한다.
이 메서드가 XRFrame
frame에서 호출될 때 UA는 다음을 반드시 수행해야 한다:
-
frame을 this로 한다.
-
session을 frame의
session객체로 한다. -
frame의 active가
false면InvalidStateError발생 후 종료. -
jointSpaces 내 각 joint에 대해:
-
joint의 session이 session과 다르면
InvalidStateError발생 후 종료.
-
-
jointSpaces 길이가 radii의 요소 개수보다 크면
TypeError발생 후 종료. -
offset을
0으로 하는 새 숫자 변수로 둔다. -
allValid을
true로 한다. -
jointSpaces 내 모든 joint에 대해:
-
allValid을 반환.
참고: UA가 동일 XRHand에 속한 공간 중 어느
하나도 포즈 결정을 못하면, 그 XRHand의 모든 공간은 포즈를 못
가져야 한다.
fillPoses(sequence<XRSpace> spaces, XRSpace baseSpace, Float32Array transforms)
메서드는 spaces 각각의 baseSpace 기준 포즈 행렬을 transforms에 채우고, 모든 공간이 유효한 포즈를
가지는지 boolean으로 반환한다.
이 메서드가 XRFrame
frame에서 호출되면, UA는 다음을 수행해야 한다:
-
frame을 this로 한다.
-
session을 frame의
session객체로 한다. -
frame의 active가
false면InvalidStateError발생 후 종료. -
spaces 시퀀스 내 각 space에 대해:
-
space의 session이 session과 다르면
InvalidStateError발생 후 종료.
-
-
baseSpace의 session이 session과 다르면
InvalidStateError발생 후 종료. -
spaces 길이 *
16이 transforms의 요소 개수보다 크면TypeError발생 후 종료. -
offset을
0으로 하는 새 숫자 변수로 둠. -
pose를 다음 기준으로 초기화:
-
fillPoses()가 이전에 호출된 적 있으면 UA가 객체 재사용 허용 - pose는 앞서 호출에 사용된 객체 동일하게 둔다.
- 아니면
- pose를 session의 relevant realm에 새로운
XRPose객체로 생성.
-
-
allValid을
true로 한다. -
spaces 내 각 space에 대해:
-
allValid을 반환.
참고: 동일 XRHand 소속 공간 중 어떤
것이든 포즈 초기화 시 null을 반환하면, 해당 XRHand의 모든 공간이 포즈 초기화 시 null을 반환해야 한다.
4.2. XR관절포즈(XRJointPose)
XRJointPose는
XRPose이며,
표현하는 골격 관절의 크기 추가 정보를
가진다.
[Exposed =Window ]interface :XRJointPose XRPose {readonly attribute float radius ; };
radius 속성은 해당 골격 관절의 반지름을 미터 단위로 반환한다.
UA는 XR 장치가 이 값을 일반적으로, 또는 현재 애니메이션 프레임에서(예: 해당 골격 관절이 일부 가려짐) 판별이 불가할 때
radius를
반드시 에뮬레이션 값으로 설정해야 한다.
5. 프라이버시 및 보안 고려사항
WebXR Hand Input API는 중요한 프라이버시 리스크를 수반하는 강력한 기능이다.이 기능이 새로운 센서 데이터를 반환하므로, UA는 세션 생성 시점에 반드시 사용자에게 명시적 동의를 구해야 한다.
이 API로부터 반환되는 데이터는 개인을 식별할 수 있을 정도로 구체적이어서는 안 된다. 하드웨어가 너무 정밀한 데이터를 리턴할 경우, UA는 WebXR Hand Input API를 통해 이 데이터를 노출하기 전 반드시 익명화해야 한다.
이 API는 "immersive-vr"
또는 "immersive-ar"
XRSessionMode로 생성된 XRSession에서만 지원되어야 한다.
"inline"
세션은 이 API를 지원해서는 안 된다.
-
노이징보다는 반올림을 권장한다.
-
UA가 반올림을 사용할 때 각 관절을 독립적으로 반올림하면 안 된다. 올바른 방법은 각 손을 정적 손 모델에 매핑시키는 것이다.
-
노이징을 쓰는 경우, 노이즈 데이터가 시간이 지나도 어떤 정보도 누설하지 않아야 한다:
-
익명화는 신뢰된 환경에서 이루어져야 한다.
변경 사항
2020년 10월 22일 첫 공개 워킹드래프트에서의 변경 사항
-
grasp 프로필 언급 (GitHub #68)
-
상수에서 enum으로 변경 + XRHand를 map으로 변경 (GitHub #71)
-
보안 섹션에 추가 설명 (GitHub #87)
-
hand 속성을 sameobject로 표시 + 설명 노트 추가 (GitHub #93)
-
tip에 0이 아닌 반지름 명시 (GitHub #111)