文章目录
- 一、ARKit简介
- 二、ARKit API 中的几个主要的类
- 1. ARSCNView
- 2. ARSession
- 3. ARFrame
- 4.ARAnchor
- 5. ARWorldTrackingSessionConfiguration
- 6. ARSCNViewDelegate
- 7. ARSessionDelegate
- 三、ARKit示例
- 1. 导入框架
- 2. 设置SceneKit View
- 3. 配置ARSCNView Session
- 4. Camera 授权
- 5. 添加3D物体
- 6. 添加手势
一、ARKit简介
ARKit 是苹果 WWDC2017 中发布的用于开发iOS平台 AR 功能的框架。AR 全称 Augmented Reality(增强现实),是一种在摄像机捕捉到的真实世界中加入计算机程序创造的虚拟世界的技术。
AR 系统由以下几个基础部分组成:
- 捕捉真实世界:ARKit 利用摄像头拍摄现实场景的画面。
- 虚拟世界:用SceneKit来建立虚拟世界。
- 虚拟世界与现实世界相结合:ARKit 负责将现实世界和虚拟世界的信息融合,并渲染出一个 AR 世界。
- 维持世界追踪:当真实世界变化时(移动摄像头),要能追踪到当前摄像机相对于初始时的位置、角度变化信息,以便实时渲染出虚拟世界相对于现实世界的位置和角度。
- 进行场景解析:指的是解析现实世界中有无特征点、平面等关键信息。
处理与虚拟世界的互动:指的是当用户点击或拖动屏幕时,处理有没有点击到虚拟物体或者要不要进行添加/删除物体的操作。
二、ARKit API 中的几个主要的类
1. ARSCNView
这是 SceneKit 主视图 SCNView 的子类,帮我们用 SceneKit 渲染的 3D 内容来增强实时摄像头视图,将设备摄像头的实时视频流渲染为场景背景,并会自动匹配 SceneKit 空间和真实世界。这个类做了下面几件事:
- 在视图中渲染设备摄像头的实时视频流,并就其设置为 3D 场景的背景
- ARKit 的 3D 坐标系会匹配 SceneKit 的 3D 坐标系,所以此视图渲染的对象会自动匹配增强后的 ARKit 世界视图
- 自动移动虚拟 SceneKit 3D 摄像头来匹配 ARKit 追踪到的 3D 位置,所以不需要再写代码连接 ARKit 移动事件与 SceneKit 3D 渲染。
ARSCNView 本身不会做 AR 处理,但它需要 AR session 对象来管理设备摄像头和运动处理。负责综合虚拟世界(SceneKit)的信息和现实世界的信息(由ARSession 类负责采集),然后将它们综合渲染呈现出一个 AR 世界。
2. ARSession
每个增强现实会话都都需要有一个 ARSession 实例。它负责控制摄像头、聚合所有来自设备的传感器数据等等以构建无缝体验(负责采集现实世界的信息)。ARSCNView 实例已经有 ARSession 实例,只需要在开始的时候配置一下。
3. ARFrame
ARFrame 包含了两部分信息:ARAnchor 和 ARCamera。
其中,ARCamera 指的是当前摄像机的位置和旋转信息。这一部分 ARKit 已经为我们配置好,不用特别配置。
4.ARAnchor
指的是现实世界中的锚点,具体解释如下:可以把 ARAnchor(锚点)理解为真实世界中的某个点或平面,anchor 中包含位置信息和旋转信息。拿到 anchor 后,可以在该 anchor 处放置一些虚拟物体。也可以与 SCNNode 可以绑定。它有一个子类:ARPlaneAnchor,专门指的是一个代表水平面的锚点。
5. ARWorldTrackingSessionConfiguration
这个类会告诉 ARSession,在真实世界中追踪用户时需要使用六个自由度,roll、pitch、yaw 以及 X轴、Y轴、Z轴上的变换。如果不用这个类,就只能创建在同一个点旋转查看增强内容的 AR 体验。有了这个类,就可以在 3D 空间里绕着物体移动了。如果你不需要在 X轴、Y轴、Z轴上的变换,用户就会在投影增强内容时保持在固定位置,这时可以用 ARSessionConfiguration 类替代此类来初始化 ARSession 实例。
ARSession 是整个ARKit系统的核心,ARSession 实现了世界追踪、场景解析等重要功能。而 ARFrame 中包含有 ARSession 输出的所有信息,是渲染的关键数据来源。
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 也去无法找到并追踪用户。尝试避免看向纯色、反光表面等地方。
- 快速移动:通常情况下检测和估算 3D 姿态只会借助图片,如果摄像头移动太快图片就会糊,从而导致追踪失败。但 ARKit 会利用视觉惯性里程计,综合图片信息和设备运动传感器来估计用户转向的位置。因此 ARKit 在追踪方面非常强大。
6. ARSCNViewDelegate
先介绍 ARSCNView 的代理:ARSCNViewDelegate,他有以下几个回调方法。
func renderer(SCNSceneRenderer, nodeFor: ARAnchor)
当 ARSession 检测到一个锚点时,可以在这个回调方法中决定是否给它返回一个 SCNNode。默认是返回一个空的 SCNNode(),我们可以根据自己的需要将它改成只在检测到平面锚点(ARPlaneAnchor)时返回一个锚点,诸如此类。
func renderer(SCNSceneRenderer, didAdd: SCNNode, for: ARAnchor)
func renderer(SCNSceneRenderer, willUpdate: SCNNode, for: ARAnchor)
func renderer(SCNSceneRenderer, didUpdate: SCNNode, for: ARAnchor)
func renderer(SCNSceneRenderer, didRemove: SCNNode, for: ARAnchor)
以上方法会在为一个锚点已经添加、将要更新、已经更新、已经移除一个虚拟锚点时进行回调。
7. ARSessionDelegate
ARSession 类也有自己的代理:ARSessionDelegate
func session(ARSession, didUpdate: ARFrame)
在 ARKit 中,当用户移动手机时,会实时更新很多 ARFrame。这个方法会在更新了 ARFrame 时,进行回调。它可以用于类似于始终想维持一个虚拟物体在屏幕中间的场景,只需要在这个方法中将该节点的位置更新为最新的 ARFrame 的中心点即可。
func session(ARSession, didAdd: [ARAnchor])
func session(ARSession, didUpdate: [ARAnchor])
func session(ARSession, didRemove: [ARAnchor])
如果使用了 ARSCNViewDelegate 或 ARSKViewDelegate,那上面三个方法不必实现。因为另外的 Delegate 的方法中除了锚点以外,还包含节点信息,这可以让我们有更多的信息进行处理。
三、ARKit示例
1. 导入框架
import ARKit
2. 设置SceneKit View
private lazy var sceneView: ARSCNView = {
let tmpView = ARSCNView()
tmpView.frame = CGRect(x: 0, y: kNaviBarMaxY, width: kScreenWidth, height: kScreenHeight - kNaviBarMaxY)
// 添加光源
tmpView.autoenablesDefaultLighting = true
tmpView.automaticallyUpdatesLighting = true
return tmpView
}()
3. 配置ARSCNView Session
我们的AR应用程序是通过摄像头观察世界和周围的环境。所以接下来我们需要设置Camera:
配置ARKit SceneKit View。在ViewController类中插入以下代码:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
The World Tracking配置跟踪设备的方向和位置,它还能通过设备的Camera探测真实世界的表面。
设置sceneView’s AR session来运行我们刚刚初始化的配置。AR session管理视图内容的运动跟踪和camera图像处理。
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
停止tracking和视图内容的图像。
4. Camera 授权
5. 添加3D物体
private func addBox() {
let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
let boxNode = SCNNode()
boxNode.geometry = box
boxNode.position = SCNVector3(0, 0, -0.2)
let scene = SCNScene()
scene.rootNode.addChildNode(boxNode)
sceneView.scene = scene
}
- 创建一个Box,1 Float = 1 meter。
- 创建一个node。node表示物体在三维空间中的位置和坐标。node本身没有可见的内容。
- 给node设置一个形状(Box)。
- 设置box的位置。这个位置相对于camera的,右边是X正,左边是X负。上面表示Y正,向下表示Y负。向后表示Z正,向前表示Z负。
- 创建一个scene(SceneKit scene),将box添加到场景中去。
- 将sceneView的scene设置为显示刚刚创建的场景。
6. 添加手势
- (点击删除3D物体)
private func addTapGestureToSceneView() {
let tap = UITapGestureRecognizer(target: self, action: #selector(didTap(withGestureRecognizer:)))
sceneView.addGestureRecognizer(tap)
}
@objc func didTap(withGestureRecognizer recognizer: UIGestureRecognizer) {
let tapLocation = recognizer.location(in: sceneView)
let hitTestResults = sceneView.hitTest(tapLocation)
guard let node = hitTestResults.first?.node else { return }
node.removeFromParentNode()
}
- 添加多个3D物体
在ViewController类的末尾创建一个extension:
extension float4x4 {
var translation: float3 {
let translation = self.columns.3
return float3(translation.x, translation.y, translation.z)
}
}
extension将矩阵转换为float3。修改addBox()方法:
func addBox(x: Float = 0, y: Float = 0, z: Float = -0.2) {
let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
let boxNode = SCNNode()
boxNode.geometry = box
boxNode.position = SCNVector3(x, y, z)
sceneView.scene.rootNode.addChildNode(boxNode)
}
修改didTap(使用gesturerecognizer:)方法,在guard let语句内部和return语句之前。添加以下代码:
let hitTestResultsWithFeaturePoints = sceneView.hitTest(tapLocation, types: .featurePoint)
if let hitTestResultWithFeaturePoints = hitTestResultsWithFeaturePoints.first {
let translation = hitTestResultWithFeaturePoints.worldTransform.translation
addBox(x: translation.x, y: translation.y, z: translation.z)
}
接下来实现使用x、y和z在检测到的点击时添加一个新的box。didTap(withGestureRecognizer:)方法的代码如下:
@objc func didTap(withGestureRecognizer recognizer: UIGestureRecognizer) {
let tapLocation = recognizer.location(in: sceneView)
let hitTestResults = sceneView.hitTest(tapLocation)
guard let node = hitTestResults.first?.node else {
let hitTestResultsWithFeaturePoints = sceneView.hitTest(tapLocation, types: .featurePoint)
if let hitTestResultWithFeaturePoints = hitTestResultsWithFeaturePoints.first {
let translation = hitTestResultWithFeaturePoints.worldTransform.translation
addBox(x: translation.x, y: translation.y, z: translation.z)
}
return
}
node.removeFromParentNode()
}