心理学哲学批判性思维 2017-12-16
作为一名刚入门的 iOS 开发者,前阵子稍稍研究了一下最新发布的 ARKit,然后结合几个其他开源项目做成了一个 ARGitHubCommits。前天在上海第 8 次 T 沙龙上分享了一个《ARKit 初探》的 topic,现在将它写成文章,以便浏览。
下面是苹果开发者官网 ARKit 页面的一段介绍:
iOS 11 引入了新的 ARKit 框架,让您轻松创建无可比拟的 iPhone 和 iPad 增强现实体验。 通过将数字对象和信息与您周围的环境相融合,ARKit 为 App 解开了屏幕之缚,带领着它们跨越屏幕的界限,让它们以全新的方式与现实世界交流互动。
可见,苹果在 AR 的市场上应该是做了很多准备。不仅有本文要介绍的 ARKit,在最新发布的 iPhone X 中也对摄像头做了优化,配备了前置景深摄像头,将 AR 和面部识别结合起来。所以,AR 可能将会是未来几年内的一个重要发展方向。
苹果在硬件上也做了一些努力。我们可以从官网的介绍中得出以下几个信息:
当然,要运行 ARKit,在硬件上也有一些要求。一定是要具备 A9 及以上的处理器(iPhone 6s 为 A9 处理器)的设备才可以运行 AR。软件上,如果要开发 ARKit App,那么要有 Xcode 9 和 iOS 11 SDK。
当你做好了一切准备,那就让我们进入 ARKit 的世界!
上图解释的是 ARKit 的工作流程。其中蓝色表示 ARKit 负责的部分,绿色表示 SceneKit 负责的部分。当然,建立虚拟世界也可以使用其他的框架,比如 SpriteKit、Metal,本文将以 SceneKit 为例子进行讲解。
由此可见,ARKit 主要做的事是:捕捉现实世界信息、将现实和虚拟世界混合渲染、并且时刻处理新的信息或者进行互动。
理解了 AR 的工作流程后,让我们来看看 ARKit 中一些重要的类的职责。
上面是 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链接,仅供参考: