苹果在 WWDC2017 中推出了 ARKit,经过这个新框架能够看出苹果将来会在 AR 方向不断发展,本着学习兴趣,对此项新技术进行了学习,并在团队进行了一次分享,利用业余时间把几周前分享的内容整理成文档供你们交流学习。html
本文并非简单的介绍 ARKit 中的 API 如何使用,而是在介绍 ARKit API 的同时附上了一些理论知识,因此有些内容可能会不太容易理解。笔者能力有限,文章如有误请指出。团队分享(本次分享为网易杭研院前端技术部的内部分享)时进行了一次直播,若文中有不理解的内容,欢迎观看下录播:ARKit 分享直播 。本文也附上分享的 PPT 地址:ARKit-keynote。前端
如需转载请注明做者和原文地址。node
AR 全称 Augmented Reality(加强现实),是一种在摄像机捕捉到的真实世界中加入计算机程序创造的虚拟世界的技术。下图是一个简单的 AR 的 Demo:git
在上图中,椅子是计算机程序建立的虚拟世界的物体,而背景则是摄像机捕捉到的真实世界,AR 系统将二者结合在一块儿。咱们从上图能够窥探出 AR 系统由如下几个基础部分组成:github
根据上面的描述,咱们能够得出 AR 系统的大体结构图:swift
Note:这里只介绍基于计算机显示器的 AR 系统实现方案,此外还有光学透视式和视频透视式方案。可参考加强现实-组成形式。segmentfault
ARKit 是苹果 WWDC2017 中发布的用于开发iOS平台 AR 功能的框架。ARKit 为上一节中提到的 AR 系统架构中各个部分都提供了实现方案,而且为开发者提供了简单便捷的 API,使得开发者更加快捷的开发 AR 功能。数组
ARKit 的使用须要必定的软硬件设施:session
下面几节中,咱们将逐步介绍 ARKit 中 AR 系统的各个组成部分。架构
ARKit 定义了一套简单易用的 API,API 中引入了多个类,为了更加清晰的理解 ARKit,咱们从 AR 系统组成的角度对 ARKit API 进行了分类,以下图:
上图列出了 ARKit API 中的几个主要的类,如 ARSession、ARSessionConfiguration、ARFrame、ARCamera 等。并依据各个类的功能进行了模块划分:红色(World Tracking)、蓝色(Virtual World)、土色(Capture Real Wrold)、紫色(Scene Understanding)、绿色(Rendering)。
对于上图,ARSession 是核心整个ARKit系统的核心,ARSession 实现了世界追踪、场景解析等重要功能。而 ARFrame 中包含有 ARSession 输出的全部信息,是渲染的关键数据来源。虽然 ARKit 提供的 API 较为简单,但看到上面整个框架后,对于初识整个体系的开发者来讲,仍是会觉着有些庞大。不要紧,后面几节会对每一个模块进行单独的介绍,当读完最后时,再回头来看这个架构图,或许会更加明了一些。
咱们将 Virtual World 从 ARKit 架构图中抽出:
ARKit 自己并不提供建立虚拟世界的引擎,而是使用其余 3D/2D 引擎进行建立虚拟世界。iOS 系统上可以使用的引擎主要有:
ARKit 并无明确要求开发者使用哪一种方式构建虚拟世界,开发者能够利用 ARKit 输出的真实世界、世界追踪以及场景解析的信息(存在于 ARFrame 中),本身将经过图形引擎建立的虚拟世界渲染到真实世界中。值得一提的是,ARKit 提供了 ARSCNView 类,该类基于 SceneKit 为 3D 虚拟世界渲染到真实世界提供了很是简单的 API,关于 ARSCNView 会在最后的渲染部分进行介绍,因此下面咱们来介绍下 SceneKit。
这里不对 SceneKit 进行深刻探讨,只简单介绍下基础概念。读者只须要理解 SceneKit 里虚拟世界的构成就能够了。
咱们知道 UIKit 使用一个包含有 x 和 y 信息的 CGPoint 来表示一个点的位置,可是在 3D 系统中,须要一个 z 参数来描述物体在空间中的深度,SceneKit 的坐标系能够参考下图:
这个三维坐标系中,表示一个点的位置须要使用(x,y,z)坐标表示。红色方块位于 x 轴,绿色方块位于 y 轴,蓝色方块位于 z 轴,灰色方块位于原点。在 SceneKit 中咱们能够这样建立一个三维坐标:
let position = SCNVector3(x: 0, y: 5, z: 10)
复制代码
咱们能够将 SceneKit 中的场景(SCNScene)想象为一个虚拟的 3D 空间,而后能够将一个个的节点(SCNNode)添加到场景中。SCNScene 中有惟一一个根节点(坐标是(x:0, y:0, z:0)),除了根节点外,全部添加到 SCNScene 中的节点都须要一个父节点。
下图中位于坐标系中心的就是根节点,此外还有添加的两个节点 NodeA 和 NodeB,其中 NodeA 的父节点是根节点,NodeB 的父节点是 NodeA:
SCNScene 中的节点加入时能够指定一个三维坐标(默认为(x:0, y:0, z:0)),这个坐标是相对于其父节点的位置。这里说明两个概念:
上图中咱们能够看到 NodeA 的坐标是相对于世界坐标系(因为 NodeA 的父节点是根节点)的位置,而 NodeB 的坐标表明了 NodeB 在 NodeA 的本地坐标系位置(NodeB 的父节点是 NodeA)。
有了 SCNScene 和 SCNNode 后,咱们还须要一个摄像机(SCNCamera)来决定咱们能够看到场景中的哪一块区域(就比如现实世界中有了各类物体,但还须要人的眼睛才能看到物体)。摄像机在 SCNScene 的工做模式以下图:
上图中包含如下几点信息:
在 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)
复制代码
最后,咱们须要一个 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
复制代码
捕捉真实世界就是为了将咱们现实世界的场景做为 ARKit 显示场景的背景。为了方便阅读,咱们首先将 Capture Real World 从 ARKit 架构图中抽取出:
若是咱们想要使用 ARKit,咱们必需要建立一个 ARSession 对象并运行 ARSession。基本步骤以下:
// 建立一个 ARSessionConfiguration.
// 暂时无需在乎 ARWorldTrackingSessionConfiguration.
let configuration = ARWorldTrackingSessionConfiguration()
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)
复制代码
从上面的代码看,运行一个 ARSession 的过程是很简单的,那么 ARSession 的底层如何捕捉现实世界场景的呢?
从上一步骤得知 ARFrame 中包含了现实世界场景的全部信息,那么 ARFrame 中与现实世界场景有关的信息有哪些?
var capturedImage: CVPixelBuffer
该属性是摄像机捕捉到的图像信息,就是构成咱们现实世界场景中的一帧图像。顺便说明下,对于摄像机捕捉的每一帧图像,都会生成一个 ARFrame。
var timestamp: TimeInterval
该属性是摄像机捕捉到的对应于 capturedImage 的一帧图像的时间。
var camera: ARCamera
获取现实世界的相机信息。详细介绍见下。
ARCamera 是 ARFrame 中的一个属性,之由于单独拿出来讲,是由于这里有必要介绍下相机的一些特性,ARCamera 中与现实世界场景有关的信息有两个:
var imageResolution: CGSize
该属性表示了相机捕捉到的图像的长度和宽度(以像素为单位),能够理解成捕捉到的图像的分辨率。
var intrinsics: matrix_float3x3
intrinsics 是一个 3x3 矩阵,这个矩阵将咱们现实世界中三维坐标系的点映射到相机捕捉的图像中。有兴趣可看下面的详述。
Intrinsic Matrix 是相机的一个固有属性,也就是说每一个相机都会有 Intrinsic Matrix,由于全部的相机都须要将现实世界中三维空间的点映射到捕捉的图像中二维空间的点。
那么这个矩阵是如何工做的呢?咱们先来看一个图片:
上图包含以下基本信息:
如今咱们须要将三维空间的点(x', y', z')映射到成像平面中的一个点(N')。下面咱们来看下映射过程。
Intrinsic Matrix 通常是下图所示的样子:
上图中,fx 和 fy 是摄像机镜头的焦距,这里不作深究,ox 和 oy 则是点 M(成像平面与 z 轴交点)相对于点 O(成像平面二维坐标系原点)的 x 与 y 方向的偏移。
下图展现了利用 Intrinsic Matrix 将 N 映射 N' 的过程:
上图中,Intrinsic Matrix 与表示点 N 的向量相乘后,再除以 z',就获得了一个 z 坐标为 1 的三维向量, 咱们丢弃掉 z 坐标信息就获得了 N' 的坐标:((x' * fx)/z' + ox, (y' * fy)/z' + oy)。
这就是 Intrinsic Matrix 的做用过程,至于为什么这么映射,则是相机原理的内容了,因为水平有限,就不作介绍了。若是不太好理解,咱们这样简单理解为相机使用这个矩阵就能够将空间中的某个点映射到二维成像平面的一个点。
在第一部分 AR 系统介绍时,咱们看到虚拟椅子是放在地面上的,当咱们移动时能够看到不一样角度,咱们也能够移动椅子,这些功能的实现都离不开世界追踪。总结来讲,世界追踪用来为真实世界与虚拟世界结合提供有效信息,以便咱们能在真实世界中看到一个更加真实的虚拟世界。
为了方便阅读,咱们首先将 World Tracking 从 ARKit 架构图中抽取出:
下面咱们分析一下 ARKit 中与世界追踪相关的技术以及类。
若是咱们想要使用 ARKit,咱们必需要建立一个 ARSession 对象并运行 ARSession。基本步骤以下:
// 建立一个 ARSessionConfiguration.
// 暂时无需在乎 ARWorldTrackingSessionConfiguration.
let configuration = ARWorldTrackingSessionConfiguration()
// Create a session.
let session = ARSession()
// Run.
session.run(configuration)
复制代码
从上面的代码看,运行一个 ARSession 的过程是很简单的,那么 ARSession 的底层如何进行世界追踪的呢?
那么世界追踪到底追踪了哪些信息?下图给出了 AR-World 的坐标系,当咱们运行 ARSession 时设备所在的位置就是 AR-World 的坐标系原点。
在这个 AR-World 坐标系中,ARKit 会追踪如下几个信息:
苹果文档中对世界追踪过程是这么解释的:ARKit 使用视觉惯性测距技术,对摄像头采集到的图像序列进行计算机视觉分析,而且与设备的运动传感器信息相结合。ARKit 会识别出每一帧图像中的特征点,而且根据特征点在连续的图像帧之间的位置变化,而后与运动传感器提供的信息进行比较,最终获得高精度的设备位置和偏转信息。
咱们经过一个 gif 图来理解上面这段话:
在运行 ARSession 时,咱们必需要有一个 Configuration。Configuration 告诉 ARKit 应该如何追踪设备的运动。ARKit 为咱们提供了两种类型的 Configuration:
上面两个 Configuration 的差异就是 DOF 不同,那么什么是 DOF?
DOF
自由度(DOF,Degree Of Freedom)表示描述系统状态的独立参数的个数。6DOF 主要包括以下 6 个参数:
平移:
旋转: 4. Yawing 2. Pitching 3. Rolling
其中 3DOF 中只包含旋转中的三个参数,平移的几个参数其实很好理解,为了更形象的理解旋转参数,咱们看下面几个动图:
Yawing 效果:
Pitching 效果:
Rolling 效果:
3DOF 与 6DOF 追踪的效果差别
ARWorldTrackingSessionConfiguration
使用 3 个旋转参数和 3 个平移参数来追踪物理系统的状态, ARSessionConfiguration
使用 3 个旋转参数来追踪物理系统的状态。那么二者有什么效果差异?
咱们下面看两个图,下图一使用 3DOF 的 ARSessionConfiguration
进行世界追踪,下图二使用 6DOF 的 ARWorldTrackingSessionConfiguration
进行世界追踪:
上面两个图,都是先对设备进行旋转,再对设备进行平移。
那么,对于 3DOF 追踪,咱们旋转设备时能够看到虚拟的飞机视角有所变化;但当平移时,咱们能够看到飞机是随着设备进行移动的。
对于 6DOF 追踪,咱们旋转设备时能够看到虚拟的飞机视角有所变化(这点与 3DOF 追踪没有区别);平移时,咱们能够看到飞机的不一样位置,例如向上平移看到了飞机的上表面,围着飞机平移能够看到飞机的四周,而 3DOF 没有提供这种平移的追踪。若是仍是不理解二者区别,能够看动图的后半段,效果差别实际上是很是明显的。
判断当前设备是否支持某类 SessionConfiguration
class var isSupported: Bool 复制代码
示例代码以下:
if ARWorldTrackingSessionConfiguration.isSupported {
configuration = ARWorldTrackingSessionConfiguration()
} else {
configuration = ARSessionConfiguration()
}
复制代码
关于 ARSessionConfiguration
咱们就介绍到这里,下面咱们看一下 ARFrame。
ARFrame 中包含有世界追踪过程获取的全部信息,ARFrame 中与世界追踪有关的信息主要是:anchors 和 camera:
camera: 含有摄像机的位置、旋转以及拍照参数等信息。
var camera: [ARCamera]
复制代码
ahchors: 表明了追踪的点或面。
var anchors: [ARAnchor]
复制代码
至于 ARCamera 和 ARAnchor 是什么?下面分别进行介绍。
咱们能够把 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 的矩阵,三维坐标系表示一个点不是用三个坐标就能够了吗?
物体在三维空间中的运动一般分类两类:平移和旋转,那么表达一个物体的变化就应该可以包含两类运动变化。
首先看上图,假设有一个长方体(黄色虚线)沿 x 轴平移Δx、沿 y 轴平移Δy、沿 z 轴平移Δz 到了另外一个位置(紫色虚线)。长方体的顶点 P(x1, y1, z1)则平移到了 P'(x2, y2, z2),使用公式表示以下:
在旋转以前,上图中包含如下信息:
那么在旋转以前,P 点坐标能够表示为:
x1 = L * sinα
y1 = L * cosα
z1 = z1
复制代码
下面咱们让长方体绕着 z 轴逆时针旋转β角度,那么看图能够获得如下信息:
那么在旋转以后,P' 点的坐标能够表示为:
使用矩阵来表示:
从上面的分析能够看出,为了表达旋转信息,咱们须要一个 3x3 的矩阵,在表达了旋转信息的 3x3 矩阵中,咱们没法表达平移信息,为了同时表达平移和旋转信息,在 3D 计算机图形学中引入了齐次坐标系,在齐次坐标系中,使用四维矩阵表示一个点或向量:
加入一个变化是先绕着 z 轴旋转 β 角度,再沿 x 轴平移Δx、沿 y 轴平移Δy、沿 z 轴平移Δz,咱们能够用如下矩阵变化表示:
最后,还有一种变化是缩放,在齐次坐标系中只须要在前三列矩阵中某个位置添加一个系数便可,比较简单,这里不在展现矩阵变换。从上面能够看出,为了完整的表达一个物体在 3D 空间的变化,须要一个 4x4 矩阵。
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 中点应该映射到屏幕哪一个点以外,还决定了哪些范围的点是不须要的,咱们看下图:
上图中 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 关联的一个信息是 ARCamera.TrackingState.Reason,这是一个枚举类型:
咱们能够经过 ARSessionObserver 协议去获取追踪状态的变化,比较简单,能够直接查看接口文档,这里不作深刻介绍。
到这里,ARKit 中有关于世界追踪的知识基本介绍完了,世界追踪算是 ARKit 中核心功能了,若是理解了本部份内容,相信去看苹果的接口文档也会觉着很是容易理解。若是没有看懂,能够去看一下分享的录播(本文开头有连接)。
为了方便阅读,咱们首先将 Scene Understanding 从 ARKit 架构图中抽取出:
场景解析主要功能是对现实世界的场景进行分析,解析出好比现实世界的平面等信息,可让咱们把一些虚拟物体放在某些实物处。ARKit 提供的场景解析主要有平面检测、场景交互以及光照估计三种,下面逐个分析。
ARKit 的平面检测用于检测出现实世界的水平面。
上图中能够看出,ARkit 检测出了两个平面,图中的两个三维坐标系是检测出的平面的本地坐标系,此外,检测出的平面是有一个大小范围的。
平面检测是一个动态的过程,当摄像机不断移动时,检测到的平面也会不断的变化。下图中能够看到当移动摄像机时,已经检测到的平面的坐标原点以及平面范围都在不断的变化。
此外,随着平面的动态检测,不一样平面也可能会合并为一个新的平面。下图中能够看到已经检测到的平面随着摄像机移动合并为了一个平面。
开启平面检测很简单,只须要在 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
extent: 表示该平面的大小范围。如上图中检测到的屏幕都有一个范围大小。
var extent: vector_float3
复制代码
当 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 是为了获取当前捕捉到的图像中某点击位置有关的信息(包括平面、特征点、ARAnchor 等)。
先看一下原理图:
当点击屏幕时,ARKit 会发射一个射线,假设屏幕平面是三维坐标系中的 xy 平面,那么该射线会沿着 z 轴方向射向屏幕里面,这就是一次 Hit-testing 过程。这次过程会将射线遇到的全部有用信息返回,返回结果以离屏幕距离进行排序,离屏幕最近的排在最前面。
ARFrame 提供了 Hit-testing 的接口:
func hitTest(_ point: CGPoint, types: ARHitTestResult.ResultType) -> [ARHitTestResult]
复制代码
上述接口中有一个 types 参数,该参数表示这次 Hit-testing 过程须要获取的信息类型。ResultType 有如下四种:
featurePoint 表示这次 Hit-testing 过程但愿返回当前图像中 Hit-testing 射线通过的 3D 特征点。以下图:
estimatedHorizontalPlane 表示这次 Hit-testing 过程但愿返回当前图像中 Hit-testing 射线通过的预估平面。预估平面表示 ARKit 当前检测到一个多是平面的信息,但当前还没有肯定是平面,因此 ARKit 尚未为此预估平面添加 ARPlaneAnchor。以下图:
existingPlaneUsingExtent 表示这次 Hit-testing 过程但愿返回当前图像中 Hit-testing 射线通过的有大小范围的平面。
上图中,若是 Hit-testing 射线通过了有大小范围的绿色平面,则会返回此平面,若是射线落在了绿色平面的外面,则不会返回此平面。
existingPlane 表示这次 Hit-testing 过程但愿返回当前图像中 Hit-testing 射线通过的无限大小的平面。
上图中,平面大小是绿色平面所展现的大小,但 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,上面代码就显示了此过程。
上图中,一个虚拟物体茶杯被放在了现实世界的桌子上。
当周围环境光线较好时,摄像机捕捉到的图像光照强度也较好,此时,咱们放在桌子上的茶杯看起来就比较贴近于现实效果,如上图最左边的图。可是当周围光线较暗时,摄像机捕捉到的图像也较暗,如上图中间的图,此时茶杯的亮度就显得跟现实世界格格不入。
针对这种状况,ARKit 提供了光照估计,开启光照估计后,咱们能够拿到当前图像的光照强度,从而可以以更天然的光照强度去渲染虚拟物体,如上图最右边的图。
光照估计基于当前捕捉到的图像的曝光等信息,给出一个估计的光照强度值(单位为 lumen,光强单位)。默认的光照强度为 1000lumen,当现实世界较亮时,咱们能够拿到一个高于 1000lumen 的值,相反,当现实世界光照较暗时,咱们会拿到一个低于 1000lumen 的值。
ARKit 的光照估计默认是开启的,固然也能够经过下述方式手动配置:
configuration.isLightEstimationEnabled = true
复制代码
获取光照估计的光照强度也很简单,只须要拿到当前的 ARFrame,经过如下代码便可获取估计的光照强度:
let intensity = frame.lightEstimate?.ambientIntensity
复制代码
渲染是呈现 AR world 的最后一个过程。此过程将建立的虚拟世界、捕捉的真实世界、ARKit 追踪的信息以及 ARKit 场景解析的的信息结合在一块儿,渲染出一个 AR world。渲染过程须要实现如下几点才能渲染出正确的 AR world:
若是咱们本身处理这个过程,能够看到仍是比较复杂的,ARKit 为简化开发者的渲染过程,为开发者提供了简单易用的使用 SceneKit(3D 引擎)以及 SpriteKit(2D 引擎)渲染的视图ARSCNView
以及ARSKView
。固然开发者也可使用其余引擎进行渲染,只须要将以上几个信息进行处理融合便可。这里只介绍下ARSCNView
,对于使用其余引擎渲染,可参考 WWDC2017-ARKit 最后的 Metal 渲染示例。
ARSCNView 帮咱们作了以下几件事情:
关于 ARSCNView 的各个属性这里再也不进行一一介绍了,若是已经掌握了以前章节的内容,相信直接看 ARSCNView 的接口文档不会有什么问题。下面对 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 分享直播
欢迎转载本文,请声明原文地址及做者,多谢。