Cloud Anchors 云锚点跨设备共享 AR 体验
Cloud Anchors 解决什么问题
普通 ARCore Anchor 的生命周期是本地的——你在手机 A 上放了一个虚拟雕像,把手机交给另一个人,他看不到这个雕像。
Cloud Anchors 把本地锚点的特征点地图托管到 Google Cloud,返回一个唯一的字符串 ID。任何其他设备用这个 ID 就能下载特征点地图,在同一真实位置解析出同一个锚点——从而在不同手机上看到完全相同的 AR 内容。
Cloud Anchors 的工作流程
设备 A(托管方) Google Cloud 设备 B(解析方)
──────────────────────────────────────────────────────────────
创建本地 Anchor
托管锚点
(上传:特征点地图 + 当前姿态)
→ 存储地图,返回 anchorId
←──────────────────
收到 anchorId
用 anchorId 请求解析
← 发送 anchorId
(下载:特征点地图)
→ 上传当前摄像头画面做匹配
(匹配成功,计算设备姿态)
← 返回解析后的 Anchor
收到解析的 Anchor(和设备 A 的是同一个真实位置)
两阶段模型
| 阶段 | API | 说明 |
|---|---|---|
| 托管(Host) | session.hostCloudAnchor(anchor) | 本地 Anchor 的特征点地图上传到云端,获得 anchorId |
| 解析(Resolve) | session.resolveCloudAnchor(anchorId) | 用 anchorId 从云端下载地图,在当前设备上创建匹配的 Anchor |
托管端(Host)示例
// 1. 先在本地 SLAM 中创建好锚点
val localAnchor = session.createAnchor(hitResult.pose)
// 2. 托管到云端
val future = session.hostCloudAnchor(localAnchor, TTL_DAYS = 7)
// 3. 异步等待托管完成
future.addListener({
if (future.result?.cloudAnchorState == CloudAnchorState.SUCCESS) {
val anchorId = future.result!.anchorId // 这是一个字符串
// 把 anchorId 分享给其他设备(通过网络、二维码、直接传等)
shareAnchorId(anchorId)
}
}, Handler(Looper.getMainLooper()))
解析端(Resolve)示例
// 1. 拿到从设备 A 分享来的 anchorId
val anchorId = sharedAnchorId
// 2. 开始解析
val future = session.resolveCloudAnchor(anchorId)
// 3. 异步等待解析完成
future.addListener({
val state = future.result?.cloudAnchorState
when (state) {
CloudAnchorState.SUCCESS -> {
val cloudAnchor = future.result!!
// cloudAnchor 和本地 Anchor 一样用法
// cloudAnchor.pose 是在设备 B 的坐标系下对应的真实世界位置
placeContentAtAnchor(cloudAnchor)
}
CloudAnchorState.TTL_EXPIRED -> {
// 锚点已过期(超过设置的 TTL 天数)
}
CloudAnchorState.NOT_FOUND -> {
// anchorId 无效或被删除
}
}
}, Handler(Looper.getMainLooper()))
TTL:锚点过期时间
Cloud Anchors 有两种过期设置:
| TTL 模式 | 托管参数 | 说明 |
|---|---|---|
| 默认 | 不指定 TTL | 锚点保留 24 小时 |
| 自定义 | hostCloudAnchor(anchor, ttlDays) | 最长 365 天 |
| 永久 | hostCloudAnchor(anchor, 0) 或使用永久模式 | 长期保留(消耗更多配额) |
TTL 到期后,锚点在云端被删除,用这个 ID 解析会返回 NOT_FOUND。
配额与定价
Cloud Anchors 有免费额度(详见定价授权页面):
- 每月每项目 10,000 次 Host + 10,000 次 Resolve
- 超出部分按量计费(约 $0.016/千次 Host,$0.004/千次 Resolve)
监控用量
在 Google Cloud Console 中可以实时查看 Cloud Anchors 的用量和剩余配额。商业应用应该设置用量告警,避免超出配额导致功能中断。
与 Geospatial API 结合
Cloud Anchors + Geospatial API 是最强组合:
- Cloud Anchors 需要先有设备在某个位置扫描地图
- Geospatial API 让开发者可以在特定经纬度"预置"锚点
- 两者结合:开发者可以预先在某个地点托管 Cloud Anchor,然后让所有走到该地点的人都能解析同一个锚点
// 在服务器上预先托管锚点
// 或利用 Geospatial Anchor 的位置创建 Cloud Anchor
val geoAnchor = earth.createAnchor(lat, lng, altitude)
val cloudFuture = session.hostCloudAnchor(geoAnchor)
// 获得 anchorId 后,任何走到这个经纬度的用户都能解析
多人实时协作场景
Cloud Anchors 本身只负责静态锚点的创建和解析——它不提供实时同步。要实现多人实时共享同一 AR 场景,你需要自己写一个实时同步层(Firebase Realtime Database、WebSocket 等):
设备 A 托托管 Cloud Anchor → 获得 anchorId
→ 把 anchorId 发给 Firebase
→ 同时把设备 A 的虚拟物体位置推给 Firebase
设备 B 收到通知 → 用 anchorId Resolve Cloud Anchor
→ 订阅 Firebase 中的虚拟物体位置
→ 在 Cloud Anchor 坐标系中渲染来自设备 A 的虚拟物体
Google 的 Just a Line(多人 AR 绘画应用)和 Squanch Chat(多人 AR 社交)都是这种架构。
常见失败状态处理
| CloudAnchorState | 含义 | 处理 |
|---|---|---|
| SUCCESS | 托管/解析成功 | 正常使用 |
| ERROR_INTERNAL | 云端内部错误 | 可以重试,通常几秒后恢复 |
| ERROR_NOT_AUTHORIZED | API Key 无效 | 检查 Cloud Anchors API Key 配置 |
| ERROR_RATE_LIMITED | 频率超限 | 降低请求频率,或申请提高配额 |
| ERROR_RESOURCE_EXHAUSTED | 配额用完 | 升级计划或等下个月重置 |
| TTL_EXPIRED | 锚点已过期 | 让托管方重新托管 |
| NOT_FOUND | anchorId 无效 | 让托管方检查 ID 是否正确 |