WebXR 平面检测模块

编辑稿,

关于本文档的更多详细信息
本版本:
https://immersive-web.github.io/plane-detection/
问题跟踪:
GitHub
编辑:
(Google)
(Meta)
前任编辑:
(Google)
参与:
提交问题(待处理问题)
邮件列表存档
W3C 的 #immersive-web IRC

摘要

平面检测是一个扩展 WebXR Device API 功能的模块。它使应用能够接收 原生 XR 设备检测到的平面集合,从而实现更具沉浸感的体验。

本文档的状态

本节描述本文档在发布时的状态。当前 W3C 出版物列表以及本 技术报告的最新修订版可在 http://www.w3.org/TR/ 的 W3C 技术 报告索引中找到。

本文档由沉浸式 Web 工作组 作为编辑稿发布。本文档预期将成为 W3C 推荐标准。 欢迎对此规范提供反馈和评论。请使用 GitHub 议题。 相关讨论也可在 public-immersive-web-wg@w3.org 存档中找到。

作为编辑稿发布并不意味着获得 W3C 及其成员的认可。本文档是草案,可能 随时被其他文档更新、替代或废止。除将其作为尚在进行中的工作外, 不应引用本文档。

本文档由一个依据 W3C 专利政策运作的小组制定。 W3C 维护着一份与该小组交付成果相关的所有 专利 披露的公开列表;该页面还包含 专利披露说明。实际知晓某项专利,并认为该专利包含 必要 权利要求的个人,必须依照 W3C 专利政策第 6 节披露相关信息。

本文档受 2025 年 8 月 18 日 W3C 流程文档约束。

1. 简介

2. 初始化

2.1. 功能描述符

为了使应用能够表明其有意在会话期间使用平面检测,必须使用适当的功能 描述符请求该会话。本模块引入字符串 plane-detection,将其作为平面检测功能的一个新的有效功能 描述符。

如果设备的跟踪系统公开了原生平面检测能力,则该设备能够支持平面检测功能。内联 XR 设备不得被视为能够支持平面检测功能。

创建启用了平面检测功能的会话时,必须将更新平面算法添加到该会话的帧更新列表中。

以下代码演示了如何请求一个要求平面检测的会话:
const session = await navigator.xr.requestSession("immersive-ar", {
  requiredFeatures: ["plane-detection"]
});

3. 平面

3.1. XRPlaneOrientation

enum XRPlaneOrientation {
    "horizontal",
    "vertical"
};

3.2. XRPlane

[Exposed=Window]
interface XRPlane {
    [SameObject] readonly attribute XRSpace planeSpace;

    readonly attribute FrozenArray<DOMPointReadOnly> polygon;
    readonly attribute XRPlaneOrientation? orientation;
    readonly attribute DOMHighResTimeStamp lastChangedTime;
    readonly attribute DOMString? semanticLabel;
};

XRPlane 表示底层 XR 系统检测到的单个平坦表面。

planeSpace 是一个用于建立平面坐标系的 XRSpace。 planeSpace 的原生原点跟踪平面的中心。底层 XR 系统定义平面中心的确切含义。由 planeSpace 定义的坐标系的 Y 轴必须表示平面的法向量。

每个 XRPlane 都有 一个关联的原生实体。

每个 XRPlane 都有 一个关联的帧。

polygon 是描述平面形状的顶点数组。这些顶点以多边形边缘上的闭合点序列形式返回,并使用由 planeSpace 定义的坐标系表示。每个顶点的 Y 坐标必须为 0.0。

semanticLabel 属性是一个描述多边形语义标签的字符串。如果不存在语义信息,该字符串可以为 null 或空字符串。XRSystem 应当使用其已知的语义标签填充该属性。

语义标签是一个 ASCII 小写 DOMString,用于描述 XRPlane 在现实世界中的名称,该名称由 XRSystem 所知。语义标签列表在语义标签注册表中定义。

orientation 描述由底层 XR 系统分类的平面方向。如果底层 XR 系统无法将方向分类为 "horizontal" 或 "vertical", 则该属性将设置为 null。

lastChangedTime 是平面某些属性最后一次发生更改的时间。

注:平面的姿态不被视为平面 属性,因此平面姿态的更新不会导致 lastChangedTime 发生变化。这是因为平面姿态是从两个不同实体派生的属性,即 planeSpace 和作为相对参照的 XRSpace, 姿态将通过 getPose() 函数相对于后者进行计算。

4. 获取检测到的平面

4.1. XRPlaneSet

[Exposed=Window]
interface XRPlaneSet {
  readonly setlike<XRPlane>;
};

XRPlaneSet 是 XRPlane 的集合。它是 获取 XRFrame 中检测到的平面集合的主要机制。

partial interface XRFrame {
  readonly attribute XRPlaneSet detectedPlanes;
};

XRFrame 被扩展为包含 detectedPlanes 属性,其中包含帧中仍在跟踪的所有平面。该集合最初为空,并将由更新 平面算法填充。如果在帧未处于活动状态时访问该属性, 用户代理必须抛出 InvalidStateError。

partial interface XRSession {
  Promise<undefined> initiateRoomCapture();
};

XRSession 被扩展为包含一个关联的已跟踪平面集合,该集合最初 为空。集合中的元素将为 XRPlane 类型。

XRSession 被扩展为包含一个布尔值房间捕获已完成,其初始值为 false。

如果 XR 设备支持手动捕获,则它具有一个异步房间捕获方法,该方法返回一个布尔值。

XRSession 还被扩展为包含 initiateRoomCapture 方法。如果支持,该方法将要求 XR 设备捕获当前房间布局。是否使用捕获结果替换或扩充已跟踪 平面集合,由 XR 设备决定。

调用此方法时,用户代理必须运行以下步骤:
  1. 令 session 为此对象

  2. 令 promise 为在 session 的相关领域中创建的新 Promise。

  3. 如果 session 的 ended 值为 `true`,则使用一个“InvalidStateError” DOMException 拒绝 promise,并返回 promise。

  4. 如果房间捕获已完成为 `true`,则使用一个“InvalidStateError” DOMException 拒绝 promise, 并返回 promise。

  5. 如果 plane-detection 功能描述符未被包含在 session 的XR 设备针对 session 的模式的已启用功能列表中,则使用一个“NotSupportedError” DOMException 拒绝 promise,并返回 promise。

  6. 如果 session 的 XR 设备不支持手动捕获,或者 XRSystem 确定不需要进行房间捕获:

    1. 将 session 的房间捕获已完成设置为 `true`。

    2. 兑现 promise。

    3. 返回 promise。

  7. 排入一个任务以执行以下步骤:

    1. 调用 session 的 XR 设备的房间捕获方法,等待其结果,并将该结果 赋给 result。

    2. 运行以下步骤:

      如果 result 为 `true`:

      兑现 promise

      否则:

      使用一个“OperationError” DOMException 拒绝 promise

    3. 将 session 的房间捕获已完成设置为 `true`。

  8. 返回 promise。

为了为 frame 更新 平面,用户代理必须运行以下步骤:
  1. 令 session 为 frame 的会话。

  2. 令 device 为 session 的XR 设备。

  3. 如果 plane-detection 功能描述符未被包含在 device 针对 session 的模式的已启用功能列表中,则中止这些步骤。

  4. 令 trackedPlanes 为调用 device 的原生平面检测能力,以获取 frame 的时间所对应时刻的已跟踪 平面后得到的结果。

  5. 对 trackedPlanes 中的每个 native plane,运行:

    1. 如有需要,将 native plane 视为未出现在 trackedPlanes 中,并继续处理下一个条目。有关可用于判断是否应以这种方式忽略条目的标准, 请参阅§ 6 隐私与安全考量。

    2. 如果 session 的已跟踪平面集合包含一个 与 native plane 对应的对象 plane,则以 plane、native plane 和 frame 调用更新平面对象算法,并继续处理 下一个条目。

    3. 令 plane 为使用 native plane 和 frame 调用创建平面对象 算法得到的结果。

    4. 将 plane 添加到 session 的已跟踪平面集合中。

  6. 从 session 的已跟踪 平面集合中移除在本算法调用期间既未创建也未更新的每个对象。

  7. 将 frame 的 detectedPlanes 设置为已跟踪平面集合。

为了根据原生平面对象 native plane 和 XRFrame frame 创建平面 对象,用户代理必须运行以下步骤:
  1. 令 result 为 XRPlane 的新实例。

  2. 将 result 的原生实体设置为 native plane。

  3. 将 result 的 planeSpace 设置为一个新的 XRSpace 对象;创建该对象时,将会话设置为 frame 的 session, 并将原生原点设置为跟踪 native plane 的 原生原点。

  4. 以 result、native plane 和 frame 调用更新平面对象算法。

  5. 返回 result。

以这种方式创建的平面对象 result 被称为与传入的原生平面 对象 native plane 对应。

为了使用原生平面对象 native plane 和 XRFrame frame 更新平面 对象 plane,用户代理必须运行以下步骤:
  1. 将 plane 的帧设置为 frame。

  2. 如果底层系统将 native plane 分类为垂直方向,则将 plane 的 orientation 设置为 "vertical"。 否则,如果底层系统将 native plane 分类为水平方向,则将 plane 的 orientation 设置为 "horizontal"。 否则,将 plane 的 orientation 设置为 null。

  3. 将 plane 的 polygon 设置为表示 native plane 多边形的新顶点数组,并执行所有必要的转换, 以处理原生平面多边形表示方式的差异。

  4. 将 plane 的 semanticLabel 设置为包含语义标签的新字符串。

  5. 如有需要,按照§ 6 隐私与安全考量中的描述,降低 plane 的 polygon 的细节级别。

  6. 将 plane 的 lastChangedTime 设置为时间。

以下示例演示了应用如何获取检测到的平面相关信息并据此采取操作。 可用于渲染平面图形表示的代码未在此展示。

// `planes` 将跟踪应用已知的所有检测到的平面,
// 以及它们更新时的时间戳。最初,这是一个空映射。
const planes = Map();

function onXRFrame(timestamp, frame) {
  const detectedPlanes = frame.detectedPlanes;

  // 首先,检查我们之前知道的平面中是否有任何平面已不再被跟踪:
  for (const [plane, timestamp] of planes) {
    if(!detectedPlanes.has(plane)) {
      // 处理已移除的平面——`plane` 存在于上一帧中,
      // 但现在已不再被跟踪。

      // 我们知道该平面已不存在,因此将其从映射中移除:
      planes.delete(plane);
    }
  }

  // 接下来,处理所有仍在跟踪的平面。
  // 其中既包括我们之前见过的已跟踪平面(可能已更新),
  // 也包括新平面。
  detectedPlanes.forEach(plane => {
    if (planes.has(plane)) {
      // 处理之前见过的平面:

      if(plane.lastChangedTime > planes.get(plane)) {
        // 处理之前见过且已更新的平面。
        // 这意味着该平面的某个属性与之前不同——
        // 最有可能是多边形发生了变化。

        ... // 渲染平面或为渲染准备平面等。

        // 更新我们更新该平面的时间:
        planes.set(plane, plane.lastChangedTime);
      } else {
        // 处理之前见过但未在当前帧中更新的平面。
        // 请注意,平面相对于其他某个空间的姿态可能已发生变化。
      }
    } else {
      // 处理新平面。

      // 设置我们更新该平面的时间:
      planes.set(plane, plane.lastChangedTime);
    }

    // 无论之前是否见过该平面,
    // 也无论其是否已更新,其姿态都可能已发生变化:
    const planePose = frame.getPose(plane.planeSpace, xrReferenceSpace);
  });

  frame.session.requestAnimationFrame(onXRFrame);
}

5. 原生设备概念

5.1. 原生平面检测

平面检测 API 提供有关在用户环境中检测到的平坦表面的信息。本规范假定,用户代理在实现 plane-detection 功能时可以依赖底层平台提供的原生平面检测能力。具体而言,底层 XR 设备应提供一种方式,用于查询在与特定 XRFrame 的时间相对应的时刻跟踪的所有平面。

此外,本规范假定,被称为原生平面对象的已跟踪平面会跨帧保持其标识—— 也就是说,给定底层系统在时间 t0 返回的平面对象 P, 以及底层系统在时间 t1 返回的平面对象 Q, 用户代理可以查询底层系统,以判断 P 和 Q 是否对应同一个逻辑平面对象。底层系统还应 提供一个原生原点,可用于查询时间 t 时 姿态的位置,但不能保证平面姿态始终已知(例如,对于仍被跟踪但在给定时刻 无法定位的平面)。此外,原生平面对象应公开一个描述检测到的平面近似形状的多边形。

此外,为了创建 XRAnchor, 底层系统应将原生平面识别为原生实体。有关更多信息,请参阅 WebXR 锚点模块 § native-anchor一节。

6. 隐私与安全考量

平面检测 API 会公开有关用户物理环境的信息。如果用户代理选择如此做,则可以限制公开的平面 信息(例如平面的多边形)。用户代理可以通过以下方式减少公开的信息:在更新平面对象算法中降低 平面多边形的细节级别(例如减少顶点数量,或者对顶点坐标进行舍入或量化), 或者在更新平面算法中表现得如同该平面对象 不存在于 trackedPlanes 集合中一样,从而完全移除该平面(例如,如果检测到的平面被认为 太小或过于细致而不应公开,并且用户代理未实现减少平面所公开细节的机制,则可以这样做)。平面的姿态 (可通过 planeSpace 获取)也可以进行量化。

由于平面检测 API 中的概念可用于[webxr-anchors-module] 规范公开的方法,因此与 WebXR 锚点模块相关的一些隐私与安全考量也适用于此处。有关详细信息,请参阅 WebXR 锚点模块 § privacy-security一节。

由于平面检测 API 扩展了 WebXR Device API,因此WebXR Device API § 13. 安全、隐私和舒适性考量一节也适用于 WebXR 平面检测模块公开的功能。

7. 致谢

以下人员为 WebXR 平面检测规范的设计作出了贡献:

一致性

文档 约定

一致性要求通过 描述性断言和 RFC 2119 术语的组合来表达。 本文档规范性部分中的关键词“MUST(必须)”、“MUST NOT(不得)”、“REQUIRED(要求)”、“SHALL(必须)”、“SHALL NOT(不得)”、“SHOULD(应当)”、“SHOULD NOT(不应当)”、“RECOMMENDED(建议)”、 “MAY(可以)”和“OPTIONAL(可选)” 应按照 RFC 2119 中的描述进行解释。 但是,为便于阅读, 这些词在本规范中并非全部以大写字母显示。

除明确标记为非规范性的章节、示例和注释外, 本规范的所有文本均为规范性文本。[RFC2119]

本规范中的示例以“例如”等词语引出, 或使用 class="example" 与规范性文本分隔, 如下所示:

这是一个资料性示例。

资料性注释以“注”一词开头, 并使用 class="note" 与规范性文本分隔, 如下所示:

注:这是一条资料性注释。

一致性 算法

算法中以祈使语气表述的要求 (例如“移除所有前导空格字符” 或“返回 false 并中止这些步骤”) 应按照引入该算法时使用的 关键词(“必须”、“应当”、“可以”等) 的含义进行解释。

以算法或具体步骤表述的一致性要求 可以采用任何方式实现, 只要最终结果等效即可。 特别是,本规范中定义的算法 旨在易于理解, 而非追求高性能。 鼓励实现者进行优化。

索引

本规范定义的 术语

通过引用 定义的术语

参考文献

规范性参考文献

[GEOMETRY-1]
Sebastian Zartner; Yehonatan Daniv. 几何接口 模块第 1 级。URL:https://drafts.csswg.org/geometry/
[HR-TIME-3]
Yoav Weiss. 高精度时间。URL:https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; 等。HTML 标准。 现行标准。URL:https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra 标准。现行标准。URL:https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. 用于 RFC 中 指示要求级别的关键词。1997 年 3 月。最佳当前实践。URL:https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL 标准。现行 标准。URL:https://webidl.spec.whatwg.org/
[WEBXR]
Brandon Jones; Manish Goregaokar; Rik Cabanier. WebXR 设备 API。URL:https://immersive-web.github.io/webxr/

资料性参考文献

[WEBXR-ANCHORS-MODULE]
Piotr Bialecki. WebXR 锚点模块。 DR。URL:https://immersive-web.github.io/anchors/

IDL 索引

enum XRPlaneOrientation {
    "horizontal",
    "vertical"
};

[Exposed=Window]
interface XRPlane {
    [SameObject] readonly attribute XRSpace planeSpace;

    readonly attribute FrozenArray<DOMPointReadOnly> polygon;
    readonly attribute XRPlaneOrientation? orientation;
    readonly attribute DOMHighResTimeStamp lastChangedTime;
    readonly attribute DOMString? semanticLabel;
};

[Exposed=Window]
interface XRPlaneSet {
  readonly setlike<XRPlane>;
};

partial interface XRFrame {
  readonly attribute XRPlaneSet detectedPlanes;
};

partial interface XRSession {
  Promise<undefined> initiateRoomCapture();
};