WebXR 手部输入模块 - 第一级

W3C 工作草案,

关于本文档的更多信息
本版本:
https://www.w3.org/TR/2024/WD-webxr-hand-input-1-20240605/
最新发布版本:
https://www.w3.org/TR/webxr-hand-input-1/
编辑草案:
https://immersive-web.github.io/webxr-hand-input/
以往版本:
历史记录:
https://www.w3.org/standards/history/webxr-hand-input-1/
反馈:
GitHub
规范内留言
编辑:
(Google [Mozilla 至2020])
参与方式:
提交新问题 (已开放的问题)
邮件列表存档
W3C的 #immersive-web IRC

摘要

WebXR 手部输入模块为 WebXR 设备 API 扩展了用于追踪手部关节姿势的功能。

本文档状态

本节描述了本文档在发布时的状态。当前 W3C 公布的规范列表和本技术报告的最新修订版可在 W3C 技术报告索引 https://www.w3.org/TR/ 中找到。

沉浸式 Web 工作组维护着 尚未处理的所有问题报告清单。本草案重点说明了工作组尚待讨论的一些待决问题。对于这些问题的最终处理(包括其有效性),尚未作出决定。 针对未决问题提交规范文本的 Pull Request 非常鼓励。

本文档由 沉浸式 Web 工作组推荐路径 工作草案身份发布。本文件旨在成为 W3C 推荐。

作为工作草案发布并不意味着 W3C 及其成员的认可。本文为草稿文件,可能随时被其他文档更新、替换或废止。除非作为正在进行的工作引用,否则不适宜引用本文档。

本文档由一组根据 W3C 专利政策 运作的小组编写。 W3C 维护着 与该小组成果相关的公开专利披露列表;该页面还包括专利披露的说明。任何实际知晓某专利并认为其包含 必要声明 的个人,都必须按照 W3C 专利政策第6节 披露相关信息。

本文件遵循 2023年11月3日 W3C 过程文档 管理。

本 WebXR 增强现实模块被设计为 WebXR 设备 API 的补充模块,并最初包含于 WebXR 设备 API 之中,后拆分为核心和子模块。

1. 简介

在某些XR 设备上,当手作为输入源使用时,可以获得用户手部的完整关节信息。

该 API 暴露了用户每只手的骨骼关节的姿势。这可用于手势检测或在 VR 场景中渲染手部模型。

2. 初始化

如果应用希望在会话期间查看手部关节姿态信息,必须通过合适的功能描述符请求会话。本模块引入了字符串 "hand-tracking" 作为用于手部关节追踪的新合法功能描述符

只有在 XRSessionXR 设备拥有物理手部输入源支持手部追踪时,才应授予" 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

Visual aid demonstrating joint layout

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]] 在会话期间不得发生变化。

用于遍历的值对,对于一个 XRHand 对象,就是 以XRHandJoint 作为 key、XRJointSpace 为 value 的 值对, 顺序同 关节列表(见骨骼关节)。

如果某设备不支持本规范定义的某个关节,则必须进行模拟。

size 属性必须返回 25。

get(jointName) 方法在 XRHand this 上被调用时,应执行以下步骤:
  1. jointsthis[[joints]] 内部槽的值。

  2. 返回 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时间一致。

该方法调用时,用户代理必须执行以下步骤:

  1. framethis

  2. sessionframesession 对象。

  3. 如果frameactivefalse,抛出InvalidStateError并中止。

  4. 如果baseSpacejointsession不同于 this session,抛出InvalidStateError并中止。

  5. posenew XRJointPose ,构造于session相关领域

  6. force emulationfalse填充(Populate) jointbaseSpaceframe时刻的姿态到pose

  7. posenull,返回null

  8. poseradiusjoint(如需可模拟)的半径

  9. 返回pose

fillJointRadii(sequence<XRJointSpace> jointSpaces, Float32Array radii) 方法将jointSpaces的半径填充到radii,返回布尔值指示所有空间是否都有有效姿态。

该方法在XRFrame frame上调用时,用户代理必须:

  1. framethis

  2. sessionframesession 对象。

  3. frameactivefalse,抛出InvalidStateError 并中止。

  4. 遍历jointSpaces中的每一个joint

    1. jointsession不同于session,抛出InvalidStateError 并中止。

  5. jointSpaces长度大于radii元素数,抛出TypeError 并中止。

  6. offset为新数字,初值0

  7. allValidtrue

  8. 遍历jointSpaces中的每一个joint

    1. radii[offset] 浮点值:

      若用户代理能获取joint所属所有关节姿态:
      设值为半径
      否则
      设值为NaN
      allValid设为false
    2. offset1

  9. 返回allValid

注意: 如果用户代理无法获取同一 XRHand 任何空间的姿态,则该 XRHand 所有关节空间也无法获得姿态。

fillPoses(sequence<XRSpace> spaces, XRSpace baseSpace, Float32Array transforms) 方法将spaces相对于baseSpace的姿态矩阵填入transforms,返回布尔值表示所有空间是否都有有效姿态。

该方法在XRFrame frame上调用时,用户代理必须:

  1. framethis

  2. sessionframesession 对象。

  3. frameactivefalse,抛出InvalidStateError 并中止。

  4. 遍历spaces序列的每一个space

    1. spacesession不同于 session,抛出InvalidStateError 并中止。

  5. baseSpacesession不同于session,抛出InvalidStateError 并中止。

  6. spaces长度乘16大于transforms元素数,抛出TypeError 并中止。

  7. offset为新数字,初值0

  8. 初始化pose如下:

    若上一次已调用fillPoses(),用户代理可:
    pose与上次同对象。
    否则
    posenew XRPose 构造于session相关领域
  9. allValidtrue

  10. 遍历spaces序列的每一个space

    1. 填充 space 相对 baseSpaceframe时刻的姿态到pose

    2. posenull,则:

    3. transformsoffset开始的16个单元设置为NaN

    4. allValid设为false

    5. pose不为null,把posematrix 拷贝到 transforms,起始索引offset

    6. offset16

  11. 返回allValid

注意: 若同一 XRHand 的任何空间填充姿态时返回null,则该XRHand 下所有空间也应返回null

4.2. XRJointPose(关节姿态)

XRJointPoseXRPose 的子类,包含其表示的骨骼关节的尺寸信息。

[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。

匿名化手部数据时,UA 可遵循如下准则:

变更内容

2020年10月22日首次公开工作草案以来的变更

一致性

文档约定

一致性要求通过描述性断言与 RFC 2119 术语的组合表达。 本规范规范性部分中出现的关键字 “MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY” 和 “OPTIONAL” 按照 RFC 2119 的说明进行解释。 为了可读性,这些词在本规范中未全部大写。

除明确标注为非规范性、示例和注释的章节外,本规范的全部文本均为规范性内容。[RFC2119]

本规范中的示例以 “for example” 引入,或用 class="example" 与规范性文本分隔,格式如下:

这是一个说明性示例。

说明性注释以 “Note” 开头,并用 class="note" 与规范性文本分隔,格式如下:

Note, 这是一个说明性注释。

一致性算法

以祈使语气出现的算法要求(例如“去除所有前导空格字符”或“返回 false 并中止这些步骤”),要根据引入算法时所用关键字(如 “must”、“should”、“may” 等)理解其含义。

以算法或具体步骤表达的一致性要求,可以以任何方式实现,只需最终结果等价即可。 本规范中的算法尤其侧重易于理解,而非性能。鼓励实现者进行优化。

索引

本规范定义的术语

引用中定义的术语

参考文献

规范性引用

[HTML]
Anne van Kesteren; et al. HTML 标准. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. 用于指示需求级别的关键词. 1997年3月. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SERVICE-WORKERS]
Jake Archibald; Marijn Kruisselbrink. Service Workers. 2022年7月12日. CR. URL: https://www.w3.org/TR/service-workers/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准. Living Standard. URL: https://webidl.spec.whatwg.org/
[WEBXR]
Brandon Jones; Manish Goregaokar; Rik Cabanier. WebXR 设备 API. 2024年4月16日. CR. URL: https://www.w3.org/TR/webxr/
[WEBXR-AR-MODULE-1]
Brandon Jones; Manish Goregaokar; Rik Cabanier. WebXR 增强现实模块 - 第一级 1. 2022年11月2日. CR. URL: https://www.w3.org/TR/webxr-ar-module-1/

IDL 索引

partial interface XRInputSource {
   [SameObject] readonly attribute XRHand? hand;
};

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

[Exposed=Window]
interface XRJointSpace: XRSpace {
  readonly attribute XRHandJoint jointName;
};

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

[Exposed=Window]
interface XRJointPose: XRPose {
    readonly attribute float radius;
};

问题索引

默认情况下,这无法真实反映多指/少指的手,不过出于指纹识别问题考虑,这种能力今后很可能需要单独开启。详情参见 问题 11
MDN

XRFrame/fillJointRadii

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

XRFrame/fillPoses

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

XRFrame/getJointPose

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

XRHand

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?

XRJointSpace

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

XRInputSource/hand

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

XRJointPose/radius

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

XRJointPose

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

XRJointSpace/jointName

In no current engines.

FirefoxNoneSafariNoneChromeNone
Opera?EdgeNone
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?