说明
你是否曾经站在博物馆展台前面,不满足于艺术品或手工艺品前面的小卡片所展示的内容,想要知道更多?要是有一个这样的 app 该有多好。现在,你可以用 ARKit 2 来自己制作一个带有图片/物体检测与追踪的 app。
为了让体验更加有趣,ARKit 允许 app 添加动态的虚拟内容到真实世界物体上。它允许我们为真实世界的地方和物品创建一个交互式的引导 app。是博物馆,画廊,游乐场和学校等地的完美配套 app。它可以让任何地方“活起来”,提供一个动态的或定制的体验。
在本教程中,你将构建一个TriassicLoupe(三叠纪放大镜),一个用在自然历史博物馆里恐龙展览的配套 app。这个概念来自珠宝商的放大镜;这个 app 会在用户指向展品的时候显示出隐藏的细节。如果你周围没有任何恐龙,请不要担心 - 你可以使用普通的家用物品作为占位。
最终应用程序在信息图像上显示一段简短的动画。它还会在复制品旁边显示有关恐龙的信息。该 app 还将为该物体添加一些可怕的声音效果。
该应用程序将 ARKit 与 SceneKit(iOS的3D图形框架)结合使用。你会看到 ARKit 使用 SceneKit 完成所有繁重的工作。对于图片追踪和物体追踪,您将只使用非常基本的 SceneKit 功能,本教程中您从中学到的所有内容都是通用的。了解有关 SceneKit 的更多信息将使您能够构建更丰富的应用程序,但这超出了本教程的范围。
开始
ARKit 依赖于A9或更高版本处理器的内置功能。它使用了机器学习,后置摄像头,以及视频和图像处理。这意味着 ARKit 应用程序需要在iPhone 6s或更新版本上运行,并且它们无法在模拟器中运行。
使用超长的Lightning线缆或者设置设备以通过 Wi-Fi 连接到Xcode方便调试。 ARKit 需要移动一点才能获得一张好的世界地图。世界地图是 ARKit 对物理空间的意识。它是一系列特征点,尺寸,方向和锚点。
稍后你会需要扫描一些图像。显示器上显示的图像应该也适用,但如果您在扫描图像时遇到问题,则可能需要打印图像以获得更好的效果。
要开始使用,请使用本教程顶部或底部的“下载材料”按钮下载入门项目。 .zip文件包含一个帮助项目,包含将在教程中使用的素材,以及初始项目和最终项目。
打开入门项目。应用程序本身非常简单,只需一个ViewController即可添加所有逻辑。有一个辅助结构体,DinosaurFacts,其中包含一些关于一些恐龙的基本信息。
如果你构建并运行,你会看到一个黑屏,因为你尚未连接ARKit会话。
创建图片检测
首先要做的是创建图片检测。图片检测可能是ARKit最简单的功能。要创建图片检测器,您所要做的就是提供带有图像副本的图像跟踪会话。这些提供的图像称为参考图片。
添加参考图片
TriassicLoupe 使用含有信息标志的艺术作品作为参考图像。当用户将应用程序指向其中一个标志时,该应用程序将添加恐龙图像叠加层。
与其他应用程序图像一样,增强现实(AR)参考图像存在于素材目录asset catalog中。参考图像有点特殊,因为它们需要专门为ARKit分组。
打开Assets.xcassets 点击下面的 + 按钮。
从弹出菜单中,选择New AR Resource Group以创建新组。将其重命名为AR Images,因为该组将保留参考图像。
在Finder中,从下载的材料中打开Dinosaur Images文件夹。将每个图像文件逐个拖动到Xcode中的AR Images中。完成后,您应该有三个带黄色警告三角形的图像。
如果参考图像作为参考质量不好,Xcode会发出警告。这可能是因为它们太小或者没有足够的特征或对比度。具有大量空白空间,颜色太少或缺乏独特形状的图像是难以检测的。
在这种情况下,警告是“不支持的配置”警告。这是因为参考图像必须具有非零的正宽度。 AR参考图像需要您指定其实际尺寸!
在资产库中选择stegosaurus(剑龙)图像。然后选择Attributes inspector。
将单位更改为英寸。接下来,输入宽度4.15。当你这样做时,根据纵横比,高度将自动变为2.5711!这些数字就是您桥接虚拟世界和真实世界的地方。
这两张图片,配置值如下:
- trex:Inches, width: 6.3333, height: 4.75
- triceratops: Inches, width: 5, height: 2.8125
当你输入尺寸后,警告就消失了。
这些图像对应于所包含的Dinosaurs.key Keynote文件的幻灯片。每张幻灯片代表一个信息标语牌,旁边是博物馆展示。在美国信纸大小的纸张上打印时,这个尺寸就是物理图像尺寸。
注意:某些图像实际上是在Keynote幻灯片中裁剪出来的。只要保持大小和宽高比相同,ARKit 完全能够识别并匹配。
这些图像都是不同的风格,以展示ARKit系列的一小部分。这里有两件事情:1。图像中有足够的形状和对比度。 2.真实世界中的图片平坦,光线充足,不反光。
书页上,壁纸上或在镜相打印出的图片是难以识别的。照片,绘画或插图可以很好地识别出来。如果参考图片不够友好,Xcode会发出警告。这样就无需在运行时猜测!
现在,是时候继续编写代码来寻找这些图像了。
添加图片追踪
在ViewController.swift中,在注释下添加一个新变量:// Add configuration variables here:
private var imageConfiguration: ARImageTrackingConfiguration?
这将设置一个变量,以便在创建后保留图像跟踪配置。
现在,查找setupImageDetection()并添加以下代码:
imageConfiguration = ARImageTrackingConfiguration()
这会将该实例变量设置为新的ARImageTrackingConfiguration。顾名思义,此类是一个ARKit配置,用于检测和跟踪图像。
在该行下面,添加以下内容:
guard let referenceImages = ARReferenceImage.referenceImages(
inGroupNamed: "AR Images", bundle: nil) else {
fatalError("Missing expected asset catalog resources.")
}
imageConfiguration?.trackingImages = referenceImages
这将使用您刚刚在素材目录中创建的AR Images组中的图像,创建一个ARReferenceImage集。然后,将它们作为要跟踪的图像列表添加到配置中。
注意:图像检测最适用于资源组中不超过25个图片。如果您的博物馆有超过25个展品,您可以创建多个资源组,并在用户在建筑物周围移动时切换它们。
要使用配置,请将以下内容添加到viewWillAppear(_ :):
if let configuration = imageConfiguration {
sceneView.session.run(configuration)
}
这将使用imageConfiguration启动ARKit会话。一旦运行,ARKit将处理相机数据以检测参考图片。
要确保全部启动,请将以下内容添加到viewDidLoad()的底部:
setupImageDetection()
最后,为了平衡会话运行,在viewWillDisappear(_ :)添加:
sceneView.session.pause()
当视图消失时,这会暂停会话。ARKit会话会因为相机调用,视频处理和渲染,而把电池耗尽。在我们的单视图应用程序中,这不是什么大问题,但是尊重用户的设备并在没有显示时暂停会话总是一个好主意。
处理检测到的图片
一旦检测到图像,AR会话就会将ARImageAnchor添加到其世界地图中。当发生这种情况时,您将在渲染器处获得回调renderer(_:didAdd:for:)。
在ViewController.swift的底部找到此函数并添加以下代码:
DispatchQueue.main.async { self.instructionLabel.isHidden = true }
if let imageAnchor = anchor as? ARImageAnchor {
handleFoundImage(imageAnchor, node)
}
此代码检查是否为图像锚点添加了新添加的节点。这意味着在现实世界中检测到图像。
将handleFoundImage(_:_:)函数替换为:
let name = imageAnchor.referenceImage.name!
print("you found a \(name) image")
let size = imageAnchor.referenceImage.physicalSize
if let videoNode = makeDinosaurVideo(size: size) {
node.addChildNode(videoNode)
node.opacity = 1
}
这将从锚点的参考图片中获取图像的名称和大小。您在素材目录中指定了这些值。使用该尺寸,调用辅助函数以在检测到的图像之上创建视频播放器。
要创建视频节点,请将makeDinosaurVideo(size :)的内容替换为:
// 1
guard let videoURL = Bundle.main.url(forResource: "dinosaur",
withExtension: "mp4") else {
return nil
}
// 2
let avPlayerItem = AVPlayerItem(url: videoURL)
let avPlayer = AVPlayer(playerItem: avPlayerItem)
avPlayer.play()
// 3
NotificationCenter.default.addObserver(
forName: .AVPlayerItemDidPlayToEndTime,
object: nil,
queue: nil) { notification in
avPlayer.seek(to: .zero)
avPlayer.play()
}
// 4
let avMaterial = SCNMaterial()
avMaterial.diffuse.contents = avPlayer
// 5
let videoPlane = SCNPlane(width: size.width, height: size.height)
videoPlane.materials = [avMaterial]
// 6
let videoNode = SCNNode(geometry: videoPlane)
videoNode.eulerAngles.x = -.pi / 2
return videoNode
此功能创建一个视频播放器并将其放入一个大小适合图像的SceneKit节点。它通过以下方式实现:
- 从资源包中抓取视频。这有一个适用于所有恐龙的简单动画。但您可以根据图像锚点的名称为每种恐龙类型提供不同的视频。
- 为该视频创建和启动
AVPlayer。 - AVPlayer 实例并不会自动循环播放。此通知块通过侦听播放器完成来循环视频。然后, 它会返回开头并重新开始播放。
- SceneKit 不使用
UIViews, 而是使用节点来渲染场景。不能直接添加AVPlayer。相反, 视频播放器可以用作节点的纹理或 "材质"。这将把视频帧映射到关联的节点。 - 检测到的图像是平坦正方形 (即平面), 因此覆盖的节点是与检测到的图像大小相同的
SCNPlane。这架飞机被装饰成视频作为其纹理。 - 创建出的实际节点将成为场景的一部分。这就需要在 x 轴上进行翻转, 以便它正确显示给用户。
尝试一下
最后的最后, 是时候构建并运行了!但是, 首先, 打印幻灯片Dinosaurs.key中至少一张恐龙。将其放置在光线充足的区域中 (垂直或水平).
构建并运行该应用程序。接受相机权限并显示视频后, 请将其指向打印页面。它可能需要两只稳定的手来检测图像。完成后, 您将在控制台中看到一个注释, 并在屏幕上显示一个动画叠加。
不幸的是, 如果会话启动, 但它没有检测到图像, 那也没有错误消息。大多数情况下, ARKit 不保证能找到该图像, 因此它不被视为错误。只要素材目录中没有关于图像的警告, 就应该最终检测到该图像。
添加物体检测和追踪
现在, 您已经看到了图像检测如何工作。接下来, 您将向应用添加物体检测。从开发人员的角度来看, 物体检测的工作原理几乎相同。主要区别在于它将寻找三维物体而不是平面图像。设置物体检测稍微复杂一些。参考物体的创建也比较复杂。
回顾一下, 以下是使用图像和物体检测的步骤:
- 创建参考对象。
- 将参考对象放入素材集的AR Resources组中。
- 创建一个 ARKit 会话。
- 加载参考图片/物体,并设置会话来检测他们。
- 启动 session.
- 等待锚点被添加时的回调。
- 添加交互节点到场景中,或采取其他措施。
物体检测
另一个有用的 ARKit 函数是物体检测和跟踪。TriassicLoupe 检测已知物体并标记它们。在现实中, 这些将是恐龙在一个透视或恐龙骨架。对于本教程, 您将使用手头上的任何东西。
选择参考物体
检测物体所需的第一件事是参考物体。通过图像检测, 您可以创建、扫描或拍摄图像。但是, 对于3D 对象, 参考物体很难构造.
ARKit 提供了自己的 API, 用于使用 iPhone 扫描它们来创建参考物体。TriassicLoupe 不直接使用此项, 因为它只能检测到一组已知的对象。您可以使用 Apple 提供的实用程序提前扫描它们。
如果可以, 您应该下载 Apple 的对象扫描程序项目Object Scanner project。它也包括在项目下载ScanningAndDetecting3DObjects文件夹中, 为您的方便。请注意, 在您阅读此内容时, 包含的项目可能已过期。
此应用程序扫描对象, 并允许您导出. arobject文件。然后, 您可以将此文件作为素材导入到 Xcode 项目中。成功的扫描需要有合适的对象和良好的照明。适当的对象是:
- 实体不可变形。
- 拥有很多细节纹理(比如形状和颜色)。
- 无反射或透明效果。
- 尺寸应在是垒球和椅子中间。
您可能没有在您的家中的3D 恐龙显示, 因此您可以使用任何家庭物体来完成教程。比较合适的物体是饼干罐、动作图或植物。将物体放在平坦且光照良好的表面上 。
创建参考物体
构建并运行此应用程序到真机设备上。为获得最佳效果, 请使用 iPhone 8、8 + 或 X, 它具有足够的处理能力, 在执行扫描时保持良好的帧速率。
- 将手机摄像头对准物体。对象周围应出现一个黄色框。一旦对象位于框的中间, 请点按Next。
- 通过移动边界框并长按拖动边线来调整边框大小。让该框应仅包含被扫描物体。与新的测量应用一样, 此扫描仪使用 ARKit 来测量对象的真实世界大小。一旦对象处于中间, 点击Scan按钮。
- 将手机对准物体,绕着物体扫描。一定要有多个角度, 以及上面和两侧。黄色填充框, 意味着已经扫描获取足够的信息。应尽量得到完整的覆盖。
- 下一步将设置锚点。这个点用来控制模型的节点几何体是如何与真实世界互动的。对于本教程, 确切的位置并不重要。试着把它放在物体的中央底部平面上。准备好后按 Finish。
- 点击Export按钮,用 AirDrop,文件共享或电子邮件将
.arobject文件发送到你的设备上。
重复上述步骤,得到另外两个物体的模型。
导入参考物体
回到Assets.xcassets,创建一个新的AR Resource Group。命名为AR Objects.
将每个. arobject文件拖到此组中。您将在扫描时看到物体的小照片预览。重命名对象以匹配这些恐龙名称: brachiosaurus腕龙、iguanodon禽龙和velociraptor迅猛龙。
与图像检测不同, 您不必指定大小, 因为它已由物体扫描过程测量。
寻找物体
下一步是设置配置项以查找这些对象。在ViewController.swift的顶部, 在imageConfiguration定义下, 添加:
private var worldConfiguration: ARWorldTrackingConfiguration?
这将创建一个变量来存储世界追踪配置。这个配置是物体检测必需的。与图像检测不同的是, 没有针对物体的配置。
下一步,将setupObjectDetection()方法替换为:
worldConfiguration = ARWorldTrackingConfiguration()
guard let referenceObjects = ARReferenceObject.referenceObjects(
inGroupNamed: "AR Objects", bundle: nil) else {
fatalError("Missing expected asset catalog resources.")
}
worldConfiguration?.detectionObjects = referenceObjects
这将创建ARWorldTrackingConfiguration的实例。此配置是功能最齐全的 ARKit 配置。它可以检测水平和垂直平面以及 3D 物体。它使用后置摄像头以及所有运动传感器来计算真实世界的虚拟呈现.
创建配置后, 从素材目录加载参考物体, 并将参考物体设置为detectionObjects。一旦检测到, 当 ARKit 将其锚点添加到场景中时, 您将获得相应的回调。
在viewDidLoad中,将最后一行改为:
setupObjectDetection()
这样,就会将图片检测改为了物体检测。
用下面代码替换viewWillAppear(_:)中的内容,以使用新配置项启动会话:
super.viewWillAppear(animated)
if let configuration = worldConfiguration {
sceneView.debugOptions = .showFeaturePoints
sceneView.session.run(configuration)
}
这同时还激活了ARSCNDebugOptions.showFeaturePoints调试选项。这会在屏幕上为 ARKit 检测到的特征点处放置黄色点。这有助于在您再次运行应用时进行调试。在物体上显示的点越多, 检测就越容易。
注意: 场景视图一次只能运行一个配置, 但您可以随时替换该配置。如果要更新选项或切换配置类型, 只需运行新配置即可。除非明确清除, 否则会话将保留与检测到的要素和锚点相同的世界地图。如果要切换一组检测物体, 请执行此操作。例如, 如果用户从恐龙展览移动到天文学展览, 他们可以看到一组不同的物体。
找到物体
与图片检测中类似,当 ARKit 检测到一个 3D 物体时,会向世界地图中添加一个锚点,并向场景树中添加一个节点。
修改renderer(_:didAdd:for:):
DispatchQueue.main.async { self.instructionLabel.isHidden = true }
if let imageAnchor = anchor as? ARImageAnchor {
handleFoundImage(imageAnchor, node)
} else if let objectAnchor = anchor as? ARObjectAnchor {
handleFoundObject(objectAnchor, node)
}
这将保留以前对图像锚点的处理, 只添加检查以查看新锚点是否为物体锚点。如果它是一个物体锚点, 这意味着检测到一个物体!然后, 将节点和锚点交给帮助方法。
说到这里,还要将handleFoundObject(_:_:)中的内容替换为:
// 1
let name = objectAnchor.referenceObject.name!
print("You found a \(name) object")
// 2
if let facts = DinosaurFact.facts(for: name) {
// 3
let titleNode = createTitleNode(info: facts)
node.addChildNode(titleNode)
// 4
let bullets = facts.facts.map { "• " + $0 }.joined(separator: "\n")
// 5
let factsNode = createInfoNode(facts: bullets)
node.addChildNode(factsNode)
}
这段代码收集有关找到的物体的信息。用户就可以读取恐龙节点旁边,添加的文本节点上的额外信息。查看代码:
referenceObject的名称在素材目录中,并匹配恐龙的名称。DinosaurFact是个帮助类,描述每个已知的恐龙。它有一个facts的列表。- 此帮助器函数创建具有恐龙名称的文本节点, 并将其添加到场景中。此文本将看起来像浮动在在物体上方。
- 这个小圆点符号,拼接在每个 fact 前面, 将它们组合成一个单独的、行分隔的字符串。SCNText 节点可以有多行, 但只接受一个字符串。
- 这个帮助类创建文本节点, 它将显示在对象的旁边,并被添加到场景中。
展示文本节点
现在该深入了解 SceneKit 的文本节点了。
在handleFoundObject(_:_:)下面,添加帮助函数来创建文本节点:
private func createTitleNode(info: DinosaurFact) -> SCNNode {
let title = SCNText(string: info.name, extrusionDepth: 0.6)
let titleNode = SCNNode(geometry: title)
titleNode.scale = SCNVector3(0.005, 0.005, 0.01)
titleNode.position = SCNVector3(info.titlePosition.x, info.titlePosition.y, 0)
return titleNode
}
这将创建具有恐龙名称的文本节点。SCNText是描述字符串形状的几何图形。它允许你创建可放置在场景中的形状。与物体相比, 默认文本尺寸很大, 因此缩放titleNode会将其缩小到合理大小.
这个position用来将文本与物体的中心对齐。由于物体的大小可能不同, 因此需要为每个单独的恐龙指定尺寸。您可以为自己的物体调整DinosaurFacts.swift中的值。默认情况下, SceneKit 的尺寸以米为单位。
接下来, 添加其他帮助器函数:
private func createInfoNode(facts: String) -> SCNNode {
// 1
let textGeometry = SCNText(string: facts, extrusionDepth: 0.7)
let textNode = SCNNode(geometry: textGeometry)
textNode.scale = SCNVector3(0.003, 0.003, 0.01)
textNode.position = SCNVector3(0.02, 0.01, 0)
// 2
let material = SCNMaterial()
material.diffuse.contents = UIColor.blue
textGeometry.materials = [material]
// 3
let billboardConstraints = SCNBillboardConstraint()
textNode.constraints = [billboardConstraints]
return textNode
}
和先前的帮助类相似,但有几点不同:
- 首先, 与上一个帮助函数一样, 这将创建一个带有节点的文本几何图形, 并将其缩放到合理的大小。
- 使文本显示蓝色。蓝色是通过创建一个新的材料, 其
diffuse漫反射内容设置为蓝色设置的。节点的材质是用来让场景渲染器找出物体对光照的反应。漫反射属性类似于基本外观。在这里, 它被设置为蓝色, 但也可以改为图像或视频, 正如你以前看到的。 SCNBillboardConstraint是一个有用的约束, 它一直面向节点, 以便文本始终面向用户。这提高了可读性;在移动时, 您不必移动到一个尴尬的角度来查看文本。
构建并运行应用, 然后指向其中一个扫描物体。
物体检测可能需要多次尝试。在物体周围移动: 前后, 两侧等, 给 ARKit 了解形状的最佳机会有助于识别物体。
ARKit 检测到物体后, 应用程序将在物体旁边显示信息。请注意, 标题浮动在物体上方。如果您的物体高一些,高了几英寸, 则必须在DinosaurFacts.swift中调整titlePosition。文本朝向将沿扫描期间设置的原点方向.
与标题不同, 信息项目符号会与相机一起移动, 以便它们始终面向您。
同时进行图片追踪和物体追踪
此时, 您已使用物体追踪替换了图像追踪。ARWorldTrackingConfiguration是一种超集配置;它支持与ARImageTrackingConfiguration相同的图像跟踪.
要回到图像追踪, 将以下行添加到setupObjectDetection的底部。
guard let referenceImages = ARReferenceImage.referenceImages(
inGroupNamed: "AR Images", bundle: nil) else {
fatalError("Missing expected asset catalog resources.")
}
worldConfiguration?.detectionImages = referenceImages
这将加载与以前相同的一组参考图片, 但将它们设置为世界跟踪配置的detectionImages.
现在再构建运行一次, 您将看到恐龙海报上的动画和物体上的信息文本。
添加声音
让应用程序变得很酷的一种方法,就是给他一些带位置的音频。是时候添加一些可怕的恐龙的声音了!
在文件顶部, 在配置下面添加一个新变量:
lazy var audioSource: SCNAudioSource = {
let source = SCNAudioSource(fileNamed: "dinosaur.wav")!
source.loops = true
source.load()
return source
}()
这里创建了一个SCNAudioSource,它会持有 SceneKit 中用到的音频数据。这些音频数据是从对应的音频文件夹中加载的。
下一步,在handleFoundObject(_:_:)的最末尾,添加下面一行代码:
node.addAudioPlayer(SCNAudioPlayer(source: audioSource))
Voilà! 就是这样。默认情况下,一个SCNAudioPlayer处理 3D 音效的所有细节。
再次构建并运行。将相机对准物体。当物体被识别后,就开始播放惊悚的音效。要获得最佳的效果,建议戴上耳机。这样,当你绕着物体走动的时候,就能听到声音位置变化带来的声音变化。
总结
是时候对博物馆说晚安了。但这并不意味着 ARKit 的乐趣必须停止。
这个应用程序本身是很难完全补充完成的。您可以在场景中显示更多信息和更多媒体类型。而且, 你可以添加更多的展品, 不仅仅是恐龙。你在前面看到过一些提示: 在区域之间移动时切换配置, 并与 Core Location 结合, 以自动确定用户位于何处。
ARKit 还允许共享世界地图。这意味着博物馆观众可以分享个人物品的注释和评论。
这里有很多免费和高级 ARKit 内容, 参考一下了解你还能做些什么。
如果您想了解有关 SceneKit 的更多信息, 请查阅Swift SceneKit 教程或查看我们的书籍。
译者注:《3d apple games by tutorials》的读书笔记免费参考这里