1. 简介
该 API 暴露了用户每只手的骨骼关节的姿势。这可用于手势检测或在 VR 场景中渲染手部模型。
2. 初始化
如果应用希望在会话期间查看手部关节姿态信息,必须通过合适的功能描述符请求会话。本模块引入了字符串 "hand-tracking" 作为用于手部关节追踪的新合法功能描述符。
只有在 XRSession
的XR 设备拥有物理手部输入源并支持手部追踪时,才应授予" hand-tracking " 功能描述符。
用户代理可以基于该功能描述符对基于手的XRInputSources
的支持进行限制。
注意: 这意味着如果 XRSession
未请求" hand-tracking " 功能描述符,用户代理可以选择不支持基于手的输入控制器。
3. 物理手部输入源
XRInputSource
如果追踪物理手部,则为物理手部输入源。物理手部输入源支持手部追踪,只要它能报告本规范定义的一个或多个骨骼关节的姿态。
物理手部输入源必须在其profiles
中包含 "generic-hand-select" 的输入配置文件名。
对于许多物理手部输入源,主操作与捏紧操作的手势可能存在重叠。例如,捏合手势可根据与物体的远近,既表示 "选择" 也表示 "捏紧" 事件。由于内容可能假定它们是独立事件,用户代理可以选择将捏紧操作作为额外的"抓取按钮"(而非主捏紧操作)暴露,使用源自"generic-hand-select-grasp"配置文件的输入配置文件。
3.1. XRInputSource
partial interface XRInputSource { [SameObject ]readonly attribute XRHand ?hand ; };
hand 属性在支持手部追踪的物理手部输入源上,将是一个
XRHand
对象,用于访问底层手部追踪能力。hand
的输入源被设置为该对象。
如果XRInputSource
属于未请求 "hand-tracking" 功能描述符
的XRSession,则hand
必须为 null。
3.2. 骨骼关节
物理手部输入源由许多骨骼关节组成。
某只手的骨骼关节可由骨骼关节名(enum 类型为
XRHandJoint)唯一标识。
一个骨骼关节可能有一个关联骨骼,用于确定其
-Z 轴方向。骨骼关节的关联骨骼是从关节向指尖方向的下一个骨骼。指尖和手腕关节没有关联骨骼。
一个骨骼关节有一个半径,即在关节中心放置的球体半径,使其大致接触手部两侧的皮肤。Tip 骨骼关节应有适当的非零半径,以支持指尖碰撞。实现时可以调整 Tip 关节的原点,以保证其有球形体且半径非零。
该关节列表按如下顺序定义了骨骼关节及其顺序:
| 骨骼关节 | 骨骼关节名 | 序号 | |
|---|---|---|---|
| 手腕 | 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
XRHandJoint
枚举定义了每个XRHand必须包含的各个关节。
每个 XRHand 都有一个
输入源,即其追踪的物理手部输入源。
注意: handedness 属性用于描述 XRInputSource
关联的是哪只手(如有关联)。
XRHand对象有一个
[[joints]]内部槽,
它是以 XRHandJoint
为 key、XRJointSpace
为 value 的
有序映射。
[[joints]]
内部槽的顺序由 关节列表(见骨骼关节)给出。
[[joints]]
在会话期间不得发生变化。
如果某设备不支持本规范定义的某个关节,则必须进行模拟。
size 属性必须返回 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
都在报告时才被报告。当一只手部分被遮挡时,用户代理必须模拟被遮挡关节或将所有关节的姿态报告为 null。
注意: 这意味着获取手部姿态时,要么获得整只手,要么什么也得不到。
这一默认行为无法如实反映多指/少指手,但出于指纹识别风险考虑,该行为未来可能作为可选功能开放。详细内容见Issue 11。
原生原点的 -Y 方向垂直于皮肤指向手掌外侧,-Z 方向沿其关联骨骼、远离手腕方向。
对于没有关联骨骼的指尖骨骼关节,其 -Z
方向与前一关节(远节关节)保持一致,即沿前一根骨头方向。对于手腕骨骼关节,-Z 方向应大致指向手掌中心。
每个 XRJointSpace
都有一个关联的 hand(手),即创建它的 XRHand。
jointName(关节名) 返回其追踪的关节名称。
每个 XRJointSpace
都有一个关联的 joint(关节),即与 jointName 对应的骨骼关节。
4. 帧循环
4.1. 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的
时间一致。
该方法调用时,用户代理必须执行以下步骤:
-
设frame为this。
-
设session为frame的
session对象。 -
如果frame的active为
false,抛出InvalidStateError并中止。 -
如果baseSpace或joint的session不同于 this
session,抛出InvalidStateError并中止。 -
设pose为 new
XRJointPose,构造于session的相关领域。 -
以
force emulation为false, 填充(Populate) joint 在 baseSpace、frame时刻的姿态到pose。 -
若pose为
null,返回null。 -
返回pose。
fillJointRadii(sequence<XRJointSpace> jointSpaces, Float32Array radii)
方法将jointSpaces的半径填充到radii,返回布尔值指示所有空间是否都有有效姿态。
该方法在XRFrame
frame上调用时,用户代理必须:
-
设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。
注意: 如果用户代理无法获取同一 XRHand 任何空间的姿态,则该
XRHand
所有关节空间也无法获得姿态。
fillPoses(sequence<XRSpace> spaces, XRSpace baseSpace, Float32Array transforms)
方法将spaces相对于baseSpace的姿态矩阵填入transforms,返回布尔值表示所有空间是否都有有效姿态。
该方法在XRFrame
frame上调用时,用户代理必须:
-
设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(),用户代理可: - 令pose与上次同对象。
- 否则
- 令pose为 new
XRPose构造于session的相关领域
- 若上一次已调用
-
令allValid为
true。 -
遍历spaces序列的每一个space:
-
返回allValid。
注意: 若同一 XRHand 的任何空间填充姿态时返回null,则该XRHand
下所有空间也应返回null
4.2. XRJointPose(关节姿态)
XRJointPose
是 XRPose
的子类,包含其表示的骨骼关节的尺寸信息。
[Exposed =Window ]interface :XRJointPose XRPose {readonly attribute float radius ; };
radius 属性返回该关节的半径,单位为米。
如XR 设备无法获得radius
,无论是固有缺陷还是在当前动画帧下(例如该骨骼关节被遮挡),用户代理必须为其赋予模拟值。
5. 隐私与安全性考虑
WebXR 手部输入 API 是一项强大功能,带来重要的隐私风险。因该功能会返回新的传感器数据,用户代理在创建会话时必须获得用户的明确同意。
本 API 返回的数据必须不得精确到可识别单个用户。如底层硬件返回过于精细的数据,用户代理在通过 WebXR 手部输入 API 暴露前必须进行匿名化。
本 API 只支持以 "immersive-vr"
或 "immersive-ar"
的 XRSessionMode 创建的 XRSession。
"inline"
的会话不得支持此 API。
变更内容
自2020年10月22日首次公开工作草案以来的变更
-
提及 grasp 配置文件(GitHub #68)
-
由常量改为枚举,以及 XRHand 改为 map(GitHub #71)
-
安全章节补充说明(GitHub #87)
-
hand 属性标记为 sameobject 并添加澄清说明(GitHub #93)
-
tip 关节非零半径(GitHub #111)