查看原文
其他

AR丨iPhone新机发布,ARKit iOS 11原生开发入门(上)

2017-09-13 eseedo Gad-腾讯游戏开发者平台

特别说明:


本教程基于ARKit和Swift的Native开发,并非基于Unity或Unreal。内容翻译改编自iOS 11 By Tutorials的beta试读章节,版权归原作者所有。此外,由于作者在写作该教程时基于iOS 11 beta5之前的版本,部分API已被弃用或更改(这就是当小白鼠的代价~),本人基于iOS 11 beta8做了部分细节上的调整(2017年8月31日),具体以苹果最新的官方文档为主。


原文作者似乎是在iPad上做的测试,本人的开发和测试环境参考如下:


测试设备:iPhone 6s Plus

测试系统:iOS 11 beta 8

开发环境-工具:Xcode 9 beta6

开发环境-硬件:MacBook Pro(Retina,15-inch,Mid 2014), macOS Sierra(10.12.6)


前言:


在不久之前,主流的增强现实技术看起来还是一个很遥远的梦想。因此当苹果在WWDC2017开发者大会上推出全新的iOS框架ARKit时,整个业界再次为之振奋。我们只需拥有一部iOS设备就可以运行AR应用,完全不需要任何的第三方配件。


ARKits的展示效果令人震惊。在本教程结束的时候,我们将创建一个名为HomeHero的应用,它可以帮我们来测量、规划和可视化的呈现室内的装修设计。



开始前的准备


打开本章的Starter项目文件夹(链接: https://pan.baidu.com/s/1jI3Plqu 密码: w3kc),找到并打开HomeHero.xcodeproj文件。在选择了HomeHero项目文件和TARGETS后,在General 选项卡中选择Team信息。


注意:和其它类型的应用开发不同,使用ARKit开发应用时Simulator是没什么作用的,我们需要在一台具有A9处理器或更好配置的iOS设备上进行测试。


目前搭载了Apple A9和A10处理器的只有iPhone 6s, iPhone 6s Plus, iPhone SE,iPhone7,iPhone 7 Plus,两代iPad Pro和最新的iPad。


当然,即便发布的iPhone7s ,iPhone7s Plus,iPhone Pro肯定在此系列。


点击编译并在设备上运行,可以看到类似下面的画面。



目前这个画面平淡无奇,它只是在界面上放置了几个普通的UIButton而已。接下来我们需要配置ARKit并进行渲染。不过在此之前,我们首先需要了解ARKit的各种组成元素。


ARKit会使用设备上的摄像头和运动传奇器件来检测设备位置随时间的变化。摄像头所捕捉的图像还会被用来识别景深信息、平面,以及光照条件。因此,在展示使用ARKit开发的应用时,需要确保背景具备良好的光照条件,同时有大量的物体和丰富的纹理。ARKit的算法非常精确,因此我们可以在它的帮助下来测量真实世界中的物体。


ARKit session


让我们假定iOS设备是个机器人:它有自己的眼睛,也可以感受到自己的运动。那么ARSession就是机器人的大脑,它可以通过API来交流自己所看到和所感受到的。


ARSession中包含了两个组成部分:


1.ARAnchor:


这个类用来表达某个物体在真实世界中的位置和朝向。我们可以手动给session添加anchor,以跟踪物体。当ARSession在检测特定的物体时,也会自动添加ARAnchor。举例而言,当我们打开自动平面检测时,就会自动添加ARAnchor的子类ARPlaneAnchor物体。


2.ARFrame:


ARKit需要捕捉视频帧,并使用ARFrame来分析每个视频帧的运动数据,并返回信息摘要。ARFrame中包含了所捕捉到的视频信息,光线评估数据,和所有跟踪到的ARAnchor对象。ARFrame中包含了ARCamera,它代表了一个物理的摄像头及其位置。ARFrame中还包含了所检测到的feature points(功能点),其中包含了真实世界3D坐标的有意思的特性。视频帧上的信息越多,那么特征点也越多。


ARSession包含了一个ARSessionDelegate代理对象,可以让程序随时了解正在发生的事情。




Session configuration


我们需要ARConfiguration来运行ARSession。我们不推荐直接使用ARConfiguration,因为它只会检测设备的旋转,而非位置信息。但是有一点需要说明的是,它支持没有配置A9处理器的设备,因此仅当我们需要支持之前更老的设备时,才需要用到它。正常情况下我们应使用ARWorldTrackingConfiguration(ARConfiguration的子类),它可以跟踪所有角度的运动,并给出最佳的结果。


ARWorldTrackingConfiguration让我们可以选择:


1.Light Estimation(光线评估)


通过额外的计算,可以评估光照条件,并以ARFrame对象的形式返回。它可以帮助虚拟物体更好的契合真实世界的光照。


2.Plane Detection(平面检测)


可以检测真实世界中的平面,并通过添加ARPlaneAnchor对象的方式来自动追踪。当然到目前为止,ARKit仅支持对水平面的支持。ARKit目前支持对地板、桌子、沙发等平面的支持。当我们移动摄像头的时候,ARKit可以持续追踪数据的变化,包括平面的位置和延伸等,或是合并为一个平面。


Rendering(渲染)


之前做过类比,ARKit好比机器人的大脑,其中包含了它所观察到的原始数据,但是它并不会渲染出任何东西。我们需要使用其它的渲染器来渲染出增强现实对象,如SceneKit,Metal或是SpriteKit。苹果为此提供了ARSCNView,可以让我们使用SceneKit来渲染ARKit所提供的数据。ARSCNView会创建一个ARSession实例对象,并将ARCamera映射到一个SCNCamera上,这样当我们移动设备的时候,所渲染出的SceneKit对象也会随之移动。通过这种方式,可以大大减轻我们的开发工作量,你很快就会意识到这一点。


开始设置ARKit


为了放置和渲染虚拟物体,我们首先需要设置ARSession和ARSCNView。在Xcode中打开HomeHeroViewController.swift。这个starter项目中已经包含了一个ARSCNView,它和HomeHeroViewController的sceneView属性关联在一起。


让我们创建一个遵从ARSCNViewDelegate的HomeHeroViewController扩展如下:


extension HomeHeroViewController:ARSCNViewDelegate{

}


在该方法中,ARSCNView将更新ARSession和renderer的状态。我们将在后面具体实现这个方法。


在HomeHeroViewController类中添加以下方法:


func runSession(){

//1

sceneView.delegate = self;

//2

let configuration = ARWorldTrackingConfiguration()

//3

configuration.planeDetection = .horizontal

//4

configuration.isLightEstimationEnabled = true

//5

sceneView.session.run(configuration)

//6

#if DEBUG

sceneView.debugOptions = ARSCNDebugOptions.showFeaturePoints

#endif

}


在以上方法中,我们设置并运行了ARSession,下面依次解释下各行代码的作用:


1.将HomeHeroViewController注册为ARSCNView的代理,后续我们将用它来渲染物体。


2.使用ARWorldTrackingSessionConfiguration来充分利用所有的运动信息,并给出最佳的结果。需要注意的是,它只支持苹果A9处理器,或者更高。


3.打开自动水平面检测。我们将用词来渲染平面,从而进行测试,以及将物体放置在真实世界之中。


4.打开光线评估运算。ARSCNView将自动使用该功能,并基于所测算的真实世界光照条件来给物体打光。


5.run(_:options)开启ARKit进程,并开始捕捉视频画面。该方法将会让设备请求使用相机,如果用户拒绝该请求,那么ARKit将无法工作。


6.ASRCNView还有一个额外的功能,也即渲染出特征点,使用该行代码可以在调试的过程中查看特征点。


为了在HomeHeroViewController的viewDidLoad()方法中调用runSession()方法,我们还需要更改当前的viewDidLoad()方法如下:


override func viewDidLoad() {

super.viewDidLoad()

runSession()

trackingInfo.text = ""

messageLabel.text = ""

distanceLabel.isHidden = true

selectVase()

}


此时,当视图加载的时候,将开启ARKit的进程。


在手机上编译运行该应用,首先我们需要允许使用相机。ARSCNView将自动为你完成这些事情:显示背景中所捕捉到的视频图像,并渲染特征点。如果没有ARSCNView,很难想象你自己该如何完成以上的工作。



绘制平面


在放置物体对象之前,我们需要了解ARKit是如何认识这个世界的。可能大家对特征点已经有所了解了,但是特征点并非真实世界的精确模型与写照。幸运的是,ARKit提供了另一种更为精确的世界描述方式:plane(平面)。


ARPlaneAnchors将会被自动添加到ARSession之中,而ARSCNView则会自动将ARPlaneAnchor对象添加到SCNNode的节点中。这样就给开发者带来了极大的便利,因为我们只需实现ARSCNViewDelegate方法,然后渲染新的平面。


接下来让我们实现HomeHeroViewController的ARSCNViewDelegate扩展的renderer(_:didAdd:for:)方法如下:


func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

//1

DispatchQueue.main.async {

//2

if let planeAnchor = anchor as? ARPlaneAnchor{

//3

#if DEBUG

//4

let planeNode = createPlaneNode(center: planeAnchor.center, extent: planeAnchor.extent)

//5

node.addChildNode(planeNode)


#endif

}

}

}


当ARSession识别到一个新的平面时,ARSCNView就自动为该平面添加一个ARAnchor,并调用renderer(_:didAdd:for:)代理方法。这里详细解释下以上代码的作用:


1.在一个单独的queue中调用了Renderer代理方法。为了避免多线程的问题,最简单的方法就是将其分发到主线程队列中。


2.这行代码用来检测新添加的ARAnchor是否是ARPlaneAnchor的子类。


3.表示此处的内容仅用于调试输出。


4.createPlaneNode(center:extent:)是项目中所提供的一个辅助方法。它是一段和SceneKit相关的代码,用于使用从ARPlaneAnchor中获取的指定中心的和延伸范围来创建一个蓝色的平面SCNNode。


5.这里的node参数是一个空的SCNNode,将会被ARSCNView自动添加到场景之中,其坐标跟anchor参数相关。我们只需要使用addChildNode(_:)方法将一个子对象关联到该空白节点即可,子对象(比如这里的平面)就会自动显示在正确的位置。


除此之外,我们还需要考虑当平面的大小或者位置发生变化时,或者一起被删除时的情况。


因此,我们需要实现renderer(_:didUpdate:for:)和renderer(_:didRemove:for:)两个代理方法。


其代码如下:


func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {

DispatchQueue.main.sync {

if let planeAnchor = anchor as? ARPlaneAnchor{

//1

updatePlaneNode(node.childNodes[0], center: planeAnchor.center, extent: planeAnchor.extent)

}

}

}

func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {

guard anchor is ARPlaneAnchor else{

return

}

//2

removeChildren(inNode: node)

}


以上代码的主要作用就是更新平面的状态。其中renderer(_:didUpdate:for:)方法将在相关的ARAnchor更新时被调用,而renderer(_didRemove:for:)方法则将在相关的ARAnchor被删除时被调用。这里是对加了数字的代码行的解释:


1.更新子节点,也就是此前在renderer(_:didAdd:for:)方法中所添加的平面节点。updatePlaneNode(_:ceenter:extent:)是起始项目中所提供的一个辅助方法,用来根据ARPlaneAnchor中的更新信息来更新平面的坐标和大小。


2.当对应的ARAnchorPlane呗删除时从节点中删除平面。removeChildren(inNode:)方法也是起始项目中所提供的辅助方法,具体细节大家可以自己研究。


此时编译并运行应用,就可以看到在真实的世界中出现可视化的平面了。



需要注意的是,在测试时如果没有立即检测出平面,需要耐心等待一点时间。


创建AR对象


好了!现在我们可以进入虚拟世界,然后按照自己的想法来改造世界了。首先我们将要学习如何在所检测的平面上放置物体。ARSCNView提供了非常有用的hit检测方法,因此我们将使用该方法来检测手指在虚拟世界中触碰所检测的平面上的点。


在HomeHeroViewController.swift中添加新的方法如下:


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

//1

if let hit = sceneView.hitTest(viewCenter, types: [.existingPlaneUsingExtent]).first{

//2

sceneView.session.add(anchor: ARAnchor(transform:hit.worldTransform))

return

}

}


以上方法将在hit test的结果点中添加一个新的anchor。这里对数字编号的代码注释如下:


1.hitTest(_:types:)方法将返回指定平面坐标和类型的所有hit test结果。这里我们传递了一个viewCenter参数作为屏幕的中心坐标点,也就是灰色小点的位置。viewCenter是起始项目中所提供的辅助属性。接下来使用existingPlaneUsingExtent这个hit test选,从而说明我们需要获取已存在平面的hit test结果信息,并保留平面的有限大小(范围)。


2.如果我们获取到结果,则使用ARSession中的add(anchor:)方法来创建一个anchor锚点,以表示物体在真实世界中所放置的点。

以上代码虽然添加了anchor,但是并不会渲染出任何东西。ARSCNView将在添加了新的ARAnchor时调用renderer(_:didAdd:for:)代理方法。我们将在该方法中处理对新物体的渲染。


更改之前的renderer(:_:didAdd:for:)方法的代码如下:


func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

//

DispatchQueue.main.async {

if let planeAnchor = anchor as? ARPlaneAnchor{

#if DEBUG

let planeNode = createPlaneNode(center: planeAnchor.center, extent: planeAnchor.extent)

node.addChildNode(planeNode)

#endif

//1

}else{

//2

switch self.currentMode{

case .none:

break

//3

case .placeObject(let name):

//4

let modelClone = nodeWithModelName(name)

//5

self.objects.append(modelClone)

//6

node.addChildNode(modelClone)

//7

case .measure:

break

}

}

}

}


以上代码将在真实世界中放置所选择的模型,具体的数字代码行注释如下:


1.else 意味着该ARAnchor并非ARPlaneAnchor的子类,而是一个普通的ARAnchor实例,通过touchesBegan(_:with:)方法添加。


2.currentMode是已经添加到起始项目中的HomeHeroController属性,它代表当前的UI状态:其中placeObject代表选择的是物体按钮,而measure则代表所选择的是测量按钮。switch代码将根据UI状态的不同执行不同的代码。


3.placeObject包含了一个关联的string值,其代表到3D模型.scn文件的路径。我们可以在Models.scnassets中查看所有的3D模型。


4.nodeWithModelName(_:)方法使用指定的路径名称来创建一个新的3D模型SCNNode,该方法是起始项目中所提供的辅助方法。


5.将节点扩展到起始项目中所提供的objects数组中。


6.最后,我们将新的物体节点添加到代理方法中的SCNNode。

7.关于测量工具,我们将在后续具体来实现。


编译并在手机上运行项目,将灰色的点指向所检测出的平面上,并触碰屏幕,即可将物体添加到场景之中。



未完待续。


----------------------


今日推荐


《辐射》的辅助瞄准系统是如何制作的?

挥别读取丢帧!如何实现虚拟场景的转换?

深度强化学习训练AI,像人类一样玩DOOM


添加小编微信,可享双重福利

1.加入GAD游戏VR交流圈

获取行业干货资讯,观看大牛分享直播

2.领取GAD独家VR资料库,地址在小编朋友圈

包括研究报告、游戏制作、项目分享等全套资料


↓长按添加小编GAD-安琪↓


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存