做为一名刚入门的 iOS 开发者,前阵子稍稍研究了一下最新发布的 ARKit,而后结合几个其余开源项目作成了一个 ARGitHubCommits。前天在上海第 8 次 T 沙龙上分享了一个《ARKit 初探》的 topic,如今将它写成文章,以便浏览。node
下面是苹果开发者官网 ARKit 页面的一段介绍:git
iOS 11 引入了新的 ARKit 框架,让您轻松建立无可比拟的 iPhone 和 iPad 加强现实体验。 经过将数字对象和信息与您周围的环境相融合,ARKit 为 App 解开了屏幕之缚,带领着它们跨越屏幕的界限,让它们以全新的方式与现实世界交流互动。github
可见,苹果在 AR 的市场上应该是作了不少准备。不只有本文要介绍的 ARKit,在最新发布的 iPhone X 中也对摄像头作了优化,配备了前置景深摄像头,将 AR 和面部识别结合起来。因此,AR 可能将会是将来几年内的一个重要发展方向。算法
苹果在硬件上也作了一些努力。咱们能够从官网的介绍中得出如下几个信息:bash
固然,要运行 ARKit,在硬件上也有一些要求。必定是要具有 A9 及以上的处理器(iPhone 6s 为 A9 处理器)的设备才能够运行 AR。软件上,若是要开发 ARKit App,那么要有 Xcode 9 和 iOS 11 SDK。session
当你作好了一切准备,那就让咱们进入 ARKit 的世界!app
上图解释的是 ARKit 的工做流程。其中蓝色表示 ARKit 负责的部分,绿色表示 SceneKit 负责的部分。固然,创建虚拟世界也可使用其余的框架,好比 SpriteKit、Metal,本文将以 SceneKit 为例子进行讲解。框架
因而可知,ARKit 主要作的事是:捕捉现实世界信息、将现实和虚拟世界混合渲染、而且时刻处理新的信息或者进行互动。优化
理解了 AR 的工做流程后,让咱们来看看 ARKit 中一些重要的类的职责。ui
上面是 ARKit 和 SceneKit 的关键的类的关系图。其中 ARSCNView 是继承自 SCNView 的,因此其中关于 3D 物体的属性、方法都是 SCNView 的(如 SCNScene、SCNNode 等)。
下面简单介绍一下 ARKit 中各个类是如何协做的。
最顶层的 ARSCNView 主要负责综合虚拟世界(SceneKit)的信息和现实世界的信息(由ARSession 类负责采集),而后将它们综合渲染呈现出一个 AR 世界。
ARSession 类负责采集现实世界的信息。这一行为也被称做__世界追踪__。它主要的职责是:
它采集到的现实世界信息以 ARFrame 的形式返回。
固然,为了有一个比较好的追踪效果,要知足如下要求:
总的说来,就是要提示用户移动手机,且速度不能太快,要在略微复杂的场景中探测。
ARFrame 包含了两部分信息:ARAnchor 和 ARCamera。其中,
指的是 ARSession 将如何追踪世界,有如下几种子类:
并且,若是要开启平面检测,须要加入如下语句:
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
复制代码
用一段话总结的话,就是 ARSCNView 结合 SCNScene 中的虚拟世界信息和 ARsession 捕捉到的现实世界信息,渲染出 AR 世界。ARConfiguration 指导 ARSession 如何追踪世界,追踪的结果以 ARFrame 返回。ARFrame 中的 ANAnchor 信息为 SceneKit 中的 SCNNode 提供了一些放置的点,以便将虚拟节点和现实锚点绑定。
先介绍 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)
复制代码
以上方法会在为一个锚点已经添加、将要更新、已经更新、已经移除一个虚拟锚点时进行回调。
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 的方法中除了锚点之外,还包含节点信息,这可让咱们有更多的信息进行处理。
下面就几种经常使用场景给出一些示例代码。
添加物体能够有如下两种方式:自动检测并添加或者手动点击添加。
主要利用了 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 的转换方法。
有了以上的知识基础,咱们能够用如下思路来构建这个项目:
具体的代码,欢迎参考GitHub。
下面是一些能够参考的文章/GitHub连接,仅供参考: