ARKit 初探

0. 前言

作为一名刚入门的 iOS 开发者,前阵子稍稍研究了一下最新发布的 ARKit,然后结合几个其他开源项目做成了一个 ARGitHubCommits。前天在上海第 8 次 T 沙龙上分享了一个《ARKit 初探》的 topic,现在将它写成文章,以便浏览。

1. ARKit 简介

下面是苹果开发者官网 ARKit 页面的一段介绍:

iOS 11 引入了新的 ARKit 框架,让您轻松创建无可比拟的 iPhone 和 iPad 增强现实体验。 通过将数字对象和信息与您周围的环境相融合,ARKit 为 App 解开了屏幕之缚,带领着它们跨越屏幕的界限,让它们以全新的方式与现实世界交流互动。

可见,苹果在 AR 的市场上应该是做了很多准备。不仅有本文要介绍的 ARKit,在最新发布的 iPhone X 中也对摄像头做了优化,配备了前置景深摄像头,将 AR 和面部识别结合起来。所以,AR 可能将会是未来几年内的一个重要发展方向。

2. 设备要求

苹果在硬件上也做了一些努力。我们可以从官网的介绍中得出以下几个信息:

  • 建立在优秀的硬件设施和算法上,ARKit 采集的现实世界数据相对来说比较精准(比 Google 的 ARCore 要好些)。
  • 可以利用 ARKit 来探测水平面,然后可以在水平面上放置小的物体。
  • ARKit 会根据周围光线的亮度自动调节虚拟物体的亮度和阴影、纹理等信息。

当然,要运行 ARKit,在硬件上也有一些要求。一定是要具备 A9 及以上的处理器(iPhone 6s 为 A9 处理器)的设备才可以运行 AR。软件上,如果要开发 ARKit App,那么要有 Xcode 9 和 iOS 11 SDK。

当你做好了一切准备,那就让我们进入 ARKit 的世界!

3. AR 工作流程

ARKit 初探

上图解释的是 ARKit 的工作流程。其中蓝色表示 ARKit 负责的部分,绿色表示 SceneKit 负责的部分。当然,建立虚拟世界也可以使用其他的框架,比如 SpriteKit、Metal,本文将以 SceneKit 为例子进行讲解。

  1. 首先,ARKit 利用摄像头拍摄现实场景的画面,然后 SceneKit 用来建立虚拟世界。
  2. 建立好了以后,ARKit 负责将现实世界和虚拟世界的信息融合,并渲染出一个 AR 世界。
  3. 在渲染的同时,ARKit 要负责以下三件事:
  • 维持世界追踪指的是当你移动摄像头,要去获取新的现实世界的信息。
  • 进行场景解析指的是解析现实世界中有无特征点、平面等关键信息。
  • 处理与虚拟世界的互动指的是当用户点击或拖动屏幕时,处理有没有点击到虚拟物体或者要不要进行添加/删除物体的操作。

由此可见,ARKit 主要做的事是:捕捉现实世界信息、将现实和虚拟世界混合渲染、并且时刻处理新的信息或者进行互动

理解了 AR 的工作流程后,让我们来看看 ARKit 中一些重要的类的职责。

4. ARKit 和 SceneKit 关系图

ARKit 初探

上面是 ARKit 和 SceneKit 的关键的类的关系图。其中 ARSCNView 是继承自 SCNView 的,所以其中关于 3D 物体的属性、方法都是 SCNView 的(如 SCNScene、SCNNode 等)。

下面简单介绍一下 ARKit 中各个类是如何协作的。

ARSCNView

最顶层的 ARSCNView 主要负责综合虚拟世界(SceneKit)的信息和现实世界的信息(由ARSession 类负责采集),然后将它们综合渲染呈现出一个 AR 世界。

ARSession

ARSession 类负责采集现实世界的信息。这一行为也被称作__世界追踪__。它主要的职责是:

  • 追踪设备的位置以及旋转,这里的两个信息均是相对于设备起始时的信息。
  • 追踪物理距离(以“米”为单位),例如 ARKit 检测到一个平面,我们希望知道这个平面有多大。
  • 追踪我们手动添加的希望追踪的点,例如我们手动添加的一个虚拟物体。

它采集到的现实世界信息以 ARFrame 的形式返回。

当然,为了有一个比较好的追踪效果,要满足以下要求:

  • 运动传感器不能停止工作。如果运动传感器停止了工作,那么就无法拿到设备的运动信息。根据我们之前提到的世界追踪的工作原理,毫无疑问,追踪质量会下降甚至无法工作。
  • 真实世界的场景需要有一定特征点可追踪。世界追踪需要不断分析和追踪捕捉到的图像序列中特征点,如果图像是一面白墙,那么特征点非常少,那么追踪质量就会下降。
  • 设备移动速度不能过快。如果设备移动太快,那么 ARKit 无法分析出不同图像帧之中的特征点的对应关系,也会导致追踪质量下降。

总的说来,就是要提示用户移动手机,且速度不能太快,要在略微复杂的场景中探测

ARFrame

ARFrame 包含了两部分信息:ARAnchor 和 ARCamera。其中,

  • ARCamera 指的是当前摄像机的位置和旋转信息。这一部分 ARKit 已经为我们配置好,不用特别配置。
  • ARAnchor 指的是现实世界中的__锚点__,具体解释如下:

ARAnchor

  • 可以把 ARAnchor(锚点)理解为真实世界中的某个点或平面,anchor 中包含位置信息和旋转信息。拿到 anchor 后,可以在该 anchor 处放置一些虚拟物体。
  • 与 SCNNode 可以绑定
  • 它有一个子类:ARPlaneAnchor,专门指的是一个代表水平面的锚点。

ARConfiguration

指的是 ARSession 将如何追踪世界,有以下几种子类:

  • ARWorldTrackingConfiguration(6 DOF)是 ARSession 的默认配置,以6个自由度(x y z轴上的位移及绕着三个轴的旋转)追踪现实锚点和虚拟物体。
  • AROrientationTrackingConfiguration(3 DOF)以 3 个自由度(没有位移,只有旋转)追踪,但是被苹果文档声明不推荐使用。这样的追踪质量将比较差。
  • ARFaceTrackingConfiguration(iPhone X Only)以面部识别来追踪,只有装备了 True Depth 前置景深摄像头的 iPhone X 才能使用。

而且,如果要开启平面检测,需要加入以下语句:

let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal

总结

用一段话总结的话,就是 ARSCNView 结合 SCNScene 中的虚拟世界信息和 ARsession 捕捉到的现实世界信息,渲染出 AR 世界。ARConfiguration 指导 ARSession 如何追踪世界,追踪的结果以 ARFrame 返回。ARFrame 中的 ANAnchor 信息为 SceneKit 中的 SCNNode 提供了一些放置的点,以便将虚拟节点和现实锚点绑定。

5. ARKit 中的 Delegate

ARSCNViewDelegate

先介绍 ARSCNView 的代理:ARSCNViewDelegate,他有以下几个回调方法。

func renderer(SCNSceneRenderer, nodeFor: ARAnchor)

当 ARSession 检测到一个锚点时,可以在这个回调方法中决定是否给它返回一个 SCNNode。默认是返回一个空的 SCNNode(),我们可以根据自己的需要将它改成只在检测到平面锚点(ARPlaneAnchor)时返回一个锚点,诸如此类。

func renderer(SCNSceneRenderer, didAdd: SCNNode, for: ARAnchor)
func renderer(SCNSceneRenderer, willUpdate: SCNNode, for: ARAnchor)
func renderer(SCNSceneRenderer, didUpdate: SCNNode, for: ARAnchor)
func renderer(SCNSceneRenderer, didRemove: SCNNode, for: ARAnchor)

以上方法会在为一个锚点已经添加、将要更新、已经更新、已经移除一个虚拟锚点时进行回调。

ARSessionDelegate

ARSession 类也有自己的代理:ARSessionDelegate

func session(ARSession, didUpdate: ARFrame)

在 ARKit 中,当用户移动手机时,会实时更新很多 ARFrame。这个方法会在更新了 ARFrame 时,进行回调。它可以用于类似于__始终想维持一个虚拟物体在屏幕中间__的场景,只需要在这个方法中将该节点的位置更新为最新的 ARFrame 的中心点即可。

func session(ARSession, didAdd: [ARAnchor])
func session(ARSession, didUpdate: [ARAnchor])
func session(ARSession, didRemove: [ARAnchor])

如果使用了 ARSCNViewDelegate 或 ARSKViewDelegate,那上面三个方法不必实现。因为另外的 Delegate 的方法中除了锚点以外,还包含节点信息,这可以让我们有更多的信息进行处理。

6. 一些实践

下面就几种常用场景给出一些示例代码。

添加物体

添加物体可以有以下两种方式:自动检测并添加或者手动点击添加。

自动检测并添加

主要利用了 ARSCNViewDelegate 中的回调方法:

func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
    if let planeAnchor = anchor as? ARPlaneAnchor {
        let node = SCNNode()
        node.geometry = SCNBox(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.y), length: CGFloat(planeAnchor.extent.z), chamferRadius: 0)
        return node
    }
    return nil
}

这段代码的含义是:如果找到了一个平面锚点,那就返回一个和该平面锚点的长宽高分别相同的白色长方体节点。当你移动手机寻找平面时,一旦找到,便会有一个白色平面出现在屏幕上。

手动点击添加

ARKit 允许用户在画面中点击,来和虚拟世界互动。比如我们之前添加了一个 UITapGestureRecognizer,selector 是如下方法:

@objc func didTap(_ sender: UITapGestureRecognizer) {
    let location = sender.location(in: sceneView)
    let hitResults = sceneView.hitTest(location, types: .featurePoint)
    if let result = hitResults.first {
        let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
        let boxNode = SCNNode(geometry: box)
        boxNode.position = SCNVector3(x: result.worldTransform.columns.3.x,
                                  y: result.worldTransform.columns.3.y,
                                  z: result.worldTransform.columns.3.z)
        sceneView.scene.rootNode.addChildNode(boxNode)
    }
}

这其中用到了一个 ARHitTestResult 类,它可以检测用户手指点击的地方有没有经过一些符合要求的点/面,有如下几种选项:

  • featurePoint返回当前图像中 Hit-testing 射线经过的 3D 特征点。
  • estimatedHorizontalPlane返回当前图像中 Hit-testing 射线经过的预估平面。
  • existingPlaneUsingExtent返回当前图像中 Hit-testing 射线经过的有大小范围的平面。
  • existingPlane返回当前图像中 Hit-testing 射线经过的无限大小的平面。

上面一段代码的含义是:首先记录用户点击的位置,然后判断有没有点击到特征点,并将结果按从近到远的顺序返回。如果有最近的一个结果,就生成一个长宽高都为0.1米的立方体,并把它放在那个特征点上。

其中将 ARHitTestResult 信息转换成三维坐标,用到了 result.worldTransform.columns.3.x(y,z)的信息。我们不深究其中原理,只需知道它的转换方法就可以了。

更新物体位置

这时可以使用 ARSessionDelegate 的代理方法:

func session(_ session: ARSession, didUpdate frame: ARFrame) {
    if boxNode != nil {
        let mat = frame.camera.transform.columns.3
        boxNode?.position = SCNVector3Make((mat.x) * 3, (mat.y) * 3, (mat.z) * 3 - 0.5)
    }
}

也就是当更新了一个 ARFrame,就把一个之前建立好的 SCNNode 的位置更新为 frame 的中心点。这里 * 3 是为了放大移动的效果。注意,这里也用到了上面所说的 worldTransform 和 SCNVector3 的转换方法。

7. ARGitHubCommits 思路

有了以上的知识基础,我们可以用以下思路来构建这个项目:

  1. 获取 GitHub 的 Commits 数据。
  2. 建立 ARSCNView,开始 ARSession。
  3. 提示用户移动手机,探测水平面。
  4. 探测成功后,在 ARSCNView 中的 SCNScene 中加入各个 SCNNode。

具体的代码,欢迎参考GitHub。

8. 参考

下面是一些可以参考的文章/GitHub链接,仅供参考:

  • https://developer.apple.com/cn/arkit/
  • https://developer.apple.com/documentation/arkit
  • http://blog.csdn.net/u013263917/article/details/72903174
  • https://mp.weixin.qq.com/s/DxPHo6j6pJQuhdXM_5K4qw
  • https://github.com/olucurious/Awesome-ARKit
  • https://github.com/songkuixi/ARGitHubCommits

微博:@滑滑鸡

GitHub:songkuixi

相关推荐