AR 【WWDC2017 ARKit 技术理论】

 
 
 

苹果在 WWDC2017 中推出了 ARKit,经过这个新框架能够看出苹果将来会在 AR 方向不断发展,本着学习兴趣,对此项新技术进行了学习,并在团队进行了一次分享,利用业余时间把几周前分享的内容整理成文档供你们交流学习。html

 
 

本文并非简单的介绍 ARKit 中的 API 如何使用,而是在介绍 ARKit API 的同时附上了一些理论知识,因此有些内容可能会不太容易理解。笔者能力有限,文章如有误请指出。团队分享时进行了一次直播,若文中有不理解的内容,欢迎观看下录播:ARKit 分享直播 。本文也附上分享的 PPT 地址:ARKit-keynotenode

1、什么是 AR?

AR 全称 Augmented Reality(加强现实),是一种在摄像机捕捉到的真实世界中加入计算机程序创造的虚拟世界的技术。下图是一个简单的 AR 的 Demo:git

ARChair.gif
ARChair.gif

在上图中,椅子是计算机程序建立的虚拟世界的物体,而背景则是摄像机捕捉到的真实世界,AR 系统将二者结合在一块儿。咱们从上图能够窥探出 AR 系统由如下几个基础部分组成:程序员

  1. 捕捉真实世界:上图中的背景就是真实世界,通常由摄像机完成。
  2. 虚拟世界:例如上图中的椅子就是虚拟世界中的一个物体模型。固然,能够有不少物体模型,从而组成一个复杂的虚拟世界。
  3. 虚拟世界与现实世界相结合:将虚拟世界渲染到捕捉到的真实世界中。
  4. 世界追踪:当真实世界变化时(如上图中移动摄像机),要能追踪到当前摄像机相对于初始时的位置、角度变化信息,以便实时渲染出虚拟世界相对于现实世界的位置和角度。
  5. 场景解析:例如上图中能够看出椅子是放在地面上的,这个地面实际上是 AR 系统检测出来的。
  6. 与虚拟世界互动:例如上图中缩放、拖动椅子。(其实也属于场景解析的范畴)

根据上面的描述,咱们能够得出 AR 系统的大体结构图:github

ARKitSystem.png
ARKitSystem.png

Note:这里只介绍基于计算机显示器的 AR 系统实现方案,此外还有光学透视式和视频透视式方案。可参考加强现实-组成形式swift

2、ARKit 简介

ARKitLogo.png
ARKitLogo.png

ARKit 是苹果 WWDC2017 中发布的用于开发iOS平台 AR 功能的框架。ARKit 为上一节中提到的 AR 系统架构中各个部分都提供了实现方案,而且为开发者提供了简单便捷的 API,使得开发者更加快捷的开发 AR 功能。segmentfault

ARKit 的使用须要必定的软硬件设施:数组

  • 软件:
    • 开发工具:Xcode9
    • iOS11
    • MacOS 10.12.4 及以上版本(为了支持 Xcode9)
  • 硬件:
    • 处理器为 A9 及以上的 iPhone 或 iPad 设备(iPhone 6s 为 A9 处理器)

下面几节中,咱们将逐步介绍 ARKit 中 AR 系统的各个组成部分。session

3、ARKit 架构

ARKit 定义了一套简单易用的 API,API 中引入了多个类,为了更加清晰的理解 ARKit,咱们从 AR 系统组成的角度对 ARKit API 进行了分类,以下图:架构

ARKitArchitecture
ARKitArchitecture

上图列出了 ARKit API 中的几个主要的类,如 ARSession、ARSessionConfiguration、ARFrame、ARCamera 等。并依据各个类的功能进行了模块划分:红色(World Tracking)、蓝色(Virtual World)、土色(Capture Real Wrold)、紫色(Scene Understanding)、绿色(Rendering)。

对于上图,ARSession 是核心整个ARKit系统的核心,ARSession 实现了世界追踪、场景解析等重要功能。而 ARFrame 中包含有 ARSession 输出的全部信息,是渲染的关键数据来源。虽然 ARKit 提供的 API 较为简单,但看到上面整个框架后,对于初识整个体系的开发者来讲,仍是会觉着有些庞大。不要紧,后面几节会对每一个模块进行单独的介绍,当读完最后时,再回头来看这个架构图,或许会更加明了一些。

4、构建虚拟世界

咱们将 Virtual World 从 ARKit 架构图中抽出:

VirtualWorld.png
VirtualWorld.png

ARKit 自己并不提供建立虚拟世界的引擎,而是使用其余 3D/2D 引擎进行建立虚拟世界。iOS 系统上可以使用的引擎主要有:

  • Apple 3D Framework - SceneKit.
  • Apple 2D Framework - SpriteKit.
  • Apple GPU-accelerated 3D graphics Engine - Metal.
  • OpenGl
  • Unity3D
  • Unreal Engine

ARKit 并无明确要求开发者使用哪一种方式构建虚拟世界,开发者能够利用 ARKit 输出的真实世界、世界追踪以及场景解析的信息(存在于 ARFrame 中),本身将经过图形引擎建立的虚拟世界渲染到真实世界中。值得一提的是,ARKit 提供了 ARSCNView 类,该类基于 SceneKit 为 3D 虚拟世界渲染到真实世界提供了很是简单的 API,关于 ARSCNView 会在最后的渲染部分进行介绍,因此下面咱们来介绍下 SceneKit。

SceneKit 简介

这里不对 SceneKit 进行深刻探讨,只简单介绍下基础概念。读者只须要理解 SceneKit 里虚拟世界的构成就能够了。

SceneKit 的坐标系

咱们知道 UIKit 使用一个包含有 x 和 y 信息的 CGPoint 来表示一个点的位置,可是在 3D 系统中,须要一个 z 参数来描述物体在空间中的深度,SceneKit 的坐标系能够参考下图:

SceneKitCoordinate.png
SceneKitCoordinate.png

这个三维坐标系中,表示一个点的位置须要使用(x,y,z)坐标表示。红色方块位于 x 轴,绿色方块位于 y 轴,蓝色方块位于 z 轴,灰色方块位于原点。在 SceneKit 中咱们能够这样建立一个三维坐标:

let position = SCNVector3(x: 0, y: 5, z: 10)

SceneKit 中的场景和节点

咱们能够将 SceneKit 中的场景(SCNScene)想象为一个虚拟的 3D 空间,而后能够将一个个的节点(SCNNode)添加到场景中。SCNScene 中有惟一一个根节点(坐标是(x:0, y:0, z:0)),除了根节点外,全部添加到 SCNScene 中的节点都须要一个父节点。

下图中位于坐标系中心的就是根节点,此外还有添加的两个节点 NodeA 和 NodeB,其中 NodeA 的父节点是根节点,NodeB 的父节点是 NodeA:

SceneKitNode.png
SceneKitNode.png

SCNScene 中的节点加入时能够指定一个三维坐标(默认为(x:0, y:0, z:0)),这个坐标是相对于其父节点的位置。这里说明两个概念:

  • 本地坐标系:以场景中的某节点(非根节点)为原点创建的三维坐标系
  • 世界坐标系:以根节点为原点建立的三维坐标系称为世界坐标系。

上图中咱们能够看到 NodeA 的坐标是相对于世界坐标系(因为 NodeA 的父节点是根节点)的位置,而 NodeB 的坐标表明了 NodeB 在 NodeA 的本地坐标系位置(NodeB 的父节点是 NodeA)。

SceneKit 中的摄像机

有了 SCNScene 和 SCNNode 后,咱们还须要一个摄像机(SCNCamera)来决定咱们能够看到场景中的哪一块区域(就比如现实世界中有了各类物体,但还须要人的眼睛才能看到物体)。摄像机在 SCNScene 的工做模式以下图:

SceneKitCamera.png
SceneKitCamera.png

上图中包含如下几点信息:

  • SceneKit 中 SCNCamera 拍摄的方向始终为 z 轴负方向。
  • 视野(Field of View)是摄像机的可视区域的极限角度。角度越小,视野越窄,反之,角度越大,视野越宽。
  • 视锥体(Viewing Frustum)决定着摄像头可视区域的深度(z 轴表示深度)。任何不在这个区域内的物体将被剪裁掉(离摄像头太近或者太远),不会显示在最终的画面中。

在 SceneKit 中咱们可使用以下方式建立一个摄像机:

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)

SCNView

最后,咱们须要一个 View 来将 SCNScene 中的内容渲染到显示屏幕上,这个工做由 SCNView 完成。这一步其实很简单,只须要建立一个 SCNView 实例,而后将 SCNView 的 scene 属性设置为刚刚建立的 SCNScene,而后将 SCNView 添加到 UIKit 的 view 或 window 上便可。示例代码以下:

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

5、捕捉真实世界(Capture Real World)

捕捉真实世界就是为了将咱们现实世界的场景做为 ARKit 显示场景的背景。为了方便阅读,咱们首先将 Capture Real World 从 ARKit 架构图中抽取出:

CaptureRealWorld.png
CaptureRealWorld.png

ARSession

若是咱们想要使用 ARKit,咱们必需要建立一个 ARSession 对象并运行 ARSession。基本步骤以下:

// 建立一个 ARSessionConfiguration.
// 暂时无需在乎 ARWorldTrackingSessionConfiguration.
let configuration = ARWorldTrackingSessionConfiguration()
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)

从上面的代码看,运行一个 ARSession 的过程是很简单的,那么 ARSession 的底层如何捕捉现实世界场景的呢?

  • 首先,ARSession 底层使用了 AVCaputreSession 来获取摄像机拍摄的视频(一帧一帧的图像序列)。
  • 而后,ARSession 将获取的图像序列进行处理,最后输出 ARFrame,ARFrame 中就包含有现实世界场景的全部信息。

ARFrame

从上一步骤得知 ARFrame 中包含了现实世界场景的全部信息,那么 ARFrame 中与现实世界场景有关的信息有哪些?

  • var capturedImage: CVPixelBuffer
    该属性是摄像机捕捉到的图像信息,就是构成咱们现实世界场景中的一帧图像。顺便说明下,对于摄像机捕捉的每一帧图像,都会生成一个 ARFrame。

  • var timestamp: TimeInterval
    该属性是摄像机捕捉到的对应于 capturedImage 的一帧图像的时间。

  • var camera: ARCamera
    获取现实世界的相机信息。详细介绍见下。

ARCamera

ARCamera 是 ARFrame 中的一个属性,之由于单独拿出来讲,是由于这里有必要介绍下相机的一些特性,ARCamera 中与现实世界场景有关的信息有两个:

  • var imageResolution: CGSize
    该属性表示了相机捕捉到的图像的长度和宽度(以像素为单位),能够理解成捕捉到的图像的分辨率。

  • var intrinsics: matrix_float3x3
    intrinsics 是一个 3x3 矩阵,这个矩阵将咱们现实世界中三维坐标系的点映射到相机捕捉的图像中。有兴趣可看下面的详述。

Intrinsic Matrix

Intrinsic Matrix 是相机的一个固有属性,也就是说每一个相机都会有 Intrinsic Matrix,由于全部的相机都须要将现实世界中三维空间的点映射到捕捉的图像中二维空间的点。

那么这个矩阵是如何工做的呢?咱们先来看一个图片:

IntrinsicMatrix.png
IntrinsicMatrix.png

上图包含以下基本信息:

  • 一个三维坐标系(红色 x 轴,绿色 y 轴,蓝色 z 轴)。
  • 空间中的一个点(蓝色的点 N,坐标为(x', y', z'))。
  • 相机的成像平面(紫色的平行四边形)
  • 成像平面与 z 轴的交点(点 M)
  • 成像平面的原点(黄色的点 O),也就是捕捉的二维图像的二维坐标系的原点。

如今咱们须要将三维空间的点(x', y', z')映射到成像平面中的一个点(N')。下面咱们来看下映射过程。

Intrinsic Matrix 通常是下图所示的样子:

IntrinsicMatrixValue.png
IntrinsicMatrixValue.png

上图中,fx 和 fy 是摄像机镜头的焦距,这里不作深究,ox 和 oy 则是点 M(成像平面与 z 轴交点)相对于点 O(成像平面二维坐标系原点)的 x 与 y 方向的偏移。

下图展现了利用 Intrinsic Matrix 将 N 映射 N' 的过程:

IntrinsicMatrixMap.png
IntrinsicMatrixMap.png

上图中,Intrinsic Matrix 与表示点 N 的向量相乘后,再除以 z',就获得了一个 z 坐标为 1 的三维向量,
咱们丢弃掉 z 坐标信息就获得了 N' 的坐标:((x' * fx)/z' + ox, (y' * fy)/z' + oy)。

这就是 Intrinsic Matrix 的做用过程,至于为什么这么映射,则是相机原理的内容了,因为水平有限,就不作介绍了。若是不太好理解,咱们这样简单理解为相机使用这个矩阵就能够将空间中的某个点映射到二维成像平面的一个点。

6、世界追踪(World Tracking)

在第一部分 AR 系统介绍时,咱们看到虚拟椅子是放在地面上的,当咱们移动时能够看到不一样角度,咱们也能够移动椅子,这些功能的实现都离不开世界追踪。总结来讲,世界追踪用来为真实世界与虚拟世界结合提供有效信息,以便咱们能在真实世界中看到一个更加真实的虚拟世界。

为了方便阅读,咱们首先将 World Tracking 从 ARKit 架构图中抽取出:

WorldTracking.png
WorldTracking.png

下面咱们分析一下 ARKit 中与世界追踪相关的技术以及类。

ARSession

若是咱们想要使用 ARKit,咱们必需要建立一个 ARSession 对象并运行 ARSession。基本步骤以下:

// 建立一个 ARSessionConfiguration.
// 暂时无需在乎 ARWorldTrackingSessionConfiguration.
let configuration = ARWorldTrackingSessionConfiguration()
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)

从上面的代码看,运行一个 ARSession 的过程是很简单的,那么 ARSession 的底层如何进行世界追踪的呢?

  • 首先,ARSession 底层使用了 AVCaputreSession 来获取摄像机拍摄的视频(一帧一帧的图像序列)。
  • 其次,ARSession 底层使用了 CMMotionManager 来获取设备的运动信息(好比旋转角度、移动距离等)
  • 最后,ARSession 根据获取的图像序列以及设备的运动信息进行分析,最后输出 ARFrame,ARFrame 中就包含有渲染虚拟世界所需的全部信息。

追踪什么?

那么世界追踪到底追踪了哪些信息?下图给出了 AR-World 的坐标系,当咱们运行 ARSession 时设备所在的位置就是 AR-World 的坐标系原点。

WorldTrackingCoordinate.png
WorldTrackingCoordinate.png

在这个 AR-World 坐标系中,ARKit 会追踪如下几个信息:

  • 追踪设备的位置以及旋转,这里的两个信息均是相对于设备起始时的信息。
  • 追踪物理距离(以“米”为单位),例如 ARKit 检测到一个平面,咱们但愿知道这个平面有多大。
  • 追踪咱们手动添加的但愿追踪的点,例如咱们手动添加的一个虚拟物体。

世界追踪如何工做?

苹果文档中对世界追踪过程是这么解释的:ARKit 使用视觉惯性测距技术,对摄像头采集到的图像序列进行计算机视觉分析,而且与设备的运动传感器信息相结合。ARKit 会识别出每一帧图像中的特征点,而且根据特征点在连续的图像帧之间的位置变化,而后与运动传感器提供的信息进行比较,最终获得高精度的设备位置和偏转信息。

咱们经过一个 gif 图来理解上面这段话:

WorldTrackingProcess.gif
WorldTrackingProcess.gif
  • 上图中划出曲线的运动的点表明设备,能够看到以设备为中心有一个坐标系也在移动和旋转,这表明着设备在不断的移动和旋转。这个信息是经过设备的运动传感器获取的。
  • 动图中右侧的黄色点是 3D 特征点。3D 特征点就是处理捕捉到的图像获得的,能表明物体特征的点。例如地板的纹理、物体的边边角角均可以成为特征点。上图中咱们看到当设备移动时,ARKit 在不断的追踪捕捉到的画面中的特征点。
  • ARKit 将上面两个信息进行结合,最终获得了高精度的设备位置和偏转信息。

Configuration

在运行 ARSession 时,咱们必需要有一个 Configuration。Configuration 告诉 ARKit 应该如何追踪设备的运动。ARKit 为咱们提供了两种类型的 Configuration:

  • ARSessionConfiguration:提供 3DOF 追踪。
  • ARWorldTrackingSessionConfiguration:提供 6DOF 追踪。

上面两个 Configuration 的差异就是 DOF 不同,那么什么是 DOF?

  1. DOF

    自由度(DOF,Degree Of Freedom)表示描述系统状态的独立参数的个数。6DOF 主要包括以下 6 个参数:


    DOF.png
    DOF.png
    • 平移:

      1. 上下移动
      2. 左右移动
      3. 先后移动
    • 旋转:

      1. Yawing
      2. Pitching
      3. Rolling

    其中 3DOF 中只包含旋转中的三个参数,平移的几个参数其实很好理解,为了更形象的理解旋转参数,咱们看下面几个动图:

    • Yawing 效果:


      yaw.gif
      yaw.gif
    • Pitching 效果:

      pitch.gif
      pitch.gif
    • Rolling 效果:

      roll.gif
      roll.gif
  1. 3DOF 与 6DOF 追踪的效果差别

    ARWorldTrackingSessionConfiguration 使用 3 个旋转参数和 3 个平移参数来追踪物理系统的状态,
    ARSessionConfiguration 使用 3 个旋转参数来追踪物理系统的状态。那么二者有什么效果差异?

    咱们下面看两个图,下图一使用 3DOF 的 ARSessionConfiguration 进行世界追踪,下图二使用 6DOF 的 ARWorldTrackingSessionConfiguration 进行世界追踪:

    3DOF.gif
    3DOF.gif
    6DOF.gif
    6DOF.gif

    上面两个图,都是先对设备进行旋转,再对设备进行平移。

    那么,对于 3DOF 追踪,咱们旋转设备时能够看到虚拟的飞机视角有所变化;但当平移时,咱们能够看到飞机是随着设备进行移动的。

    对于 6DOF 追踪,咱们旋转设备时能够看到虚拟的飞机视角有所变化(这点与 3DOF 追踪没有区别);平移时,咱们能够看到飞机的不一样位置,例如向上平移看到了飞机的上表面,围着飞机平移能够看到飞机的四周,而 3DOF 没有提供这种平移的追踪。若是仍是不理解二者区别,能够看动图的后半段,效果差别实际上是很是明显的。

  2. 判断当前设备是否支持某类 SessionConfiguration

    class var isSupported: Bool

    示例代码以下:

    if ARWorldTrackingSessionConfiguration.isSupported {
        configuration = ARWorldTrackingSessionConfiguration()
    } else {
        configuration = ARSessionConfiguration()
    }

    关于 ARSessionConfiguration 咱们就介绍到这里,下面咱们看一下 ARFrame。

ARFrame

ARFrame 中包含有世界追踪过程获取的全部信息,ARFrame 中与世界追踪有关的信息主要是:anchors 和 camera:

  • camera: 含有摄像机的位置、旋转以及拍照参数等信息。

    var camera: [ARCamera]
  • ahchors: 表明了追踪的点或面。

    var anchors: [ARAnchor]

至于 ARCamera 和 ARAnchor 是什么?下面分别进行介绍。

ARAnchor

ARAnchor.png
ARAnchor.png

咱们能够把 ARAnchor(AR 锚点) 理解为真实世界中的某个点或平面,anchor 中包含位置信息和旋转信息。拿到 anchor 后,能够在该 anchor 处放置一些虚拟物体。对于 ARAnchor 有以下几种操做:

  • 咱们可使用 ARSession 的 add/remove 方法进行手动添加或删除 Anchor。例如,咱们添加了一个虚拟物体到 ARKit 中,在以后的某个时候咱们想要在刚才的虚拟物体上面再放置一个东西,那么咱们能够为这个虚拟物体添加一个 anchor 到 ARSession 中,这样在后面能够经过 ARSession 获取到这个虚拟物体的锚点信息。

  • 经过 ARSession 获取当前帧的全部 anchors:

    let anchors = session.currentFrame.anchors
  • ARKit 自动添加 anchors。例如,ARKit 检测到了一个平面,ARKit 会为该平面建立一个 ARPlaneAnchor 并添加到 ARSession 中。

  • ARSessionDelegate 能够监听到添加、删除和更新 ARAnchor 的通知。

ARAnchor 中的主要参数是 transform,这个参数是一个 4x4 的矩阵,矩阵中包含了 anchor 偏移、旋转和缩放信息。

var transform: matrix_float4x4

这里可能存在的一个疑问就是,为什么是一个 4x4 的矩阵,三维坐标系表示一个点不是用三个坐标就能够了吗?

4x4 矩阵?

物体在三维空间中的运动一般分类两类:平移和旋转,那么表达一个物体的变化就应该可以包含两类运动变化。

  • 平移
4x4Translation.png
4x4Translation.png

首先看上图,假设有一个长方体(黄色虚线)沿 x 轴平移Δx、沿 y 轴平移Δy、沿 z 轴平移Δz 到了另外一个位置(紫色虚线)。长方体的顶点 P(x1, y1, z1)则平移到了 P'(x2, y2, z2),使用公式表示以下:

4x4TranslationExpression.png
4x4TranslationExpression.png
  • 旋转
4x4Rotation.png
4x4Rotation.png

在旋转以前,上图中包含如下信息:

  • 黄色虚线的长方体
  • P(x1, y1, z1)是长方体的一个顶点
  • P 点在 xy 平面的投影点 Q(x1, y1, 0)
  • Q 与坐标原点的距离为 L
  • Q 与坐标原点连线与 y 轴的夹角是α

那么在旋转以前,P 点坐标能够表示为:

x1 = L * sinα
    y1 = L * cosα
    z1 = z1

下面咱们让长方体绕着 z 轴逆时针旋转β角度,那么看图能够获得如下信息:

  • P 点会绕着 z 轴逆时针旋转β角度到达 P'(x2, y2, z2)
  • P' 在 xy 平面投影点 Q'(x2, y2, 0)
  • Q' 与 Q 在以 xy 平面原点为圆心,半径为 L 的圆上
  • Q' 与原点连线与 Q 与原点连线之间的夹角为 β
  • Q'与原点连线与 y 轴的角度是 α-β。

那么在旋转以后,P' 点的坐标能够表示为:

4x4RotationExpression.png
4x4RotationExpression.png

使用矩阵来表示:

4x4RotationExpression2.png
4x4RotationExpression2.png

从上面的分析能够看出,为了表达旋转信息,咱们须要一个 3x3 的矩阵,在表达了旋转信息的 3x3 矩阵中,咱们没法表达平移信息,为了同时表达平移和旋转信息,在 3D 计算机图形学中引入了齐次坐标系,在齐次坐标系中,使用四维矩阵表示一个点或向量:

HomogeneousPoint.png
HomogeneousPoint.png

加入一个变化是先绕着 z 轴旋转 β 角度,再沿 x 轴平移Δx、沿 y 轴平移Δy、沿 z 轴平移Δz,咱们能够用如下矩阵变化表示:

Homogeneous.png
Homogeneous.png

最后,还有一种变化是缩放,在齐次坐标系中只须要在前三列矩阵中某个位置添加一个系数便可,比较简单,这里不在展现矩阵变换。从上面能够看出,为了完整的表达一个物体在 3D 空间的变化,须要一个 4x4 矩阵。

ARCamera

ARCamera 中的主要参数是:

  • transform: 表示摄像头相对于起始时的位置和旋转信息。至于为什么是 4x4 矩阵,上面已经解释过了。

    var transform: matrix_float4x4
  • eulerAngles: 欧拉角,另外一种表示摄像头的偏转角度信息的方式,与咱们以前介绍的 3DOF 有关,
    欧拉证实了一个物体的任何旋转均可以分解为 yaw、pitch、roll 三个方向的旋转。

    var eulerAngles: vector_float3
  • projectionMatrix: 投影矩阵,其实这个很相似于上面介绍的 Intrinsic Matrix,但不一样点是,投影矩阵是将 AR-world 中的物体投影到屏幕上,因为 AR-world 中采用的是齐次坐标,因此这里是一个 4x4 矩阵,投影矩阵除了决定 AR-world 中点应该映射到屏幕哪一个点以外,还决定了哪些范围的点是不须要的,咱们看下图:

ProjectionMatrix.png
ProjectionMatrix.png

上图中 Field-of-View 和 View-Frustum 影响了投影矩阵的部分参数,对于超过 Field-of-View 或者超出 View-Frustum 范围的点,ARKit 不会对其进行投影映射到屏幕。

此外,ARKit 还提供了一个接口让咱们自定义 Field-of-View 和 View-Frustum:

func projectionMatrix(withViewportSize: CGSize, orientation: UIInterfaceOrientation, zNear: CGFloat, zFar: CGFloat)

追踪质量:

世界追踪须要必定的条件才能达到较好的效果,若是达不到所需的条件要求,那么世界追踪的质量会下降,甚至会没法追踪。较好的世界追踪质量主要有如下三个依赖条件:

  • 运动传感器不能中止工做。若是运动传感器中止了工做,那么就没法拿到设备的运动信息。根据咱们以前提到的世界追踪的工做原理,毫无疑问,追踪质量会降低甚至没法工做。

  • 真实世界的场景须要有必定特征点可追踪。世界追踪须要不断分析和追踪捕捉到的图像序列中特征点,若是图像是一面白墙,那么特征点很是少,那么追踪质量就会降低。

  • 设备移动速度不能过快。若是设备移动太快,那么 ARKit 没法分析出不一样图像帧之中的特征点的对应关系,也会致使追踪质量降低。

追踪状态

世界追踪有三种状态,咱们能够经过 camera.trackingState 获取当前的追踪状态。

TrackingState.png
TrackingState.png

从上图咱们看到有三种追踪状态:

  • Not Available:世界追踪正在初始化,还未开始工做。
  • Normal: 正常工做状态。
  • Limited:限制状态,当追踪质量受到影响时,追踪状态可能会变为 Limited 状态。

与 TrackingState 关联的一个信息是 ARCamera.TrackingState.Reason,这是一个枚举类型:

  • case excessiveMotion:设备移动过快,没法正常追踪。
  • case initializing:正在初始化。
  • case insufficientFeatures:特征过少,没法正常追踪。
  • case none:正常工做。

咱们能够经过 ARSessionObserver 协议去获取追踪状态的变化,比较简单,能够直接查看接口文档,这里不作深刻介绍。

到这里,ARKit 中有关于世界追踪的知识基本介绍完了,世界追踪算是 ARKit 中核心功能了,若是理解了本部份内容,相信去看苹果的接口文档也会觉着很是容易理解。若是没有看懂,能够去看一下分享的录播(本文开头有连接)。

7、场景解析(Scene Understanding)

为了方便阅读,咱们首先将 Scene Understanding 从 ARKit 架构图中抽取出:

SceneUnderstanding.png
SceneUnderstanding.png

场景解析主要功能是对现实世界的场景进行分析,解析出好比现实世界的平面等信息,可让咱们把一些虚拟物体放在某些实物处。ARKit 提供的场景解析主要有平面检测场景交互以及光照估计三种,下面逐个分析。

平面检测(Plane detection)

主要功能:

  • ARKit 的平面检测用于检测出现实世界的水平面。

    PlaneDetection.png
    PlaneDetection.png

    上图中能够看出,ARkit 检测出了两个平面,图中的两个三维坐标系是检测出的平面的本地坐标系,此外,检测出的平面是有一个大小范围的。

  • 平面检测是一个动态的过程,当摄像机不断移动时,检测到的平面也会不断的变化。下图中能够看到当移动摄像机时,已经检测到的平面的坐标原点以及平面范围都在不断的变化。

    MultipleFramePlane.gif
    MultipleFramePlane.gif
  • 此外,随着平面的动态检测,不一样平面也可能会合并为一个新的平面。下图中能够看到已经检测到的平面随着摄像机移动合并为了一个平面。

    PlaneMerge.gif
    PlaneMerge.gif

开启平面检测

开启平面检测很简单,只须要在 run ARSession 以前,将 ARSessionConfiguration 的 planeDetection 属性设为 true 便可。

// Create a world tracking session configuration.
let configuration = ARWorldTrackingSessionConfiguration()
configuration.planeDetection = .horizontal
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)

平面的表示方式

当 ARKit 检测到一个平面时,ARKit 会为该平面自动添加一个 ARPlaneAnchor,这个 ARPlaneAnchor 就表示了一个平面。

ARPlaneAnchor 主要有如下属性:

  • alignment: 表示该平面的方向,目前只有 horizontal 一个可能值,表示这个平面是水平面。ARKit 目前没法检测出垂直平面。

    var alignment: ARPlaneAnchor.Alignment
  • center: 表示该平面的本地坐标系的中心点。以下图中检测到的平面都有一个三维坐标系,center 所表明的就是坐标系的原点:var center: vector_float3

    PlaneDetection.png
    PlaneDetection.png
  • extent: 表示该平面的大小范围。如上图中检测到的屏幕都有一个范围大小。

    var extent: vector_float3

ARSessionDelegate

当 ARKit 系统检测到新平面时,ARKit 会自动添加一个 ARPlaneAnchor 到 ARSession 中。咱们能够经过 ARSessionDelegate 获取当前 ARSession 的 ARAnchor 改变的通知,主要有如下三种状况:

  • 新加入了 ARAnchor

    func session(_ session: ARSession, didAdd anchors: [ARAnchor])

    对于平面检测来讲,当新检测到某平面时,咱们会收到该通知,通知中的 ARAnchor 数组会包含新添加的平面,其类型是 ARPlaneAnchor,咱们能够像下面这样使用:

    func session(_ session: ARSession, didAdd anchors: [ARAnchor]) {
        for anchor in anchors {
            if let anchor = anchor as? ARPlaneAnchor {
                print(anchor.center)
                print(anchor.extent)
            }
        }
    }
  • ARAnchor 更新

    func session(_ session: ARSession, didUpdate anchors: [ARAnchor])

    从上面咱们知道当设备移动时,检测到的平面是不断更新的,当平面更新时,会回调这个接口。

  • 删除 ARAnchor

    func session(_ session: ARSession, didRemove anchors: [ARAnchor])

    当手动删除某个 Anchor 时,会回调此方法。此外,对于检测到的平面来讲,若是两个平面进行了合并,则会删除其中一个,此时也会回调此方法。

场景交互(Hit-testing)

Hit-testing 是为了获取当前捕捉到的图像中某点击位置有关的信息(包括平面、特征点、ARAnchor 等)。

工做原理

先看一下原理图:

Hit-testing.png
Hit-testing.png

当点击屏幕时,ARKit 会发射一个射线,假设屏幕平面是三维坐标系中的 xy 平面,那么该射线会沿着 z 轴方向射向屏幕里面,这就是一次 Hit-testing 过程。这次过程会将射线遇到的全部有用信息返回,返回结果以离屏幕距离进行排序,离屏幕最近的排在最前面。

ResultType

ARFrame 提供了 Hit-testing 的接口:

func hitTest(_ point: CGPoint, types: ARHitTestResult.ResultType) -> [ARHitTestResult]

上述接口中有一个 types 参数,该参数表示这次 Hit-testing 过程须要获取的信息类型。ResultType 有如下四种:

  • featurePoint
    表示这次 Hit-testing 过程但愿返回当前图像中 Hit-testing 射线通过的 3D 特征点。以下图:

    HitFeaturePoint.gif
    HitFeaturePoint.gif
  • estimatedHorizontalPlane
    表示这次 Hit-testing 过程但愿返回当前图像中 Hit-testing 射线通过的预估平面。预估平面表示 ARKit 当前检测到一个多是平面的信息,但当前还没有肯定是平面,因此 ARKit 尚未为此预估平面添加 ARPlaneAnchor。以下图:

    HitEstimatedPlane.gif
    HitEstimatedPlane.gif
  • existingPlaneUsingExtent
    表示这次 Hit-testing 过程但愿返回当前图像中 Hit-testing 射线通过的有大小范围的平面。

    HitExistingPlaneUseExtent.gif
    HitExistingPlaneUseExtent.gif

    上图中,若是 Hit-testing 射线通过了有大小范围的绿色平面,则会返回此平面,若是射线落在了绿色平面的外面,则不会返回此平面。

  • existingPlane
    表示这次 Hit-testing 过程但愿返回当前图像中 Hit-testing 射线通过的无限大小的平面。

    HitExistingPlane.gif
    HitExistingPlane.gif

    上图中,平面大小是绿色平面所展现的大小,但 exsitingPlane 选项表示即便 Hit-testing 射线落在了绿色平面外面,也会将此平面返回。换句话说,将全部平面无限延展,只要 Hit-testing 射线通过了无限延展后的平面,就会返回该平面。

使用方法

下面给出使用 Hit-testing 的示例代码:

// Adding an ARAnchor based on hit-test
let point = CGPoint(x: 0.5, y: 0.5)  // Image center

// Perform hit-test on frame.
let results = frame. hitTest(point, types: [.featurePoint, .estimatedHorizontalPlane])

// Use the first result.
if let closestResult = results.first {
    // Create an anchor for it.
    anchor = ARAnchor(transform: closestResult.worldTransform)
    // Add it to the session.
    session.add(anchor: anchor)
}

上面代码中,Hit-testing 的 point(0.5, 0.5)表明屏幕的中心,屏幕左上角为(0, 0),右下角为(1, 1)。 对于 featurePoint 和 estimatedHorizontalPlane 的结果,ARKit 没有为其添加 ARAnchor,咱们可使用 Hit-testing 获取信息后本身为 ARSession 添加 ARAnchor,上面代码就显示了此过程。

光照估计(Light estimation)

LightEstimation.png
LightEstimation.png

上图中,一个虚拟物体茶杯被放在了现实世界的桌子上。

当周围环境光线较好时,摄像机捕捉到的图像光照强度也较好,此时,咱们放在桌子上的茶杯看起来就比较贴近于现实效果,如上图最左边的图。可是当周围光线较暗时,摄像机捕捉到的图像也较暗,如上图中间的图,此时茶杯的亮度就显得跟现实世界格格不入。

针对这种状况,ARKit 提供了光照估计,开启光照估计后,咱们能够拿到当前图像的光照强度,从而可以以更天然的光照强度去渲染虚拟物体,如上图最右边的图。

光照估计基于当前捕捉到的图像的曝光等信息,给出一个估计的光照强度值(单位为 lumen,光强单位)。默认的光照强度为 1000lumen,当现实世界较亮时,咱们能够拿到一个高于 1000lumen 的值,相反,当现实世界光照较暗时,咱们会拿到一个低于 1000lumen 的值。

ARKit 的光照估计默认是开启的,固然也能够经过下述方式手动配置:

configuration.isLightEstimationEnabled = true

获取光照估计的光照强度也很简单,只须要拿到当前的 ARFrame,经过如下代码便可获取估计的光照强度:

let intensity = frame.lightEstimate?.ambientIntensity

8、渲染(Rendering)

Rendering.png
Rendering.png

渲染是呈现 AR world 的最后一个过程。此过程将建立的虚拟世界、捕捉的真实世界、ARKit 追踪的信息以及 ARKit 场景解析的的信息结合在一块儿,渲染出一个 AR world。渲染过程须要实现如下几点才能渲染出正确的 AR world:

  • 将摄像机捕捉到的真实世界的视频做为背景。
  • 将世界追踪到的相机状态信息实时更新到 AR world 中的相机。
  • 处理光照估计的光照强度。
  • 实时渲染虚拟世界物体在屏幕中的位置。

若是咱们本身处理这个过程,能够看到仍是比较复杂的,ARKit 为简化开发者的渲染过程,为开发者提供了简单易用的使用 SceneKit(3D 引擎)以及 SpriteKit(2D 引擎)渲染的视图ARSCNView以及ARSKView。固然开发者也可使用其余引擎进行渲染,只须要将以上几个信息进行处理融合便可。这里只介绍下ARSCNView,对于使用其余引擎渲染,可参考 WWDC2017-ARKit 最后的 Metal 渲染示例。

ARSCNView 的功能

ARSCNView 帮咱们作了以下几件事情:

  • 将摄像机捕捉到的真实世界的视频做为背景。
  • 处理光照估计信息,不断更新画面的光照强度。
  • 将 SCNNode 与 ARAnchor 绑定,也就是说当添加一个 SCNNode 时,ARSCNView 会同时添加一个 ARAnchor 到 ARKit 中。
  • 不断更新 SceneKit 中的相机位置和角度。
  • 将 SceneKit 中的坐标系结合到 AR world 的坐标系中,不断渲染 SceneKit 场景到真实世界的画面中。

关于 ARSCNView 的各个属性这里再也不进行一一介绍了,若是已经掌握了以前章节的内容,相信直接看 ARSCNView 的接口文档不会有什么问题。下面对 ARSCNViewDelegate 作一下简单介绍。

ARSCNViewDelegate

咱们在介绍场景解析时,已经介绍过了 ARSessionDelegate,而
ARSCNViewDelegate 其实与 ARSessionDelegate 是有关系的,下面咱们再来看下 ARSessionDelegate 的三个回调:

  • 新加入了 ARAnchor

    func session(_ session: ARSession, didAdd anchors: [ARAnchor])

    当 ARKit 新添加一个 ARAnchor 时,ARSessionDelegate 会收到上述回调。此时,ARKit 会回调 ARSCNViewDelegate 的下面一个方法询问须要为此新加的 ARAnchor 添加 SCNNode。

    func renderer(_ renderer: SCNSceneRenderer, nodeFor: ARAnchor) -> SCNNode?

    当调用完上个方法以后,ARKit 会回调 ARSCNViewDelegate 的下面一个方法告知 delegate 已为新添加的 ARAnchor 添加了一个 SCNNode。

    func renderer(_ renderer: SCNSceneRenderer, didAdd: SCNNode, for: ARAnchor)
  • ARAnchor 更新

    func session(_ session: ARSession, didUpdate anchors: [ARAnchor])

    当某个 ARAnchor 更新时,ARSessionDelegate 会收到上述回调,此时,ARSCNViewDelegate 会收到如下回调:

    func renderer(_ renderer: SCNSceneRenderer, willUpdate: SCNNode, for: ARAnchor)
    func renderer(_ renderer: SCNSceneRenderer, didUpdate: SCNNode, for: ARAnchor)
  • 删除 ARAnchor

    func session(_ session: ARSession, didRemove anchors: [ARAnchor])

    当某个 ARAnchor 删除时,ARSessionDelegate 会收到上述回调,此时,ARSCNViewDelegate 会收到如下回调:

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

本章节没有对渲染进行太过深刻的介绍,主要考虑到渲染过程当中用到的知识点,在以前大部分已经介绍过了,剩下的只是一些接口的使用方法,相信使用过苹果各类 Kit 的开发者在掌握以上章节内容后,直接查看开发者文档,并参考苹果的官方 demo,使用起来应该不会遇到太多困难。

介绍完渲染以后,本文也就算是结束了。文中的内容涉及到了多个图形学的有关知识,有些不太好理解的欢迎交流,或者查看分享的录播视频:ARKit 分享直播

欢迎转载本文,请声明原文地址及做者,多谢。

9、参考文档

做者:程序员钙片吃多了 连接:http://www.jianshu.com/p/7faa4a3af589 來源:简书 著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。