three.js ARButton 使用指南:基于 WebXR 快速构建沉浸式 AR 会话启动按钮
本文围绕 three.js 官方 API 文档 docs/pages/ARButton.html.md 展开,系统讲解 ARButton 这个 WebXR 辅助类的作用、导入方式与 createButton 工厂方法的参数语义,并结合仓库源码 examples/jsm/webxr/ARButton.js 逐段剖析按钮的内部状态机:WebXR 能力探测、immersive-ar 会话的启动与结束、dom-overlay 自动注入、offerSession 系统级唤起,以及 HTTPS 等前置约束。读完后,你可以在自己的 three.js 应用中一行代码接入 AR 入口,并能针对 hit-test、plane-detection、camera-access 等 WebXR 特性按需配置会话参数。
ARButton 是什么
ARButton 是一个用于创建“启动沉浸式 AR 会话按钮”的工具类。它的官方定义直接写在源码头部注释中:
// examples/jsm/webxr/ARButton.js
/**
* A utility class for creating a button that allows to initiate
* immersive AR sessions based on WebXR. The button can be created
* with a factory method and then appended to the website's DOM.
*
* ```js
* document.body.appendChild( ARButton.createButton( renderer ) );
* ```
*/
它的定位非常聚焦:
- 只负责发起
immersive-ar这一种 WebXR 会话模式(区别于VRButton的immersive-vr,也区别于XRButton的“先 AR 后 VR”回退策略,见 docs/pages/XRButton.html.md); - 自动完成能力探测、按钮样式、会话生命周期管理,把 WebXR 原始 API 的样板代码封装起来;
- 以工厂方法(静态方法)形式返回一个
HTMLElement,由开发者自行挂载到页面 DOM。
它是 three.js 的 addon(插件),不属于核心包 three 本体,必须显式导入。
导入方式
按照文档要求,ARButton 需要从 addons 路径显式导入:
import { ARButton } from 'three/addons/webxr/ARButton.js';
该模块由 examples/jsm/Addons.js 统一再导出,仓库中所有 AR 示例(如 examples/webxr_ar_hittest.html)都通过 import map 把 three/addons/ 映射到 ./jsm/ 目录来解析这个路径。
基本用法:一行代码创建按钮
文档给出的最小可运行示例:
document.body.appendChild( ARButton.createButton( renderer ) );
在 AR 场景里,这行代码通常与以下三处配套代码一起出现。以 examples/webxr_ar_cones.html 为例,AR 会话中相机画面由设备透传提供,因此渲染器必须带透明背景:
const renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.xr.enabled = true; // 开启 XR 渲染路径
container.appendChild( renderer.domElement );
document.body.appendChild( ARButton.createButton( renderer ) );
两个关键前提值得强调:
renderer.xr.enabled = true:按钮启动会话后调用的是renderer.xr.setSession( session ),若未启用 XR 路径,渲染不会接管 XR 帧循环;renderer使用setAnimationLoop而非requestAnimationFrame:XR 会话下必须由浏览器在 XR 帧边界内回调,three.js 的动画循环接口正好满足这一点;alpha: true:让 WebGL 画布透明,AR 透视画面(由系统合成在底层)才能透出。
API 详解:.createButton( renderer, sessionInit )
文档定义的静态方法签名为:
.createButton( renderer : WebGLRenderer | WebGPURenderer, sessionInit : XRSessionInit ) : HTMLElement
参数 renderer
渲染器实例,WebGLRenderer 或 WebGPURenderer 均可。按钮内部通过它调用 renderer.xr(即 WebXRManager)完成会话接管,见 examples/jsm/webxr/ARButton.js 中的 onSessionStarted:
async function onSessionStarted( session ) {
session.addEventListener( 'end', onSessionEnded );
renderer.xr.setReferenceSpaceType( 'local' );
await renderer.xr.setSession( session );
button.textContent = 'STOP AR';
sessionInit.domOverlay.root.style.display = '';
currentSession = session;
}
注意 ARButton 固定使用 local 参考空间。local 表示以会话启动时用户头部位置为原点——这正是 AR 场景的合理选择(不依赖房间平面标定)。WebXRManager.setReferenceSpaceType 的实现位于 src/renderers/webxr/WebXRManager.js,且该方法明确说明“不能在进行中的 XR 会话里调用”,所以 ARButton 把它放在 setSession 之前执行。
参数 sessionInit
这是透传给 WebXR 标准的 XRSessionInit 配置对象,第二个参数可省略(默认 {})。它对会话能力的控制方式与浏览器原生 navigator.xr.requestSession 完全一致,仓库中的四个示例分别演示了典型用法:
// 命中测试:requiredFeatures 表示会话必须支持该特性,否则请求失败
// examples/webxr_ar_hittest.html
document.body.appendChild( ARButton.createButton( renderer, { requiredFeatures: [ 'hit-test' ] } ) );
// 平面检测
// examples/webxr_ar_plane_detection.html
document.body.appendChild( ARButton.createButton( renderer, {
requiredFeatures: [ 'plane-detection' ]
} ) );
// 相机画面访问
// examples/webxr_ar_camera_access.html
document.body.appendChild( ARButton.createButton( renderer, { requiredFeatures: [ 'camera-access' ] } ) );
// 光照估计:optionalFeatures 表示尽量支持,不支持也不报错
// examples/webxr_ar_lighting.html
document.body.appendChild( ARButton.createButton( renderer, { optionalFeatures: [ 'light-estimation' ] } ) );
返回值语义
文档写明返回值是“按钮本体,或者在 immersive-ar 不被支持时的错误信息”。从源码看,实际可能出现五种界面状态,这决定了你不应假设返回的永远是可点击按钮:
| 状态文本 | 触发条件 | 源码位置 |
|---|---|---|
START AR |
设备支持 immersive-ar,可点击启动 |
examples/jsm/webxr/ARButton.js |
STOP AR |
会话进行中,点击结束会话 | examples/jsm/webxr/ARButton.js |
AR NOT SUPPORTED |
isSessionSupported('immersive-ar') 返回 false,按钮被禁用 |
examples/jsm/webxr/ARButton.js |
AR NOT ALLOWED |
调用 isSessionSupported 抛出异常(如权限被拒) |
examples/jsm/webxr/ARButton.js |
WEBXR NEEDS HTTPS / WEBXR NOT AVAILABLE |
环境中不存在 navigator.xr(非安全上下文时返回带 HTTPS 地址的链接) |
examples/jsm/webxr/ARButton.js |
最后一个状态揭示了 AR 的硬性部署前提:WebXR 仅在安全上下文(HTTPS 或 localhost)下暴露。源码中检测到 window.isSecureContext === false 时会生成一个指向对应 HTTPS 地址的 <a> 元素作为提示。因此在本地开发时必须走 localhost,生产部署必须配置 HTTPS。
内部工作流程剖析
结合 examples/jsm/webxr/ARButton.js 的完整实现,按钮的完整生命周期如下。
1. 能力探测与初始渲染
createButton 创建 <button> 后立即执行 stylizeElement(绝对定位、距底部 20px、半透明白色文字等内联样式,见 examples/jsm/webxr/ARButton.js),然后分两条路径:
if ( 'xr' in navigator ) {
navigator.xr.isSessionSupported( 'immersive-ar' ).then( function ( supported ) {
supported ? showStartAR() : showARNotSupported();
} ).catch( showARNotAllowed );
return button;
} else {
// 返回 'WEBXR NOT AVAILABLE' / 'WEBXR NEEDS HTTPS' 提示链接
}
注意按钮初始 display: 'none',只有 isSessionSupported 返回 true 后才会显示为可点击的 START AR。也就是说页面加载后按钮可能一直不出现,这是设计使然而非 bug。
2. dom-overlay 的自动注入与关闭按钮
AR 会话进入后浏览器通常隐藏全部网页 DOM。ARButton 在首次展示 START AR 时(showStartAR)会检测 sessionInit.domOverlay 是否已配置,若没有则自动:
- 创建一个隐藏的
<div>overlay 容器并追加到document.body; - 在其中生成一个 38×38 的 SVG 白色 “×” 图标,绝对定位在屏幕右上角(
right: 20px; top: 20px),点击它即调用currentSession.end()退出 AR; - 向
sessionInit.optionalFeatures推入'dom-overlay',并写入sessionInit.domOverlay = { root: overlay }。
// examples/jsm/webxr/ARButton.js(showStartAR 节选)
if ( sessionInit.domOverlay === undefined ) {
const overlay = document.createElement( 'div' );
overlay.style.display = 'none';
document.body.appendChild( overlay );
const svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
// ... 右上角 “×” 图标,点击后 currentSession.end()
if ( sessionInit.optionalFeatures === undefined ) {
sessionInit.optionalFeatures = [];
}
sessionInit.optionalFeatures.push( 'dom-overlay' );
sessionInit.domOverlay = { root: overlay };
}
这一机制有两个使用上的注意点:
- 它直接修改了你传入的
sessionInit对象(给optionalFeatures数组追加元素、补上domOverlay字段)。如果你把同一个配置对象复用到其他逻辑,要意识到它已被增强; - 因为
dom-overlay是通过optionalFeatures请求的,不支持该特性的设备不会导致会话失败,只是失去右上角关闭入口。会话进行中通过sessionInit.domOverlay.root.style.display = ''显示 overlay,结束后恢复隐藏(examples/jsm/webxr/ARButton.js)。
3. offerSession:系统级免点击唤起
ARButton 还利用了 WebXR 的 offerSession API:在展示 START AR 时,若 navigator.xr.offerSession !== undefined,会主动向浏览器“推销”一次 AR 会话请求;用户若在系统层面接受,无需点击网页按钮即可直接走 onSessionStarted 流程。会话结束后再次点击时也会先 end() 旧会话、再 offerSession 发起新一轮(examples/jsm/webxr/ARButton.js)。该 API 为可选能力,未实现时自动降级为纯点击启动,代码对两种情况都做了兼容。
4. 会话生命周期:onSessionStarted / onSessionEnded
会话状态切换的闭环逻辑:
- 启动:监听
end事件 → 设置local参考空间 →await renderer.xr.setSession( session )→ 按钮文案切换为STOP AR、显示 overlay; - 结束:移除监听 → 文案恢复
START AR、隐藏 overlay、currentSession置空,按钮重新具备再次启动的能力。
STOP AR 的点击路径(currentSession !== null 分支)会主动调用 currentSession.end(),随后由 end 事件驱动收尾,避免状态不同步。
仓库中的实际应用场景
仓库里五个 AR 示例覆盖了 ARButton 的典型组合,可直接作为业务模板参考:
| 示例 | 配置要点 | 用途 |
|---|---|---|
| examples/webxr_ar_cones.html | 无额外特性 | 最简 AR 场景,透传画面上摆放锥体 |
| examples/webxr_ar_hittest.html | requiredFeatures: [ 'hit-test' ] |
屏幕空间命中测试,配合 reticle 把物体放置到真实平面 |
| examples/webxr_ar_plane_detection.html | requiredFeatures: [ 'plane-detection' ] |
结合 XRPlanes 对象直接获取设备检测到的平面 |
| examples/webxr_ar_lighting.html | optionalFeatures: [ 'light-estimation' ] |
环境光照估计,让 3D 内容与现实光照匹配 |
| examples/webxr_ar_camera_access.html | requiredFeatures: [ 'camera-access' ] |
通过 renderer.xr.getCameraTexture( view.camera ) 拿到相机画面作为贴图 |
以 hit-test 为例,示例中的按钮创建与渲染器初始化是标准四步:new THREE.WebGLRenderer( { antialias: true, alpha: true } ) → setAnimationLoop → renderer.xr.enabled = true → ARButton.createButton( renderer, { requiredFeatures: [ 'hit-test' ] } )(examples/webxr_ar_hittest.html)。后续在 sessionstart 回调中通过 session.requestHitTestSource 获取命中源,在 select 事件中把 reticle 的世界矩阵分解后放置新物体。
与 XRButton 的关系及选型建议
three.js 同时提供 XRButton(文档见 docs/pages/XRButton.html.md),其方法签名与 ARButton.createButton 完全相同。从 XRButton 的文档描述看,它会优先尝试 immersive-ar,设备不支持时回退到 immersive-vr。因此选型逻辑可以归纳为:
- 应用只服务于 AR(如手机上的增强现实放置类体验):用
ARButton,语义清晰,且AR NOT SUPPORTED的提示对用户更准确; - 应用希望同一套代码兼顾手机 AR 与头显 VR:用
XRButton,牺牲少量提示精度换取跨形态覆盖。
小结
ARButton 用一行 document.body.appendChild( ARButton.createButton( renderer ) ) 的背后,封装了完整的 WebXR immersive-ar 接入链路:安全上下文检查、能力探测、按钮状态机(START AR / STOP AR / 各类不可用提示)、dom-overlay 关闭入口自动注入、local 参考空间配置与 offerSession 系统级唤起。使用时只要记住三点:
renderer需xr.enabled = true、使用setAnimationLoop,并建议开启alpha: true;- 通过第二个参数
sessionInit声明requiredFeatures/optionalFeatures,并意识到 ARButton 可能向其中追加dom-overlay相关字段; - 运行环境必须是 HTTPS(或 localhost),否则页面得到的将是
WEBXR NEEDS HTTPS提示链接而非可点击按钮。
更多 API 细节可参考 examples/jsm/webxr/ARButton.js 源码与官方 API 页 docs/pages/ARButton.html。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00