iOS开发攻城狮的集散地AR开发从基础到实战教程iOS开发

iOS AR开发基础02 | ARKit开发基本套路和核心API

2018-03-30  本文已影响272人  TankXie

上一篇从直观体验上介绍了“什么是AR”,本篇接着上篇内容,从技术层面对AR技术进行分析,提炼出其中的开发基本套路和一些最基础的API。

写在前面

构建AR应用,涉及到两大块内容 ,对真实世界的理解和追踪3D AR世界的渲染以及用户和3D世界的交互,这二者分别对应 ARKitSceneKit 两个框架。

本文会以 ARKit 和 SceneKit 中的 API为例,来讲解 AR 项目构建的基本原理和主要流程,如果您是初学者,对这两个框架不熟悉,刚开始不必太过纠结于某一处细节,从宏观上把握整个流程即可。

关于 ARKit 相关的内容,在大致熟悉整个流程之后,可以查看 《iOS AR开发基础篇03 -- Hello World (基于AR的平面检测、人脸检测和物体识别功能)》文中给出的 demo来加深理解。

关于 SceneKit相关的内容 ,本文会给出一些相关名词,重在理解其意,在《 iOS AR开发基础篇04 -- SceneKit开发基础(3D渲染基础)》中会给出具体API的使用和细节。

如果要了解具体 API 的一些细节,可以在 Apple 官方文档中查看相应内容。

本文内容结构

本文内容结构如下:

本节内容概览

1. AR系统的结构

先从宏观上给出一张AR系统的结构:

AR System architecture-1

这张图很重要,本文的内容都是围绕这张图展开的。

对这张AR系统的结构图,从整体上,我们可以粗略的做如下解析:

我们知道基本套路之后,开发的时候,需要将具体的步骤和相应的API对应起来,如下图:

AR System architecture-2

这张图的5种颜色大框,对应5个模块,框里面对应的就是每个模块核心类和核心的API,下面详细讲解每个模块。

2. 虚拟世界

2.1 2D/3D引擎介绍

ARKit本身并不提供渲染引擎,在虚拟世界的构建时,我们需要用到其它的2D/3D引擎,对于没有3D开发经验的同学来说,我认为学习AR开发,70%的精力都会花在此处。

虚拟世界

图示用的是用SceneKit中的SCNScene构建虚拟3D世界,这种方式是苹果推荐使用的,因为ARKit中提供了很多便利方法可以供我们直接使用。例如,我们查看ARKit中负责呈现视图的ARSceneView定义不难发现,它是SCNView的子类。

open class ARSCNView : SCNView {
// ...
}

后续开发我们也会使用SceneKit作为3D内容渲染引擎,SpriteKit作为2D内容渲染引擎。
后续开发我们也会使用SceneKit作为3D内容渲染引擎,SpriteKit作为2D内容渲染引擎。
后续开发我们也会使用SceneKit作为3D内容渲染引擎,SpriteKit作为2D内容渲染引擎。
(重要的事儿说三遍!)
当然,Apple还有其他的2D/3D引擎提供:

2.2 SceneKit中的坐标系

如图所示的三维坐标系,就是SceneKit所使用的坐标系,它是一个右手坐标系

SceneKit - Coordinate

此图上的每一个方块代表空间中的一个点,其中:

2.2.1 世界坐标系

2.2.2 本地坐标系

在SceneKit中,我们用SCNVector3来表示一个三维的点,空间中创建一个坐标为(1,1,1)点的方式如下:

     let position = SCNVector3(x:1, y: 1, z: 1)

2.3 SceneKit中的 SCNScene 和 SCNNode

我们先想象一个拍电影的场景。

image.png

在SceneKit中,我们可以把SCNCamera当做拍摄镜头,SCNScene当做拍摄场景,场景中的每个人或者物体都可以看做SCNNode,在指定的坐标系下,每个SCNNode都有唯一的坐标。
在下图实线为坐标轴所表示的坐标系下,三个小黑点表示场景(scene)中的三个节点(node),其中RootNode称之为根节点,NodeA和NodeB称之为非根节点。

SCNNode

此处要引出两个重要的概念:世界坐标系本地坐标系

节点的位置是相对的父节点的本地坐标。

在上图中,在以RootNode为坐标原地的世界坐标系中,RootNode是NodeA的父节点,NodeA是RootNode的子节点。

而在以NodeA为坐标原点的本地坐标系中,NodeA是NodeB的父节点,NodeB是NodeA的子节点。

有了SCNScene和SCNNode 后,我们还需要一个 SCNCamera 来决定我们可以看到场景中的哪一块区域。

2.4 SceneKit 中的 SCNCamera

SCNCamera 在 SCNScene 的工作模式如下图所示:

SCNCamera

通过观察我们发现:

在SceneKit中,创建一个SCNCamera实例并将其添加到cameraNode上,再将cameraNode设置为场景中的根节点,代码如下:

let scene = SCNScene()
let cameraNode = SCNNode()
let camera = SCNCamera()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0)
scene.rootNode.addChildNode(cameraNode)

2.5 SceneKit 中的 SCNView

SCNView用来将SCNScene的内容展示到屏幕上,其基本用法如下:

代码如下:

let scnView = SCNView()
scnView.scene = scene
vc.view.addSubview(scnView)
scnView.frame = vc.view.bounds

3. 捕获真实世界

摄像头捕获真实的世界的图像作为AR世界的背景显示,使用的过程中,涉及到三个核心类:ARSession、ARFrame和ARCamera。


Capture Real World

3.1 ARSession

ARSession 是一个单例,用来管理、配置整个AR体验的主要流程,它包括:

每一个基于ARKit的AR项目都需要一个ARSession对象。
如果使用ARSCNView/ARSKView对象来渲染AR内容,他们本身包含一个ARSession属性session;如果自定义AR内容渲染器,就需要自行实例化和维护ARSession对象。

ARSCNView 和 ARSession 关系如下:

open class ARSCNView : SCNView {
// ...
    /**
     The session that the view uses to update the scene.
     */
    open var session: ARSession
// ...
}

运行session,还需要一个ARConfiguration(或它子类ARWorldTrackingConfiguration)实例configuration。configuration将决定ARKit如何跟踪设备相对于真实世界的位置和运动,从而影响创建的AR体验种类。

运行一个session基础代码如下:

 // Create a session configuration.
let configuration = ARWorldTrackingSessionConfiguration()
 // Create a session.
 let session = ARSession()
 // Run.
 session.run(configuration)

3.2 ARFrame

ARFrame实例是带有位置追踪信息(position-tracking information)的视频图像,对于ARFrame有以下三个要点:

查看ARFrame头文件,本节我们先掌握它的如下四个属性:

open class ARFrame : NSObject, NSCopying {
    // ...
    /**
     A timestamp identifying the frame.
     */
    open var timestamp: TimeInterval { get }
    /**
     The frame’s captured image.
     */
    open var capturedImage: CVPixelBuffer { get }

    /**
     A list of anchors in the scene.
     */
    open var anchors: [ARAnchor] { get }

    /**
     The camera used to capture the frame’s image.
     @discussion The camera provides the device’s position and orientation as well as camera parameters.
     */
    @NSCopying open var camera: ARCamera { get }
    // ...
}

至此,我们不仅获取了图像的原始数据,还获取到了图像中的位置追踪信息和其它的图像参数。

4. 世界追踪

世界追踪(World Tracking)将虚拟世界和真实世界联系起来,这里追踪的“世界”是“真实世界”。

World Tracking

ARSession是整个世界追踪的核心类,连接虚拟世界和真实世界需要运行ARSession实例,运行方式和前面“捕获真实世界”中的方式一样,此处不再赘述。追踪原理大致如下:

4.1 世界追踪基本原理

image.png

获取原始位置

设备在运行ARSession时的初始位置。

可以追踪什么?

怎么追踪?

追踪质量的影响因素

影响世界追踪的主要因素有

追踪状态

这是camera的一个状态机,在触发不同事件后,追踪状态会切换,详细内容会在【4.2.4 ARCamera】中讲解。


camera的追踪状态机

4.2 相关API

前面已经介绍过ARSession、ARCamera和ARFrame了,现在介绍一下如下几个类:

4.2.1 ARWorldTrackingConfiguration

ARWorldTrackingConfiguration可以将虚拟世界和真实世界联系起来,这样当虚拟世界和真实世界同时渲染在屏幕上的时候,用户会产生虚拟内容是真实世界的一部分的错觉。

运行 session 的时候,若将configuration参数设为 ARWorldTrackingConfiguration,ARKit将会:

维护真实世界和虚拟世界的对应关系,我们还需要获取设备的移动信息。ARWorldTrackingConfiguration类从6个自由度(degrees of freedom)来追踪设备的运动:具体来说,三个旋转轴(滚动,俯仰和偏航)以及三个平移轴(在X,Y和Z中的移动)。

6DOF

这种追踪可以给用户带来沉浸式的体验:虚拟世界的物体可以看起来与现实世界保持相同的位置,即使用户将设备倾斜到物体的上方或下方,或者移动设备以查看物体的侧面和背面。

如下是Apple官方文档中给的一张图,无论设备旋转或移动,6DOF跟踪都会保持AR的良好体验。

6DOF tracking maintains an AR illusion regardless of device rotation or movement

ARWorldTrackingConfiguration 的部分头文件如下:

open class ARWorldTrackingConfiguration : ARConfiguration {

    // ... 
    /**
     Type of planes to detect in the scene.
     @discussion If set, new planes will continue to be detected and updated over time. Detected planes will be added to the session as
     ARPlaneAnchor objects. In the event that two planes are merged, the newer plane will be removed. Defaults to ARPlaneDetectionNone.
     */
    open var planeDetection: ARWorldTrackingConfiguration.PlaneDetection

    
    /**
     Images to detect in the scene.
     @discussion If set the session will attempt to detect the specified images. When an image is detected an ARImageAnchor will be added to the session.
     */
    @available(iOS 11.3, *)
    open var detectionImages: Set<ARReferenceImage>?

    // ... 
}

planeDetection
设置这个属性,可以指定是否检测当前摄像头捕获图像中的平面。

planeDetection 是一个 ARWorldTrackingConfiguration.PlaneDetection类型的值,它可以取两个值 horizontalvertical,分别代表水平面检测和垂直面检测。

其中,垂直平面检测是iOS 11.3支持的功能。

  /**
     Option set indicating the type of planes to detect.
     */
    @available(iOS 11.0, *)
    public struct PlaneDetection : OptionSet {

        // ... 
        /** Plane detection determines horizontal planes in the scene. */
        public static var horizontal: ARWorldTrackingConfiguration.PlaneDetection { get }

        /** Plane detection determines vertical planes in the scene. */
        @available(iOS 11.3, *)
        public static var vertical: ARWorldTrackingConfiguration.PlaneDetection { get }
        // ...
    }

默认情况下,平面检测是关闭状态。如果启用水平或垂直平面检测,session 在捕获的视频图像中分析检测到平面区域时,会自动添加一个 ARPlaneAnchor 对象,并给实现 ARSessionDelegate、ARSCNViewDelegate 或 ARSKViewDelegate 的对象发送通知。ARPlaneAnchor 在后面将会讲解。

detectionImages
detectionImages 也是一个iOS 11.3支持的AR图像识别功能。后面会写一个Demo来实现这个功能。

4.2.2 AROrientationTrackingConfiguration

和 ARWorldTrackingConfiguration 类似,设置 AROrientationTrackingConfiguration 属性之后:

还是引用Apple官方文档中给的图片。

3DOF tracking maintains an AR illusion when the the device pivots, but not when the device's position moves

4.2.3 ARPlaneAnchor

anchor翻译为锚点,在世界追踪的AR session 中,一个ARPlaneAnchor对象表示在真实世界中,一个平面的位置和方向信息。

/**
 An anchor representing a planar surface in the world.
 @discussion Planes are defined in the X and Z direction, where Y is the surface’s normal.
 */
@available(iOS 11.0, *)
open class ARPlaneAnchor : ARAnchor {
}

ARPlaneAnchor 是 ARAnchor 的子类,在看 ARPlaneAnchor 之前,先看看ARAnchor。

ARAnchor

锚点表示真实世界的位置和方向,为AR场景中放置虚拟物体提供位置信息。在开发的过程中,需要掌握对anchor的如下操作:

其实ARAnchor的定义比较简单,包含两个属性:

/**
 Object representing a physical location and orientation in 3D space.
 */
@available(iOS 11.0, *)
open class ARAnchor : NSObject, NSCopying, NSSecureCoding {

    
    /**
     Unique identifier of the anchor.
     */
    open var identifier: UUID { get }

    /**
     The transformation matrix that defines the anchor’s rotation, translation and scale in world coordinates.
     */
    open var transform: matrix_float4x4 { get }

    /**
     Initializes a new anchor object.
     @param transform The transformation matrix that defines the anchor’s rotation, translation and scale in world coordinates.
     */
    public init(transform: matrix_float4x4)
}
ARPlaneAnchor

注意:iOS 11.3之前只支持水平面检测,11.3开始支持垂直面检测!

当运行启用了 planeDetection 选项的世界追踪AR session时,每检测到一个平面,ARKit 会自动将表示该平面的ARPlaneAnchor对象添加到一个数组中去。 每个ARPlaneAnchor对象提供关于平面的位置和形状的信息。

ARPlaneAnchor 在 ARAnchor 基础上添加了和“平面”相关的三个属性alignmentcenterextentgeometry:

**
 An anchor representing a planar surface in the world.
 @discussion Planes are defined in the X and Z direction, where Y is the surface’s normal.
 */
@available(iOS 11.0, *)
open class ARPlaneAnchor : ARAnchor {

    /**
     The alignment of the plane.
     */
    open var alignment: ARPlaneAnchor.Alignment { get }

    /**
     The center of the plane in the anchor’s coordinate space.
     */
    open var center: vector_float3 { get }

    /**
     The extent of the plane in the anchor’s coordinate space.
     */
    open var extent: vector_float3 { get }

    /**
     Geometry of the plane in the anchor's coordinate space.
     */
    @available(iOS 11.3, *)
    open var geometry: ARPlaneGeometry { get }
}

4.2.4 ARCamera

ARCamera作为ARFrame的属性,在ARSession捕获视频帧数据的时候,从以下三方面了解ARCamera:

camera的位置信息

和位置相关的信息通过两个属性来描述,transform 和 eulerAngles,前者表示位置,后者表示方向。

open class ARCamera : NSObject, NSCopying {
    // ... ...
    /**
     The transformation matrix that defines the camera’s rotation and translation in world coordinates.
     */
    open var transform: matrix_float4x4 { get }

    
    /**
     The camera’s orientation defined as Euler angles.
     
     @dicussion The order of components in this vector matches the axes of rotation:
                   1. Pitch (the x component) is the rotation about the node’s x-axis (in radians)
                   2. Yaw   (the y component) is the rotation about the node’s y-axis (in radians)
                   3. Roll  (the z component) is the rotation about the node’s z-axis (in radians)
                ARKit applies these rotations in the reverse order of the components:
                   1. first roll
                   2. then yaw
                   3. then pitch
     */
    open var eulerAngles: vector_float3 { get }
     // ... ...
}

成像特性信息

intrinsicsimageResolution 两个属性描述了ARCamera成像特性,设置这两个参数可以想象成给真实的相机调参。


open class ARCamera : NSObject, NSCopying {
     // ... ...
    /**
     The camera intrinsics.
     @discussion The matrix has the following contents:
     fx 0   px
     0  fy  py
     0  0   1
     fx and fy are the focal length in pixels.
     px and py are the coordinates of the principal point in pixels.
     The origin is at the center of the upper-left pixel.
     */
    open var intrinsics: matrix_float3x3 { get }

    /**
     The camera image resolution in pixels.
     */
    open var imageResolution: CGSize { get }
     // ... ...
}

intrinsics 称之为相机的内参,是相机的一个固有属性,也就是说每个相机都会有 Intrinsic Matrix,因为所有的相机都需要将现实世界中3D空间的点映射到捕捉的图像中2D空间的点。

3D空间的点N映射到捕捉的图像中2D空间的点N'

Intrinsic Matrix 一般是形式如下:

Intrinsic Matrix

其中,

后续有文章来描述这个内参的推倒过程。

imageResolution 属性理解起来就相对简单一些,图像的分辨率,以像素为单位表示 ARCamera 捕获图像的宽度和高度。

处理追踪状态

追踪状态通过两个属性来描述,trackingStatetrackingStateReason,前者表示ARCamera当前的状态,当状态为 limited 的时候,trackingStateReason输出原因。

open class ARCamera : NSObject, NSCopying {
     // ... ...
    /**
     The tracking state of the camera.
     */
    open var __trackingState: __ARTrackingState { get }

    
    /**
     The reason for the camera’s current tracking state.
     */
    open var __trackingStateReason: __ARTrackingStateReason { get }
     // ... ...
}

trackingState是一个 ARCamera.TrackingState类型变量,有三个状态可供切换:

/**
     A value describing the camera's tracking state.
     */
    public enum TrackingState {

        /** Tracking is not available. */
        case notAvailable

        /** Tracking is limited. See tracking reason for details. */
        case limited(ARCamera.TrackingState.Reason)

        /** Tracking is normal. */
        case normal
    }

当 trackingState 为 limited 的时候,可以获取原因:

public enum Reason {

            /** Tracking is limited due to initialization in progress. */
            case initializing

            /** Tracking is limited due to a excessive motion of the camera. */
            case excessiveMotion

            /** Tracking is limited due to a lack of features visible to the camera. */
            case insufficientFeatures

            /** Tracking is limited due to a relocalization in progress. */
            @available(iOS 11.3, *)
            case relocalizing
        }

4.2.5 ARSessionDelegate

ARSessionDelegate 一共有四个optional 方法,可以将这四个方法拆分为两部分:

接收捕获的视频帧图像

通过ARFrame实例获取静态图像,前面讲过,此处不赘述。

    /**
     This is called when a new frame has been updated.
     
     @param session The session being run.
     @param frame The frame that has been updated.
     */
    optional public func session(_ session: ARSession, didUpdate frame: ARFrame)
处理内容更新

通过下面三个方法,可以追踪场景中锚点的添加、更新和移除状态。

    /**
     This is called when new anchors are added to the session.
     
     @param session The session being run.
     @param anchors An array of added anchors.
     */
    optional public func session(_ session: ARSession, didAdd anchors: [ARAnchor])

    /**
     This is called when anchors are updated.
     
     @param session The session being run.
     @param anchors An array of updated anchors.
     */
    optional public func session(_ session: ARSession, didUpdate anchors: [ARAnchor])
    
    /**
     This is called when anchors are removed from the session.

     @param session The session being run.
     @param anchors An array of removed anchors.
     */
    optional public func session(_ session: ARSession, didRemove anchors: [ARAnchor])

5. 场景理解(Scene Understanding)

理解真实世界的目的,是为了将虚拟物体放置在真实世界中,场景理解需要掌握三个要点:

平面检测

上一节内容已经讲过,ARKit可检测场景中的平面(水平方向、垂直方向),检测到平面之后,会自动添加锚点,我们通过ARSessionDelegate可以获取锚点的更新状态,相关API前面也有讲解,此处不再赘述。

碰撞测试

碰撞测试要这么理解,前面我们讲过 ARCamera 将真实世界的3D场景转换到2D屏幕上,Hit-testing 就是一个还原的过程,将屏幕上的点还原到3D空间中去。理解完原理之后,一切就变得很简单了。

下图形象的展示了一次Hit-testing。用户点击屏幕之后,在AR空间中模拟发射一条射线,射线所经过的线路上的锚点都会作为结果被返回,所以Hit-testing的返回结果是一个数组,但是不是直接将锚点添加到数组中,而是将锚点封装成 ARHitTestResult 对象后,按照离用户的距离排序放入一个数组中。所以图示中这次Hit-testing返回结果中包含三个元素。

Hit-testing

对应的API如下,开发中我们拿到ARHitTestResult数组之后,一般情况下,我们会遍历这个数组,从中获取我们感兴趣的ARHitTestResult对象。

 /**
     Searches the current frame for objects corresponding to a point in the view.
     
     @discussion A 2D point in the view’s coordinate space can refer to any point along a line segment
     in the 3D coordinate space. Hit-testing is the process of finding objects in the world located along this line segment.
     @param point A point in the view’s coordinate system.
     @param types The types of results to search for.
     @return An array of all hit-test results sorted from nearest to farthest.
     */
    open func hitTest(_ point: CGPoint, types: ARHitTestResult.ResultType) -> [ARHitTestResult]

光照估计

光照估计是 ARConfiguration 的一个属性,打开之后渲染虚拟物体的时候,会适应环境的光线情况,这样AR世界看着更逼真。

    /**
     Enable or disable light estimation.
     @discussion Enabled by default.
     */
    open var isLightEstimationEnabled: Bool

6. 渲染

如下如所示,虚拟世界、捕获的真实世界、世界追踪的信息和场景理解信息一起参与渲染,形成AR世界。


Rendering

由于渲染是 SceneKit 来完成的,相关渲染的API在后续文章中详细讲解。

参考

至此,对整个AR系统的框架结构有了宏观上的理解。
下一篇,将会写一个Hello World的AR demo。

感谢
上一篇 下一篇

猜你喜欢

热点阅读