原创:知识探索型文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 1、概念
- 2、设备追踪
- 3、ARFrame
- 4、场景解析
- 5、SceneKit
- Demo
- 参考文献
1、概念
AR 全称 Augmented Reality
(增强现实)是一种在视觉上呈现虚拟物体与现实场景结合的技术。Apple 公司在 2017 年正式推出了ARKit
,iOS 开发者可以在这个平台上使用简单便捷的 API 来开发 AR 应用程序。为了获得 ARKit
的完整功能,需要A9
及以上芯片。
AR 的全称是增强现实,通过显示屏与各类传感器,能让虚拟世界中创作出的物品与现实世界产生交互。
技术方面,现有 AR 解决方案已经可以检测并记录虚拟物品在现实环境中的准确位置、分辨虚拟物品与现实中物品的遮挡关系、为虚拟物体增加在现实中的光源与阴影、让虚拟物品与现实中的物品产生交互等。就像其它新技术一样,AR 的具体使用场景仍处于探索阶段,下面是我在关注的几个使用角度。
游戏 - 沉浸与代入感:说到虚拟现实的应用,许多人可能都会想到游戏。起先我并没太注意这些所谓 AR 游戏与直接在手机里玩有什么区别,直到前一阵子,精灵宝可梦支持了 AR 模式,允许你把宝可梦放在世界中
在真实世界中与宝可梦互动的体验让我意识到,在手机中与模型交互,给人直观的感觉始终是在和屏幕互动,无论模型和场景搭建的有多精致,用户感知到的始终只是屏幕。将模型放在现实世界中,无论你怎么移动手机,它还在那里。虽然仍旧摸不到,但能看到它出现在日常生活中,熟悉的现实里,带来的沉浸与代入感完全不同。
当我打开 AR 应用时,恍然意识到三维模型本就应该空间概念。建筑学用二维的三视图来避免图纸与模型潜在的不确定因素,呈现在空间中的三维模型则可以彻底避免这个问题,人可以自然地走动来确定所有细节。
若你将 AR 的技术开发进度想象成建造一辆车,ARKit
的定位便是配件,之后的所有功能都建立在这个基础之上。ARKit
中提供一些与 AR 相关的核心能力,如:提供景深信息来判断物体在空间中的位置、用空间锚点来记录虚拟物品在真实世界中的经纬度及海拔坐标、用摄像头对面部信息进行追踪等。下图中,图标下方的阴影及切换角度后物品位置不变的特性,便是 ARKit
提供的基础能力。
原深感镜头组
许多人了解这个镜头组件是因为 iPhone X 发布会上的面容 ID,替换了传统指纹识别来用于安全的验证身份。实际上原深感镜头组用途不仅于身份验证,它会投射 30,000 多个不可见的点来来获取准确的深度数据,这些数据对 AR 很有帮助。前置摄像头所做的摄像时背景替换,脸上试戴墨镜这类 AR 应用,许多都是通过 ARKit
调用原深感摄像头组件的硬件来实现的。
针对 AR 模型的技术准备 USDZ
市面上 3D 建模软件众多,各家选用的导出格式也差异非常大。为解决素材格式不统一的问题,Apple 与皮克斯合作推出了 USDZ
模型封装格式。此后 AR 场景所采用的素材均需为 USDZ
,避免了项目中各类文件混杂的情况。USDZ
自身也是个实力派,支持将模型的动画,纹理材质等众多与模型相关的内容,全部封装在一个文件中。
空间感知芯片 U 系列
U 系列是在 iPhone 设备中新增的超宽带 UWB 芯片,它能提供在较大空间内极度精准定位的能力。举个例子,若你的 AirPods Pro 找不到了,配备 U 系列芯片的手机可以准确指向该耳机的准确方向与距离。现有 AR 技术主要基于摄像头,当用户脱离摄像头区域后,便会丢失目标,若用户携带 Air Tag 定位器,则可能用 U 芯片进行定位,来实现许多之前无法做到的场景。虽 Apple 暂时还没开放它在 AR 场景的应用,但 U 系列芯片给开发者提供了许多想象力。
iOS 平台的 AR 应用通常由 ARKit
和渲染引擎两部分构成:
![](https://img.haomeiwen.com/i9570900/c972510e48d77aea.png)
ARKit
的 ARSession
负责管理每一帧的信息。ARSession
做了两件事,拍摄图像并获取传感器数据,对数据进行分析处理后逐帧输出。如下图:
![](https://img.haomeiwen.com/i9570900/4b00374b5fbd030e.png)
2、设备追踪
a、启动 ARSession
设备追踪确保了虚拟物体的位置不受设备移动的影响。在启动 ARSession
时需要传入一个 ARSessionConfiguration
的子类对象,以区别三种追踪模式:ARFaceTrackingConfiguration
、ARWorldTrackingConfiguration
、AROrientationTrackingConfiguration
。其中 ARFaceTrackingConfiguration
可以识别人脸的位置、方向以及获取拓扑结构。此外,还可以探测到预设的 52 种丰富的面部动作,如眨眼、微笑、皱眉等等。ARFaceTrackingConfiguration
需要调用支持 TrueDepth
的前置摄像头进行追踪。以ARWorldTrackingConfiguration
为例进行追踪,获取特征点。
// 创建一个 ARSessionConfiguration
let configuration = ARWorldTrackingSessionConfiguration()
// Create a session
let session = ARSession()
// Run
session.run(configuration)
b、ARSession 底层如何进行世界追踪
-
ARSession
底层使用了AVCaputreSession
来获取摄像机拍摄的视频(一帧一帧的图像序列)。 -
ARSession
底层使用了CMMotionManager
来获取设备的运动信息(比如旋转角度、移动距离等) -
ARSession
根据获取的图像序列以及设备的运动信息进行分析,最后输出ARFrame
,ARFrame
中就包含有渲染虚拟世界所需的所有信息。
![](https://img.haomeiwen.com/i9570900/d04841a3ebfd5e64.png)
![](https://img.haomeiwen.com/i9570900/b2efa92e10ad0879.png)
c、追踪信息点
AR-World
的坐标系如下,当我们运行 ARSession
时设备所在的位置就是 AR-World
的坐标系原点。
![](https://img.haomeiwen.com/i9570900/a0019045583a5029.png)
在这个 AR-World
坐标系中,ARKit
会追踪以下几个信息:
- 追踪设备的位置以及旋转,这里的两个信息均是相对于设备起始时的信息。
- 追踪物理距离(以“米”为单位),例如
ARKit
检测到一个平面,我们希望知道这个平面有多大。 - 追踪我们手动添加的希望追踪的点,例如我们手动添加的一个虚拟物体。
d、追踪如何工作
ARKit
使用视觉惯性测距技术,对摄像头采集到的图像序列进行计算机视觉分析,并且与设备的运动传感器信息相结合。ARKit
会识别出每一帧图像中的特征点,并且根据特征点在连续的图像帧之间的位置变化,然后与运动传感器提供的信息进行比较,最终得到高精度的设备位置和偏转信息。
![](https://img.haomeiwen.com/i9570900/a370287025d053ec.gif)
上图中划出曲线的运动的点代表设备,可以看到以设备为中心有一个坐标系也在移动和旋转,这代表着设备在不断的移动和旋转。这个信息是通过设备的运动传感器获取的。
动图中右侧的黄色点是 3D 特征点。3D特征点就是处理捕捉到的图像得到的,能代表物体特征的点。例如地板的纹理、物体的边边角角都可以成为特征点。上图中我们看到当设备移动时,ARKit
在不断的追踪捕捉到的画面中的特征点。
ARKit
将上面两个信息进行结合,最终得到了高精度的设备位置和偏转信息。
e、ARWorldTrackingConfiguration
ARWorldTrackingConfiguration
提供 6DoF
(Six Degree of Freedom
)的设备追踪。包括三个姿态角 Yaw
(偏航角)、Pitch
(俯仰角)和 Roll
(翻滚角),以及沿笛卡尔坐标系中 X
、Y
和 Z
三轴的偏移量。
![](https://img.haomeiwen.com/i9570900/fd0d36e148e673f2.png)
不仅如此,ARKit
还使用了 VIO
(Visual-Inertial Odometry
)来提高设备运动追踪的精度。在使用惯性测量单元(IMU
)检测运动轨迹的同时,对运动过程中摄像头拍摄到的图片进行图像处理。将图像中的一些特征点的变化轨迹与传感器的结果进行比对后,输出最终的高精度结果。
从追踪的维度和准确度来看,ARWorldTrackingConfiguration
非常强悍。但它也有两个致命的缺点:受环境光线质量影响和受剧烈运动影响。由于在追踪过程中要通过采集图像来提取特征点,所以图像的质量会影响追踪的结果。在光线较差的环境下(比如夜晚或者强光),拍摄的图像无法提供正确的参考,追踪的质量也会随之下降。追踪过程中会逐帧比对图像与传感器结果,如果设备在短时间内剧烈的移动,会很大程度上干扰追踪结果。
e、追踪状态
世界追踪有三种状态,我们可以通过 camera.trackingState
获取当前的追踪状态。
![](https://img.haomeiwen.com/i9570900/09aabdbecb7e805e.png)
从上图我们看到有三种追踪状态:
Not Available// 世界追踪正在初始化,还未开始工作
Normal// 正常工作状态
Limited// 限制状态,当追踪质量受到影响时,追踪状态可能会变为 Limited 状态
与 TrackingState
关联的一个信息是ARCamera.TrackingState.Reason
,这是一个枚举类型:
case excessiveMotion// 设备移动过快,无法正常追踪
case initializing// 正在初始化
case insufficientFeatures// 特征过少,无法正常追踪
case none// 正常工作
我们可以通过 ARSessionObserver
协议去获取追踪状态的变化。
3、ARFrame
a、ARFrame
ARFrame
中包含有世界追踪过程获取的所有信息,ARFrame
中与世界追踪有关的信息主要是:anchors
和 camera
。
//camera: 含有摄像机的位置、旋转以及拍照参数等信息
var camera: [ARCamera]
//ahchors: 代表了追踪的点或面
var anchors: [ARAnchor]
b、ARAnchor
ARAnchor
是空间中相对真实世界的位置和角度。ARAnchor
可以添加到场景中,或是从场景中移除。基本上来说,它们用于表示虚拟内容在物理环境中的锚定,所以如果要添加自定义 anchor
,添加到 session
里就可以了,它会在 session
生命周期中一直存在。但如果你在运行诸如平面检测功能,ARAnchor
则会被自动添加到 session
中。
要响应被添加的 anchor
,可以从 current ARFrame
中获得完整列表,此列表包含 session
正在追踪的所有 anchor
。或者也可以响应 delegate
方法,例如 add
、update
以及 remove
,session
中的 anchor
被添加、更新或移除时会通知。
c、ARCamera
![](https://img.haomeiwen.com/i9570900/06518b5dba229239.png)
每个 ARFrame
都会包含一个 ARCamera
。ARCamera
对象表示虚拟摄像头。虚拟摄像头就代表了设备的角度和位置。
-
ARCamera
提供了一个transform
。transform
是一个4x4
矩阵。提供了物理设备相对于初始位置的变换。 -
ARCamera
提供了追踪状态(tracking state
),通知你如何使用transform
。 -
ARCamera
提供了相机内部功能(camera intrinsics
)。包括焦距和主焦点,用于寻找投影矩阵。投影矩阵是ARCamera
上的一个convenience
方法,可用于渲染虚拟你的几何体。
4、场景解析
场景解析主要功能是对现实世界的场景进行分析,解析出比如现实世界的平面等信息,可以让我们把一些虚拟物体放在某些实物处。ARKit
提供的场景解析主要有平面检测、场景交互以及光照估计三种,下面逐个分析。
![](https://img.haomeiwen.com/i9570900/d1d8740d6f1d967e.png)
a、平面检测(Plane detection)
ARKit
的平面检测用于检测出现实世界的水平面。
![](https://img.haomeiwen.com/i9570900/291093e979220847.png)
❶ 上图中可以看出,ARkit
检测出了两个平面,图中的两个三维坐标系是检测出的平面的本地坐标系,此外,检测出的平面是有一个大小范围的。
❷ 平面检测是一个动态的过程,当摄像机不断移动时,检测到的平面也会不断的变化。下图中可以看到当移动摄像机时,已经检测到的平面的坐标原点以及平面范围都在不断的变化。
![](https://img.haomeiwen.com/i9570900/629bf0668654a53c.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
就表示了一个平面。
❺ 当 ARKit
系统检测到新平面时,ARKit
会自动添加一个 ARPlaneAnchor
到 ARSession
中。我们可以通过 ARSessionDelegate
获取当前 ARSession
的 ARAnchor
改变的通知,主要有以下三种情况:
新加入了 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:当手动删除某个 Anchor
时就会回调此方法。此外,对于检测到的平面来说,如果两个平面进行了合并,则会删除其中一个,此时也会回调此方法。
func session(_ session: ARSession, didRemove anchors: [ARAnchor])
b、场景交互(Hit-testing)
Hit-testing
是为了获取当前捕捉到的图像中某点击位置有关的信息(包括平面、特征点、ARAnchor
等)。原理图如下:
![](https://img.haomeiwen.com/i9570900/8260e3460588ef02.png)
当点击屏幕时,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 特征点。
![](https://img.haomeiwen.com/i9570900/644974525a43ab41.gif)
estimatedHorizontalPlane:表示此次 Hit-testing
过程希望返回当前图像中 Hit-testing
射线经过的预估平面。预估平面表示 ARKit
当前检测到一个可能是平面的信息,但当前尚未确定是平面,所以 ARKit
还没有为此预估平面添加 ARPlaneAnchor
。
![](https://img.haomeiwen.com/i9570900/c547a8557cde46e5.gif)
existingPlaneUsingExtent:表示此次Hit-testing
过程希望返回当前图像中Hit-testing
射线经过的有大小范围的平面。
![](https://img.haomeiwen.com/i9570900/df1c1fddef83ed8b.gif)
existingPlane:表示此次Hit-testing
过程希望返回当前图像中Hit-testing
射线经过的无限大小的平面。
![](https://img.haomeiwen.com/i9570900/634bd58d0849a5a2.gif)
上图中,平面大小是绿色平面所展示的大小,但exsitingPlane
选项表示即使Hit-testing
射线落在了绿色平面外面,也会将此平面返回。换句话说,将所有平面无限延展,只要 Hit-testing 射线经过了无限延展后的平面,就会返回该平面。
Demo演示:Hit-testing
的point(0.5, 0.5)
代表屏幕的中心,屏幕左上角为(0, 0)
,右下角为(1, 1)
。 对于 featurePoint
和 estimatedHorizontalPlane
的结果,ARKit
没有为其添加ARAnchor
,我们可以使用Hit-testing
获取信息后自己为ARSession
添加 ARAnchor
,下面代码就显示了此过程。
// 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)
}
c、光照估计(Light estimation)
下图中,一个虚拟物体茶杯被放在了现实世界的桌子上。
![](https://img.haomeiwen.com/i9570900/1ecc3ceb181eb244.png)
当周围环境光线较好时,摄像机捕捉到的图像光照强度也较好,此时,我们放在桌子上的茶杯看起来就比较贴近于现实效果,如上图最左边的图。但是当周围光线较暗时,摄像机捕捉到的图像也较暗,如上图中间的图,此时茶杯的亮度就显得跟现实世界格格不入。
针对这种情况,ARKit
提供了光照估计,开启光照估计后,我们可以拿到当前图像的光照强度,从而能够以更自然的光照强度去渲染虚拟物体,如上图最右边的图。
光照估计基于当前捕捉到的图像的曝光等信息,给出一个估计的光照强度值(单位为lumen
,光强单位)。默认的光照强度为 1000lumen
,当现实世界较亮时,我们可以拿到一个高于 1000lumen
的值,相反,当现实世界光照较暗时,我们会拿到一个低于 1000lumen
的值。
ARKit
的光照估计默认是开启的,当然也可以通过下述方式手动配置:
configuration.isLightEstimationEnabled = true
获取光照估计的光照强度也很简单,只需要拿到当前的 ARFrame
,通过以下代码即可获取估计的光照强度:
let intensity = frame.lightEstimate?.ambientIntensity
5、SceneKit
a、简介
渲染是呈现AR world
的最后一个过程。此过程将创建的虚拟世界、捕捉的真实世界、ARKit
追踪的信息以及 ARKit
场景解析的的信息结合在一起,渲染出一个 AR world
。渲染过程需要实现以下几点才能渲染出正确的 AR world
:
- 将摄像机捕捉到的真实世界的视频作为背景。
- 将世界追踪到的相机状态信息实时更新到 AR world 中的相机。
- 处理光照估计的光照强度。
- 实时渲染虚拟世界物体在屏幕中的位置。
![](https://img.haomeiwen.com/i9570900/03e8ac488368b940.png)
如果我们自己处理这个过程,可以看到还是比较复杂的,ARKit
为简化开发者的渲染过程,为开发者提供了简单易用的使用SceneKit
(3D 引擎)以及 SpriteKit
(2D 引擎)渲染的视图ARSCNView
以及ARSKView
。当然开发者也可以使用其他引擎进行渲染,只需要将以上几个信息进行处理融合即可。
b、SceneKit 的坐标系
我们知道 UIKit
使用一个包含有 x
和 y
信息的 CGPoint
来表示一个点的位置,但是在 3D 系统中,需要一个z
参数来描述物体在空间中的深度,SceneKit
的坐标系可以参考下图:
![](https://img.haomeiwen.com/i9570900/464664cbecfd4159.png)
这个三维坐标系中,表示一个点的位置需要使用(x,y,z)
坐标表示。红色方块位于x
轴,绿色方块位于 y
轴,蓝色方块位于z
轴,灰色方块位于原点。在SceneKit
中我们可以这样创建一个三维坐标:
let position = SCNVector3(x: 0, y: 5, z: 10)
c、SceneKit 中的场景和节点
我们可以将 SceneKit
中的场景(SCNScene
)想象为一个虚拟的 3D 空间,然后可以将一个个的节点(SCNNode
)添加到场景中。SCNScene
中有唯一一个根节点(坐标是(x:0, y:0, z:0)
),除了根节点外,所有添加到 SCNScene
中的节点都需要一个父节点。
下图中位于坐标系中心的就是根节点,此外还有添加的两个节点 NodeA
和 NodeB
,其中 NodeA
的父节点是根节点,NodeB
的父节点是 NodeA
。
![](https://img.haomeiwen.com/i9570900/14f9a27be6e405ee.png)
SCNScene
中的节点加入时可以指定一个三维坐标(默认为(x:0, y:0, z:0)
),这个坐标是相对于其父节点的位置。这里说明两个概念:
- 本地坐标系:以场景中的某节点(非根节点)为原点建立的三维坐标系
- 世界坐标系:以根节点为原点创建的三维坐标系称为世界坐标系。
上图中我们可以看到 NodeA
的坐标是相对于世界坐标系(由于NodeA
的父节点是根节点)的位置,而NodeB
的坐标代表了 NodeB
在 NodeA
的本地坐标系位置(NodeB
的父节点是NodeA
)。
d、SceneKit 中的摄像机
有了 SCNScene
和SCNNode
后,我们还需要一个摄像机(SCNCamera
)来决定我们可以看到场景中的哪一块区域(就好比现实世界中有了各种物体,但还需要人的眼睛才能看到物体)。摄像机在 SCNScene
的工作模式如下图:
![](https://img.haomeiwen.com/i9570900/1392c7b4f2d5c0cc.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)
e、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
Demo
Demo在我的Github上,欢迎下载。
Multi-MediaDemo
网友评论