v2panda的技术专题iOS && AndroidiOS低频高技收集

ARKit 初探

2017-09-21  本文已影响1575人  滑滑鸡

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

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

3. AR 工作流程

AR 工作流程

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

  1. 首先,ARKit 利用摄像头拍摄现实场景的画面,然后 SceneKit 用来建立虚拟世界。
  2. 建立好了以后,ARKit 负责将现实世界和虚拟世界的信息融合,并渲染出一个 AR 世界。
  3. 在渲染的同时,ARKit 要负责以下三件事:

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

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

4. ARKit 和 SceneKit 关系图

ARKit 和 SceneKit 关系图

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

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

ARSCNView

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

ARSession

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

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

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

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

ARFrame

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

ARAnchor

ARConfiguration

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

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

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 类,它可以检测用户手指点击的地方有没有经过一些符合要求的点/面,有如下几种选项:

上面一段代码的含义是:首先记录用户点击的位置,然后判断有没有点击到特征点,并将结果按从近到远的顺序返回。如果有最近的一个结果,就生成一个长宽高都为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链接,仅供参考:

微博:@滑滑鸡

GitHub:songkuixi

上一篇 下一篇

猜你喜欢

热点阅读