概述
iOS 11 引入了 ARKit,这是一个全新的框架,允许开发者轻松地为 iPhone 和 iPad 创建无与伦比的增强现实体验。通过将虚拟对象和虚拟信息同用户周围的环境相互融合,ARKit 使得应用跳出屏幕的限制,让它们能够以全新的方式与现实世界进行交互。
视觉惯性测距【全局追踪】
ARKit 使用视觉惯性测距 (Visual Inertial Odometry, VIO) 来精准追踪周围的世界。VIO 将摄像头的传感器数据同 Core Motion 数据进行融合。这两种数据允许设备能够高精度地感测设备在房间内的动作,而且无需额外校准。
场景识别与光亮估量【场景理解】
借助 ARKit,iPhone 和 iPad 可以分析相机界面中所呈现的场景,并在房间当中寻找水平面。ARKit 不仅可以检测诸如桌子和地板之类的水平面,还可以在较小特征点 (feature points) 上追踪和放置对象。ARKit 还利用摄像头传感器来估算场景当中的可见光总亮度,并为虚拟对象添加符合环境照明量的光量。
高性能硬件与渲染优化【渲染】
ARKit 运行在 Apple A9 和 A10 处理器上。这些处理器能够为 ARKit 提供突破性的性能,从而可以实现快速场景识别,并且还可以让您基于现实世界场景,来构建详细并引人注目的虚拟内容。您可以利用
Metal
、Scenekit
、Spritekit
以及诸如Unity
、UNREAL ENGINE
之类的第三方工具,来对 ARKit 进行优化。
ARKit与SceneKit
- ARKit:相机捕捉真实世界
- Scenekit:制作3D模型
-
大家可以看到图中的 ARSession 类,配置 ARSession,然后使用 SceneKit 或者 SpriteKit 来展示 AR 内容。ARSCNView 和 ARSKView 类都是包含在 ARSession 当中的,而 ARSession 对象则用来管理设备动作追踪和进行图像处理的,也就是创建 AR 场景的必需品。但是,要运行 Session,您首先必须选择一种 Session 配置。
- ARWorldTrackingConfiguration:提供高品质的AR体验,可精确跟踪设备的位置和方向,并允许平面检测和命中测试。
- AROrientationTrackingConfiguration:提供仅跟踪设备方向的基本AR体验。
然后,我们在这个配置上调用run()函数。该会话还具有同时运行的AVCaptureSession和CMMotionManager对象,以获取用于跟踪的图像和运动数据。最后,会话将当前帧输出到ARFrame对象。
- ARWorldTrackingConfiguration:提供高品质的AR体验,可精确跟踪设备的位置和方向,并允许平面检测和命中测试。
SCNScene:节点【万物皆Scene】,每一个
Scene
都有个根节点RootNode
。如图所示节点类似于NSObject
,节点就可以看做是3D物件。
跟踪设备方向的基本配置
ARFrame:捕获和位置追踪、视频图像信息。
ARAnchor:一个真实的位置和方向,可用于将对象放置在一个基于“增大化现实”技术的游戏场景。
Tracking:如图x、y、z三个轴组成,z轴负数的情况下说明显示在相机前面的位置,正数的时候显示在相机后面的位置。
AR应用程序
如图:
- ARKit负责采集场景;
- SceneKit、SpriteKit、Metal负责渲染;
- SpriteKit:在很久以前做平面、立体大家都要依靠OpenCV、OpenGL,由于比较难用,后来有人封装成cocos2d。苹果后来在cocos2d基础上又封装一层,也就是现在的SpriteKit。效果不错后iOS8又推出了SceneKit;
- Metal:用来操作GPU;【GPU处理事情的时候是同步处理】
开发环境和适配设备
- Xcode:Xcode9以上版本,最新已更新到到 【Xcode9-beta 6】。
- 设备:需要具有A9或更高版本处理器的iOS设备。
- 系统:当然是iOS11喽,最新已更新到 【iOS 11 beta 9】,然后下载描述文件。安装描述文件,重新启动。检查软件更新就可以了。
项目创建
选择SceneKit3D模型
工程创建后初始代码
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// SCNScene:场景里面包含一个模型,然后把场景放到ARSCNView上,然后就可以展示。
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// Set the scene to the view
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// 通过`run()`启动追踪
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
//通过`pause()`暂停
sceneView.session.pause()
}
真机直接运行
真机运行第一次会提示相机权限,开启后显示出一个3D飞机在相机前。
下面我们创建一张相机截图显示在手机屏幕前
通过添加手势,点击后生成截屏图片显示在手机前。
@objc func handtap(gestureRecognizer:UITapGestureRecognizer){
guard let currentFrame = sceneView.session.currentFrame else {
return
}
/// 创建图片
let imagePlane = SCNPlane(width: sceneView.bounds.width / 6000, height: sceneView.bounds.height / 6000)
/// 渲染图片
imagePlane.firstMaterial?.diffuse.contents = sceneView.snapshot() //截屏
imagePlane.firstMaterial?.lightingModel = .constant
/// 创建节点并添加到sceneView上
let planNode = SCNNode(geometry: imagePlane)
sceneView.scene.rootNode.addChildNode(planNode)
/// 追踪相机位置
var translate = matrix_identity_float4x4
translate.columns.3.z = -0.1
///取相机里面的数据,放到simdTransform
planNode.simdTransform = matrix_multiply(currentFrame.camera.transform, translate)
}
firstMaterial:渲染
sceneView.snapshot():sceneView全部内容展示出来【截屏】
matrix_identity_float4x4:【4x4矩阵,x、y、z、高度的部分】
translate.columns.3.z = -0.1:追踪的位置是在3轴的z轴-0.1的位置
运行结果
下面我们不用程序默认的模型,我们来创建自己的模型,直接代码走起
1. 创建一个盒子模型,并进行渲染。
let scene = SCNScene()
/// 我们创建几何模型:盒子
/// chamferRadius:圆角,等于0时,说明是没有圆角
let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
/// 创建节点,传入几何
let boxNode = SCNNode(geometry: box)
/// 对盒子进行渲染
/// 创建渲染器
/// material 渲染器
let material = SCNMaterial()
/// 渲染成红色
//material.diffuse.contents = UIColor.red
/// 渲染一张照片
material.diffuse.contents = UIImage(named: "brick.png")
/// 渲染盒子
box.materials = [material]
/// 设置节点位置
/// SCNVector3:三维【x,y.z]
boxNode.position = SCNVector3(0,0,-0.2)
/// 把结点放到根节点上
scene.rootNode.addChildNode(boxNode)
sceneView.scene = scene
运行效果
2. 创建球体模型,再点击球体后,进行不同图片的渲染。
@IBOutlet var sceneView: ARSCNView!
let textures = ["earth.jpg","jupiter.jpg","mars.jpg","venus.jpg","IMG_0008.jpg"]
private var index = 1
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
let scene = SCNScene()
/// 我们创建几何模型:球体
let sphere = SCNSphere(radius: 0.1)
/// 渲染
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "earth.jpg")
sphere.materials = [material]
/// 创建节点
let sphereNode = SCNNode(geometry: sphere)
sphereNode.position = SCNVector3(0,0,-0.5)
scene.rootNode.addChildNode(sphereNode)
sceneView.scene = scene
/// 创建手势
registerGestureRecognizers()
}
func registerGestureRecognizers(){
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapped))
self.sceneView.addGestureRecognizer(tapGestureRecognizer)
}
@objc func tapped (recognizer: UIGestureRecognizer){
/// 获取ARSCNView
let sceneView = recognizer.view as! ARSCNView
/// 手势点击的位置
let touchLoaction = recognizer.location(in: sceneView)
/// 获取点击之后的结果
let hitRersults = sceneView.hitTest(touchLoaction, options: [:])
if !hitRersults.isEmpty {
if index == self.textures.count {
index = 0
}
guard let hitRersult = hitRersults.first else {
return
}
/// 更改渲染的图片
let node = hitRersult.node
node.geometry?.firstMaterial?.diffuse.contents = UIImage(named: textures[index])
index += 1
}
}
运行效果