简单使用 ARKit
概览
什么是 AR?它不同于 VR,VR 是一种沉浸式体验,你眼前的一切都是虚拟的,根本不需要理会现实世界(当然,如果体验 VR 的地方很小,你可能还需要注意一下走位,免得发生意外),VR 是 Virtual Reality,也就是虚拟现实,而 AR 是 Augmented Reality,增强现实。AR 是对现实世界的一种补充,一种增强,你的大部分精力还是放在现实世界中。AR 被用来丰富化现实世界的图像,例如在一张什么都没有的桌子上显示出一个城堡,又或者在一面干干净净的墙面上绘制涂鸦或摆放一面电视,但不同于普通的 PS 照片,AR 可以是动态的,通过 AR 引擎创造出来的虚拟物体会根据人物的移动而自动调整位置,也就是说你从桌子的这个角走到另一个角,城堡依然在桌子上,位置不变,角度不变,甚至光照也不变,就好似真的有一座城堡在桌子上。
AR 和 VR 都是创造虚拟图像的技术,但前者是基于现实世界的,没有现实世界的图像,AR 创造出来的图像是没什么意义的,而 VR 是完全虚拟的,它会在虚拟世界中虚拟出一面桌子,然后在虚拟的桌子上在创造一个虚拟的城堡。
对于 AR 这样一个神奇的技术,Apple 在 WWDC 2017 上顺势推出了 AR 引擎和开发框架,这个框架就叫做 ARKit
ARKitApple 的 ARKit 可以将你创造的 2D 平面对象和 3D 立体对象,通过 iOS 设备的摄像头映射到现实世界中,透过屏幕你就能在当前周围的环境里看到你创造的对象 。
你可以通过 Apple 的 Metal、SpriteKit 和 SceneKit 来创造你的精灵,你也可以通过强大的第三方引擎例如 Unity、Unreal Engine 为你的 AR app 创造模型。
ARKit 基于 Visual Inertial Odometry (VIO) 技术实现,利用 iPhone 和 iPad 的各种传感器提供的数据来计算你的当前位置,同时更新虚拟物体的相对位置,还可以寻找到相对水平的平面,例如桌子。所以这需要庞大的计算力,Apple 为此也做了一定限制:
- 如果要使用 worldTeacking,你的设备所使用的处理器必须大于 A9 处理器
下面就让我们一起来体验一下这神奇的框架吧!
使用 ARKit
在一切开始前,你需要打开最新的 Xcode 9.0 beta 版,然后新建一个工程:
1.新建一个 Augmented Reality App
2.填写 App 的 Bundle 名,在 Content Technology 一栏选择 SceneKit,这表示我们将使用 SceneKit 来创造 AR 环境
3.选择保存位置,一个 AR 工程就新建完毕了
新建好以后,你会看到工程里已经有一些文件,而且和以往一样,你可以直接运行这个工程到你的设备上,按下 Command + R 运行在 iPhone 上试试吧!
在调试 AR app 的时候,你可能需要到处走动,牵着一根线可能会大大阻碍你的行动,幸运的是 Xcode 9.0 开始支持了无线开发,你可以以无线的方式将 App 安装在设备上,并且还能正常接收调试数据,具体怎么做请看我的探索 Xcode 9
Demo 工程是让一艘飞船悬停在空中,如果你已经运行过这个 Demo 了应该会觉得效果还蛮不错,虽然某些 AR 技术的通病 Apple 还没有解决,让我们先来看一看这个工程里相对重要的文件有哪些吧:
-
art.scnassets:这里面存放的就是场景文件,一个场景里可以有多个物体
-
ship.scn:我们的飞船就来自这个场景文件,你可以打开并编辑它
-
texture.png:这是一个纹理球文件,每一个 3D 模型都应该需要纹理,否则看上去会很单调,除非你刻意创造那种场景。而这个纹理就是这艘飞船的纹理。
-
ViewController.swift:默认的视图控制器,它继承于 UIViewController 并遵守 ARSCNViewDelegate 协议。
-
Main.storyboard:Storyboard 文件,里面有一些默认的组件已经被添加好了
剩下的一些资源文件和普通的 Cocoa touch 工程没什么区别,相信大家应该都了解了。
那么让我们继续来看大家最关心的部分,ViewController 这个文件展示了显示一个虚拟物体在现实世界中的最基本的步骤和内容,相当有指导意义,我们以方法和属性区分,分别解释。
默认的 ViewController
@IBOutlet var sceneView: ARSCNView!
这就是 Xcode 帮我们创建好的一个 ARSceneView 对象,而且做好了牵线工作。这个 Scene 和普通的 SceneKit 的 Scene 不同的是,它是 AR Scene,专门显示 AR 的
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate 设置 view 的代理
sceneView.delegate = self
// Show statistics such as fps and timing information 显示调试信息
sceneView.showsStatistics = true
// Create a new scene 创造一个新的场景
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// Set the scene to the view 使默认场景为我们新创建的场景
sceneView.scene = scene
}
在 viewDidLoad 中,我们可以为创建好的 sceneView 设置代理,showStatistics 属性可以指定是否显示调试信息,这些信息包含 fps 值、时间信息等。
我们之前在资源文件里看到了,有一个飞船的场景文件,这个场景文件也是在这里被载入的,使用 SCNScene 的初始化方法 init(named:)
来载入一个场景文件,最后我们将 sceneView 的默认场景替换成我们载入的场景。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingSessionConfiguration()
// Run the view's session
sceneView.session.run(configuration)
}
每一个 AR 场景被创建时,它都需要一个配置类来告诉它应该使用怎样的方式去运行,配置类一共有两种:
-
ARWorldTrackingSessionConfiguration
-
ARSessionConfiguration
这里使用的就是第一种,两者的区别在于物体的摆放方式:
- 设置为 ARWorldTrackingSessionConfiguration 时,物体放在基于现实世界的坐标系,你的摄像头只是这个坐标系的另一个点,移动设备的时候物体是相对坐标系静止的。
- 设置为 ARSessionConfiguration 时,坐标系以你的设备为基准,你的设备平移时,整个坐标系也跟着平移,坐标系上的物体也跟着平移,所以摄像头和物体是相对静止的,只有设备横向旋转时能观察坐标系的其他部分的元素。
在创建好配置类的对象后,你就可以使用 sceneView 的 session 来启动一次 AR session,这里的 session 就是每一个 AR 进程所需要的 session 对话,你可以使用 run() 方法来启动一个 session。
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
当你不希望 session 继续占用系统资源时,你可以使用 pause() 来停止一次 session,你可以再一次使用 run 来启动新的 session。
另外,你还可以在 session 执行的过程中动态更新配置类。
// Override to create and configure nodes for anchors added to the view's session.
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let node = SCNNode()
return node
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
}
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
这里就列出了一些 ARSCNViewDelegate 中的代理方法,其中:
-
renderer(:nodeFor:):新建一个 Anchor(锚点)时会调用
-
renderer(:updateAtTime:):更新场景时会调用
-
session(:didFailWithError:):出现错误时会调用,此时会终止 session
-
sessionWasInterrupted(:):中断 session 时会调用
-
sessionInterruptionEnded(:):中断结束时会调用
到这里大家都应该知道一个场景的基本构造,以及如何载入一个场景。
添加一张“照片”到 AR 场景中
大家应该都发现了,我们是利用 SceneKit 创建的 AR 场景,所以我们可以将一部分 SceneKit 的东西拿来用,使用 SceneKit 创造的三维模型将其载入到 AR 世界里去,事实上,我们的 scn 场景文件本身也就是 SceneKit 的概念。
在这里我们将利用 SCNPlane 类来创造一个平面,然后设置这个平面的纹理为一张照片,在我们点击屏幕时将这个对象添加到 AR 场景中。
1.在 touchesBegan 中实现
既然是点击屏幕添加物体,那么就可以在 touchesBegan 中完成
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// code here
}
2.获取当前帧
对于 AR 场景,当前帧不仅仅包含了帧信息和图像信息,还包含了当前 ARCamera 的坐标信息、sceneView 的大小等等,我们要实现在屏幕前添加一个物体,那么就得获取当前 Camera 的位置,否则我们添加的物体可能在任何一个地方,甚至你找不到的地方
// 从 session 中获取 currentFrame
guard let currentFrame = sceneView.session.currentFrame else { return }
3.创建一个 Node
要创建一个 Node,你需要一个继承自 SCNGeometry 的对象,这里我们使用的 SCNPlane 就继承自 SCNGeometry
// 创建一个 plane。并设置大小,这里的大小单位接近米,不能太大,否则最后会挡住我们的视线,看不出效果
let plane = SCNPlane(width: 0.6, height: 0.4)
// Meterial 就是我们的材料,也就是纹理,我们设置纹理的内容为一个 UIImage 对象
plane.firstMaterial?.diffuse.contents = UIImage(named: "material.jpg")
plane.firstMaterial?.lightingModel = .constant
// 利用创建好的 plane 来创建一个精灵(Node)
let planeNode = SCNNode(geometry: plane)
// 最后把精灵加入到 sceneView 的根节点
sceneView.scene.rootNode.addChildNode(planeNode)
每一个 scene 都有一个根节点,它类似于一个 scene 的坐标中心,所有子节点都依附于根节点,根节点产生的位移、旋转变化都会应用于子节点。
4.设置新节点的位置
我们说,需要把新加入的物体放在我们面前,那就必须要指定好它的详细位置,这时候我们最开始获取的 currentFrame 就有用了
// 熟悉 SceneKit 的朋友应该都了解这里的操作,这里的重点是利用 currentFrame 的 camera 属性来获取 transform
// 这个 transform 的各种位置属性就是当前摄像机所处的位置信息,所以我们可以直接利用它
var translation = matrix_identity_float4x4
translation.columns.3.z = -20
let transform = matrix_multiply(currentFrame.camera.transform, translation)
// 最后把 transform 传给 node 的 simdTransform
planeNode.simdTransform = transform
5.运行,测试
现在重新编译工程,并运行在设备上,每当我们点击一下屏幕时,面前都会有一张我们的照片出现,而且此时你可以移动相机,在其他地方继续添加照片,最后可以创造出一个很帅气的场景~