你有没有想过一个问题:AR 应用是怎么知道手机在现实世界里移动了多少、转了多少度的?
比如说你在玩一个 AR 游戏,把一个虚拟小人放在桌面上,然后你拿着手机绕桌子走一圈。小人始终稳稳地站在那个位置,不会乱跑。这背后靠的就是 SLAM 运动跟踪。
简单说,SLAM(Simultaneous Localization and Mapping)就是让手机一边"画地图"(识别周围环境),一边"定位自己"(知道自己在哪)。这两个事情是同时做的,所以叫"同时定位与建图"。
SLAM 运动跟踪工作原理
SLAM(同时定位与建图)的工作流程可以概括为以下几个阶段:
#mermaid-svg-cx4GqaYM7cz4sTZ5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .error-icon{fill:#552222;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .marker.cross{stroke:#333333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cx4GqaYM7cz4sTZ5 p{margin:0;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .cluster-label text{fill:#333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .cluster-label span{color:#333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .cluster-label span p{background-color:transparent;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .label text,#mermaid-svg-cx4GqaYM7cz4sTZ5 span{fill:#333;color:#333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .node rect,#mermaid-svg-cx4GqaYM7cz4sTZ5 .node circle,#mermaid-svg-cx4GqaYM7cz4sTZ5 .node ellipse,#mermaid-svg-cx4GqaYM7cz4sTZ5 .node polygon,#mermaid-svg-cx4GqaYM7cz4sTZ5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .rough-node .label text,#mermaid-svg-cx4GqaYM7cz4sTZ5 .node .label text,#mermaid-svg-cx4GqaYM7cz4sTZ5 .image-shape .label,#mermaid-svg-cx4GqaYM7cz4sTZ5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .rough-node .label,#mermaid-svg-cx4GqaYM7cz4sTZ5 .node .label,#mermaid-svg-cx4GqaYM7cz4sTZ5 .image-shape .label,#mermaid-svg-cx4GqaYM7cz4sTZ5 .icon-shape .label{text-align:center;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .node.clickable{cursor:pointer;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .arrowheadPath{fill:#333333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cx4GqaYM7cz4sTZ5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cx4GqaYM7cz4sTZ5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cx4GqaYM7cz4sTZ5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .cluster text{fill:#333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .cluster span{color:#333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cx4GqaYM7cz4sTZ5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .icon-shape,#mermaid-svg-cx4GqaYM7cz4sTZ5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .icon-shape p,#mermaid-svg-cx4GqaYM7cz4sTZ5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .icon-shape .label rect,#mermaid-svg-cx4GqaYM7cz4sTZ5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cx4GqaYM7cz4sTZ5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cx4GqaYM7cz4sTZ5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cx4GqaYM7cz4sTZ5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 正常
移动过快
特征太少
启动 AR 会话
摄像头采集图像
提取环境特征点
构建点云数据
识别平面
创建锚点
持续跟踪手机位姿
跟踪状态?
更新锚点位置
EXCESSIVE_MOTION
INSUFFICIENT_FEATURES
提示用户慢速移动
提示用户换环境
回调通知应用
怎么开启 SLAM 跟踪?
要让 AR Engine 开启 SLAM 能力,你需要做两件事:
- 配置 AR 类型为
WORLD(环境追踪) - 通过
ARViewContext初始化并启动 AR 会话
先看配置部分。你需要创建一个 ARConfig 对象,告诉 AR Engine 你要用什么能力:
typescript
import { arEngine, arViewController } from '@kit.AREngine';
let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.config = {
type: arEngine.ARType.WORLD,
poseMode: arEngine.ARPoseMode.GRAVITY_AND_HEADING,
powerMode: arEngine.ARPowerMode.POWER_SAVING,
depthMode: arEngine.ARDepthMode.AUTOMATIC
};
这里每一行配置都值得说说:
type: arEngine.ARType.WORLD:这是最关键的,告诉 AR Engine "我要做环境追踪"。如果你写成FACE或BODY,那就变成人脸追踪或人体追踪了,跟 SLAM 没关系。poseMode: arEngine.ARPoseMode.GRAVITY_AND_HEADING:这个决定世界坐标系怎么建立。GRAVITY_AND_HEADING表示 Y 轴跟重力方向一致,X 轴指向指南针北向。打个比方,就像你在地上画了一个有"东南西北"的坐标系,手机在这个坐标系里移动。powerMode: arEngine.ARPowerMode.POWER_SAVING:省电模式。AR 运算很吃性能,省电模式会让手机不那么烫,但精度可能会稍低。如果你做的是对精度要求高的 AR 测量工具,可以换成PERFORMANCE_FIRST。depthMode: arEngine.ARDepthMode.AUTOMATIC:开启深度估计。AR Engine 会自动尝试获取场景的深度信息,这对放置虚拟物体很有帮助。
配置好之后,初始化 AR 会话:
typescript
await context.init();
这一步会启动摄像头、初始化传感器,开始 SLAM 追踪。调用之后,AR Engine 就开始默默地在后台做两件事:跟踪手机的姿态(位置和朝向),以及检测环境中的平面。
怎么获取手机的实时位置?
SLAM 跟踪的核心产出就是手机的位姿(Pose)------也就是手机在 3D 空间中的位置和朝向。
要拿到这个信息,你需要在每一帧的回调里去读取。AR Engine 提供了 onFrameUpdate 回调,每一帧渲染前都会触发:
typescript
import { arEngine, arViewController } from '@kit.AREngine';
import { Node } from '@kit.ArkGraphics3D';
class ARViewCallbackImpl extends arViewController.ARViewCallback {
onAnchorAdd(ctx: arViewController.ARViewContext, node: Node, anchor: arEngine.ARAnchor): void {
console.info('onAnchorAdd');
console.info(`add anchor id = ${String(anchor.id)}`);
console.info(`add anchor translation = ${anchor.getPose().translation}`);
console.info(`add node pose = ${node.position}`);
}
onAnchorUpdate(ctx: arViewController.ARViewContext, node: Node, anchor: arEngine.ARAnchor): void {
console.info('onAnchorUpdate');
console.info(`update anchor id = ${String(anchor.id)}`);
console.info(`update anchor translation = ${anchor.getPose().translation}`);
console.info(`update node pose = ${node.position}`);
}
async onFrameUpdate(ctx: arViewController.ARViewContext, sysBootTs: number): Promise<void> {
let arSession: arEngine.ARSession | undefined = ctx.session;
if (arSession) {
let frame: arEngine.ARFrame = arSession.getFrame();
if (!frame) {
console.error('Failed to get arSession.frame, it is undefined or null');
} else {
console.info(`Succeeded in getting arSession.frame = ${frame.timestamp}`);
await frame.release();
}
} else {
console.error('Failed to get arSession, arSession is undefined');
}
}
}
let context: arViewController.ARViewContext = new arViewController.ARViewContext();
context.callback = new ARViewCallbackImpl();
这段代码看起来挺长,但其实做的事情很清晰。我们继承了 arViewController.ARViewCallback,然后重写了三个回调方法:
onAnchorAdd :当 AR Engine 检测到一个新的平面时,会自动创建一个锚点(Anchor)和对应的场景节点(Node),然后触发这个回调。你在回调里可以拿到锚点的位姿信息,比如 anchor.getPose().translation 就是这个锚点在 3D 空间中的坐标。这对 AR 导航很有用------当用户走到一个新的位置,你能知道那个位置在哪。
onAnchorUpdate:锚点的位置会随着手机的移动不断更新。每更新一次就触发这个回调。你可以在这里拿到最新的位姿,用来更新虚拟物体的位置。
onFrameUpdate :每一帧都会触发,这是你做实时渲染的地方。你可以在这一帧里获取 ARFrame,里面有当前帧的时间戳、相机位姿等信息。注意用完之后要调用 frame.release() 释放资源,不然内存会一直涨。
锚点是怎么回事?
你可能会问:我只想知道手机在哪,为什么要搞锚点?
打个比方。你站在一个空旷的房间里,四面都是白墙,没有参照物。这时候你闭上眼睛原地转一圈,再睁开眼,你很难判断自己到底转了多少度。但如果墙上有个标记,你就能清楚地知道自己转了多少。
锚点就是那个"标记"。它是 AR Engine 在现实世界中识别到的一个固定参考点。有了锚点,虚拟物体才能"钉"在现实世界中不动。
你可以手动创建锚点。首先拿到当前相机的位姿,然后在那个位置创建一个锚点:
typescript
import { Quaternion, Vec3 } from '@kit.ArkGraphics3D';
import { arEngine } from '@kit.AREngine';
let r: Quaternion = {
x: 0,
y: 0,
z: 0,
w: 0
}
let t: Vec3 = { x: 0, y: 0, z: 0 };
let pose: arEngine.ARPose = arEngine.createARPose(r, t);
// arSession创建参考ARSession.getFrame接口示例代码
arSession.createAnchor(pose);
这里 arEngine.createARPose(r, t) 创建了一个位姿对象,r 是旋转(用四元数表示),t 是平移(用 3D 向量表示)。然后 arSession.createAnchor(pose) 在这个位姿处创建一个锚点。
创建好的锚点会被 AR Engine 持续追踪。即使你移开手机再回来,AR Engine 也能认出这个锚点还在原来的位置。
SLAM 跟踪失败了怎么办?
SLAM 跟踪不是万能的。有时候会失败,主要有两个原因:
- 手机移动太快 (
EXCESSIVE_MOTION):你拿着手机乱甩,摄像头看到的画面模糊了,SLAM 算法就懵了。解决办法是让用户慢慢移动手机。 - 环境特征太少 (
INSUFFICIENT_FEATURES):你对着一面纯白的墙拍,或者在很暗的环境里,SLAM 找不到足够的特征点来判断位置。解决办法是换个有纹理的地方,或者开灯。
你可以通过 ARFrame 的 getCamera() 方法拿到相机对象,然后检查跟踪状态。如果状态是 PAUSED,说明跟踪暂停了,可能需要提示用户调整手机的位置。
坐标系转换流程
AR Engine 和 3D 渲染引擎使用不同的坐标系,需要进行转换:
#mermaid-svg-06CNgg2B10bJIMpg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-06CNgg2B10bJIMpg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-06CNgg2B10bJIMpg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-06CNgg2B10bJIMpg .error-icon{fill:#552222;}#mermaid-svg-06CNgg2B10bJIMpg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-06CNgg2B10bJIMpg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-06CNgg2B10bJIMpg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-06CNgg2B10bJIMpg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-06CNgg2B10bJIMpg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-06CNgg2B10bJIMpg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-06CNgg2B10bJIMpg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-06CNgg2B10bJIMpg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-06CNgg2B10bJIMpg .marker.cross{stroke:#333333;}#mermaid-svg-06CNgg2B10bJIMpg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-06CNgg2B10bJIMpg p{margin:0;}#mermaid-svg-06CNgg2B10bJIMpg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-06CNgg2B10bJIMpg .cluster-label text{fill:#333;}#mermaid-svg-06CNgg2B10bJIMpg .cluster-label span{color:#333;}#mermaid-svg-06CNgg2B10bJIMpg .cluster-label span p{background-color:transparent;}#mermaid-svg-06CNgg2B10bJIMpg .label text,#mermaid-svg-06CNgg2B10bJIMpg span{fill:#333;color:#333;}#mermaid-svg-06CNgg2B10bJIMpg .node rect,#mermaid-svg-06CNgg2B10bJIMpg .node circle,#mermaid-svg-06CNgg2B10bJIMpg .node ellipse,#mermaid-svg-06CNgg2B10bJIMpg .node polygon,#mermaid-svg-06CNgg2B10bJIMpg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-06CNgg2B10bJIMpg .rough-node .label text,#mermaid-svg-06CNgg2B10bJIMpg .node .label text,#mermaid-svg-06CNgg2B10bJIMpg .image-shape .label,#mermaid-svg-06CNgg2B10bJIMpg .icon-shape .label{text-anchor:middle;}#mermaid-svg-06CNgg2B10bJIMpg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-06CNgg2B10bJIMpg .rough-node .label,#mermaid-svg-06CNgg2B10bJIMpg .node .label,#mermaid-svg-06CNgg2B10bJIMpg .image-shape .label,#mermaid-svg-06CNgg2B10bJIMpg .icon-shape .label{text-align:center;}#mermaid-svg-06CNgg2B10bJIMpg .node.clickable{cursor:pointer;}#mermaid-svg-06CNgg2B10bJIMpg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-06CNgg2B10bJIMpg .arrowheadPath{fill:#333333;}#mermaid-svg-06CNgg2B10bJIMpg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-06CNgg2B10bJIMpg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-06CNgg2B10bJIMpg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-06CNgg2B10bJIMpg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-06CNgg2B10bJIMpg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-06CNgg2B10bJIMpg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-06CNgg2B10bJIMpg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-06CNgg2B10bJIMpg .cluster text{fill:#333;}#mermaid-svg-06CNgg2B10bJIMpg .cluster span{color:#333;}#mermaid-svg-06CNgg2B10bJIMpg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-06CNgg2B10bJIMpg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-06CNgg2B10bJIMpg rect.text{fill:none;stroke-width:0;}#mermaid-svg-06CNgg2B10bJIMpg .icon-shape,#mermaid-svg-06CNgg2B10bJIMpg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-06CNgg2B10bJIMpg .icon-shape p,#mermaid-svg-06CNgg2B10bJIMpg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-06CNgg2B10bJIMpg .icon-shape .label rect,#mermaid-svg-06CNgg2B10bJIMpg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-06CNgg2B10bJIMpg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-06CNgg2B10bJIMpg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-06CNgg2B10bJIMpg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 重力对齐
AR Engine 坐标系
手机位姿 Pose
transformPose 转换
AGP 渲染坐标系
放置虚拟物体
坐标系模式
GRAVITY 重力对齐
GRAVITY_AND_HEADING 含方向
Y轴=重力方向
Y轴=重力方向, X轴=北方
世界坐标系的理解
SLAM 建立的世界坐标系有两种模式:
GRAVITY:Y 轴跟重力方向一致,原点在手机启动时的位置。这是最常用的模式,适合大多数 AR 应用。GRAVITY_AND_HEADING:在GRAVITY的基础上,X 轴指向指南针北向。适合 AR 导航这类需要方向感的场景。
需要注意的是,GRAVITY_AND_HEADING 目前只支持省电模式。所以如果你用了这个模式,记得把 powerMode 也设成 POWER_SAVING。
另外,AR Engine 的世界坐标系跟 3D 渲染引擎(AGP)的坐标系不一样。AR Engine 用的是重力对齐坐标系,AGP 用的是自己的世界坐标系。如果你要在 3D 场景里放虚拟物体,需要做一次坐标转换。ARViewContext 提供了 transformPose 方法来帮你做这个事情:
typescript
import { arViewController } from '@kit.AREngine';
import { Vec3, Quaternion } from '@kit.ArkGraphics3D';
let context: arViewController.ARViewContext = new arViewController.ARViewContext();
let pose: Vec3 = { x: 1.0, y: -1.0, z: -0.5 };
let rot: Quaternion = {
x: -0.1,
y: 0.2,
z: -0.3,
w: 0.5
};
context.transformPose(pose, rot);
把 AR 坐标系的位姿传进去,它会返回转换后在 AGP 渲染坐标系中的位姿。这样你就能正确地在 3D 场景里放置虚拟物体了。
点云数据怎么看?
SLAM 在跟踪的同时,还会提取环境中的特征点。这些特征点组成一个点云(Point Cloud)。
你可以通过 ARFrame 获取当前帧的点云数据。点云里的每个点都有 3D 坐标和一个置信度值。置信度越高,说明这个特征点越可靠。
点云数据在很多场景下很有用。比如你想做一个 AR 标注工具,让用户在空中画线,你就需要知道用户手指指向的 3D 位置,这时候点云就能帮上忙。或者你想在 AR 场景里做遮挡效果(虚拟物体被真实物体挡住),也需要用到深度信息,而点云就是深度信息的一种来源。
平面检测是 SLAM 的延伸
SLAM 追踪还有一个很重要的"副产品"------平面检测。
AR Engine 在做 SLAM 的时候,会从点云中识别出哪些点在一个平面上(比如地面、桌面、墙面),然后把这些点拟合成一个平面。这就是平面检测。
你可以在配置里指定要检测哪种平面:
HORIZONTAL:只检测水平面,比如地面和桌面VERTICAL:只检测竖直面,比如墙壁HORIZONTAL_AND_VERTICAL:两个都检测
默认是 HORIZONTAL_AND_VERTICAL,对大多数 AR 应用来说够用了。
当检测到平面后,AR Engine 会自动创建锚点,然后通过 onAnchorAdd 回调告诉你。你就可以在这些平面上放置虚拟物体了。想象一下,用户打开 AR 家具 APP,把手机对着地面扫一圈,AR Engine 就能识别出地面,然后你就可以在地面上摆一个虚拟沙发。
实际应用场景
说了这么多 API,来看看 SLAM 运动跟踪能做什么:
AR 室内导航:在商场里,用户打开 APP,手机摄像头对着周围环境扫一圈。SLAM 会建立一个环境地图,然后持续跟踪用户的位置。你可以在地图上标注路线,用箭头引导用户走到目的地。
AR 家具摆放:用户打开 APP,扫描房间,SLAM 识别出地面和桌面,用户就可以在这些平面上放虚拟家具,看看摆在家里好不好看。
AR 游戏:SLAM 识别出桌面后,你可以在桌面上做一个虚拟棋盘,用户围着桌子走,棋盘始终在桌面上。或者在地面上做一个虚拟跑步机,用户在 AR 世界里跑来跑去。
SLAM 运动跟踪是 AR 的基础能力。几乎所有 AR 应用都离不开它------不管是 AR 导航、AR 游戏还是 AR 测量工具,都需要先知道手机在 3D 空间中的位置,才能把虚拟内容放到正确的地方。