手机网游
手机网游
休闲娱乐
模拟经营
赛车驾驶
冒险解谜
恋爱养成
策略塔防
战争对抗
体育竞速
卡牌对阵
VR游戏
3D手游
武侠修仙
动作闯关
射击枪战
音乐舞蹈
横板格斗
双人游戏
换装游戏
橙光游戏
其他游戏
传奇手游
安卓软件
安卓软件
主题壁纸
购物优惠
资讯阅读
健康美食
地图出行
影音播放
教育学习
游戏辅助
摄影摄像
其他软件
金融理财
言情小说
生活服务
电竞赛事
通讯社交
系统工具
效率办公
安卓游戏
安卓游戏
益智休闲
动作冒险
网络游戏
模拟经营
儿童教育
体育竞速
策略塔防
冒险解谜
角色扮演
音乐游戏
飞行射击
赛车游戏
破解游戏
传奇手游
其他游戏
攻略资讯
攻略资讯
游戏资讯
游戏攻略
游戏问答
软件教程
合集
合集
教程合集
游戏合集
软件合集
文章合集
首页 > 攻略资讯 > 游戏资讯

ARKit iOS 11原生开发入门(下)

编辑:啊妹 2017-09-18

相信今天大家都已经被苹果秋季发布会刷屏了。

当然,除了已经预热了近一年的iPhone X,我们还有望见证帮主的另一份遗产-Apple Park。

临渊羡鱼不如退而结网,废话不多说,继续学习。

在特征点放置物体

可能有的朋友在上一步测试时发现了,有时候半天也检测不到平面(虽然几率不大),还以为程序崩溃了或者是有bug。显然我们不能指望用户在家里跑来跑去只会了找到一块ARKit能够识别到的平面,因此我们需要使用其它的方法来进行hit detection。当我们无法找到平面时,将使用特征点作为替代。

要实现这一点相对比较容易,我们只需要修改touchesBegan(_:with:)方法即可。使用以下代码替代之前的该方法:

override func touchesBegan(_ touches: Set, with event: UIEvent?) {

if let hit = sceneView.hitTest(

viewCenter,

types: [.existingPlaneUsingExtent]).first{

sceneView.session.add(anchor: ARAnchor(transform:hit.worldTransform))

return

}else if let hit = sceneView.hitTest(

viewCenter,

types: [.featurePoint]).last{

sceneView.session.add(anchor: ARAnchor(transform:hit.worldTransform))

return

}

}

这里我们添加了一种新的hit test类型-featurePoint,以便在我们找不到任何existingPlaneUsingExtent测试的结果时使用。特征点hit检测的结果按照从最近到最远的方式来排序,因此这里使用last结果,而非first,因为这样可以提供最佳的用户体验。

再次在手机上编译运行HomeHero。我们会发现hit检测的表现很不错,但远非完美:它会将物体放置在一些我们一无所知的特征点上。关于这一点,可以作为一个小小的挑战练习。

测量距离

现在我们已经实现了将虚拟的物体放置在真实世界中,接下来我们将实现另一个有趣的功能:测量距离。ARKit可以非常精确的放置和追踪物体位置,因此我们甚至用它来测量真实世界中的距离。

在SceneKit中,1个坐标点等同于真实世界中的1米。在HomeHero这个应用中,我们将放置两个AR球体,然后计算它们之间的距离,从而测量真实世界中的距离。为此,我们需要更改renderer(:didAdd:for:)方法,并在switch语句中实现measutre这种情况下的代码:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {

//

DispatchQueue.main.async {

if let planeAnchor = anchor as? ARPlaneAnchor{

#if DEBUG

let planeNode = createPlaneNode(center: planeAnchor.center, extent: planeAnchor.extent)

node.addChildNode(planeNode)

#endif

}else{

switch self.currentMode{

case .none:

break

case .placeObject(let name):

let modelClone = nodeWithModelName(name)

self.objects.append(modelClone)

node.addChildNode(modelClone)

case .measure:

//1

let sphereNode = createSphereNode(radius: 0.02)

//2

self.objects.append(sphereNode)

//3

node.addChildNode(sphereNode)

//4

self.measuringNodes.append(node)

}

}

}

}

以上所新增的代码将在我们选择了UI界面中的测量工具时创建测量用的节点,数字注释行的代码解释如下:

1.使用起始项目中所提供的createSphereNode(radius:)方法创建sphere节点。

2.在对象数组中添加这个新的对象。

3.将球体节点添加到传递给代理对象的节点中。

4.将球体节点添加到起始项目所提供的measureingNodes数组中,以便追踪测量节点。

接下来我们需要实现计算两个测量节点间距离的逻辑代码,在HomeHeroViewController.swift中添加如下的新方法:

func measure(fromNode:SCNNode, toNode:SCNNode){

//1

let measuringLineNode = createLineNode(fromNode: fromNode, toNode: toNode)

//2

measuringLineNode.name = "MeasuringLine"

//3

sceneView.scene.rootNode.addChildNode(measuringLineNode)

objects.append(measuringLineNode)

//4

let dist = fromNode.position.distanceTo(toNode.position)

let measurementValue = String(format:"%.2f",dist)

//5

distanceLabel.text = "Distance: (measurementValue) m"

}

以上方法创建了两个节点之间的一条直线,其代码解释如下:

1.createLineNode(fromNode:toNode:)是起始项目中所提供的一个辅助方法。它的作用是创建两个节点之间的一条直线。

2.命名直线节点的名称,以便在后续删除。

3.将直线节点添加到场景中。

4.测量两个节点之间的距离。虚拟物体之间的距离和真实世界中的位置一一对应。

5.更新UI,向用户显示所测量的距离。

此外,我们还需要添加一些逻辑代码,从而根据球体的数量来更新测量状态。在HomeHeroViewController.swift中添加以下方法:

func updateMeasuringNodes(){

guard measuringNodes.count > 1 else{

return

}

let firstNode = measuringNodes[0]

let secondNode = measuringNodes[1]

//1

let showMeasuring = self.measuringNodes.count == 2

distanceLabel.isHidden = !showMeasuring

if showMeasuring{

measure(fromNode: firstNode, toNode: secondNode)

}else if measuringNodes.count > 2{

//2

firstNode.removeFromParentNode()

secondNode.removeFromParentNode()

measuringNodes.removeFirst(2)

//3

for node in sceneView.scene.rootNode.childNodes {

if node.name == "MeasuringLine"{

node.removeFromParentNode()

}

}

}

}

以上代码的解释如下:

1.仅当有两个球体时显示测量结果。

2.如果节点超过2个,则删除旧的测量节点

3.删除旧的测量直线。

接下来我们只需要在合适的时机来调用updateMeasuringNodes()方法即可。如果在方法renderer(_:didAdd:for:)中调用有点太早,因为此时在代理方法中传递的节点还没有一个可用的位置信息。因为renderer(_:didUpdate:for:)方法在renderer(_didAdd:for:)方法之后调用,在for 语句参数中所传递的节点包含了正确的场景信息,也就意味着我们在此时开始测量。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {

DispatchQueue.main.async {

if let planeAnchor = anchor as? ARPlaneAnchor{

updatePlaneNode(node.childNodes[0], center: planeAnchor.center, extent: planeAnchor.extent)

}else{

self.updateMeasuringNodes()

}

}

}

当我们调用updateMearingNodes方法时,当新的ARAnchor被添加、映射到SCNNode,以及更新时,测量的逻辑就会随之更新。

在手机上编译运行项目,可以来体会一下ARKit魔法一般的测量精度。为了实现更为精确的hit测试,我们可能需要找到一个真实世界的平面来尝试。

ARSession state

此前我们提到过,ARSession就好比ARKit的大脑,它会根据真实世界中的不同条件而产生不同的“心情”。当光照条件重发,或是屏幕上有足够多的细节时,它的表现可谓完美而精确。但是在其它一些情况下,也可能会有很糟糕的表现。因此,我们需要使用ARFrame中所提供的状态信息让用户知道ARKit是不是在发脾气~

在HomeHeroViewController.swift中添加以下方法:

func updateTrackingInfo(){

//1

guard let frame = sceneView.session.currentFrame else{

return

}

//2

switch frame.camera.trackingState{

case .limited(let reason):

switch reason{

case .excessiveMotion:

trackingInfo.text = "Limited Tracking: Excessive Motion"

case .insufficientFeatures:

trackingInfo.text = "Limited Tracking: Insufficient Details"

default:

trackingInfo.text = "Limited Tracking"

}

default:

trackingInfo.text = ""

}

//3

guard

let lightEstimate = frame.lightEstimate?.ambientIntensity

else {

return

}

//4

if (lightEstimate < 100){

trackingInfo.text = "Limited Tracking: Too Dark"

}

}

以上代码的作用是获取当前的ARFrame信息,并当环境条件恶劣时向用户发出提示。以下是具体的代码解释:

1.我们可以使用场景视图中ARSession对象的currentFrame属性来获取当前的ARFrame。

2.我们可以从当前ARFrame的ARCamera对象中获取trackingState属性。trackingState 的枚举值limited提供了关联的TrackingStateReason值,可以告诉我们具体出现的追踪问题。

3.我们已经启用了ARWorldTrackingConfiguration的光线评估功能,因此可以从ARFrame的lightEstimate属性中获取光照信息。

4.ambientIntensity以流明为单位,如果小于100流明,表现环境过于昏暗,此时需要向用户发出提示。

我们需要在每一个渲染的frame中都更新追踪信息,因此需要在renderer(_:updateAtTime:)代理方法中实现这一点。在HomeHeroViewController.swift的ARSCNViewDelegate 扩展中添加该方法:

//updateattime

func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {

DispatchQueue.main.async {

//1

self.updateTrackingInfo()

//2

if let _ = self.sceneView.hitTest(self.viewCenter, types: [.existingPlaneUsingExtent]).first{

self.crosshair.backgroundColor = UIColor.green

}else{

self.crosshair.backgroundColor = UIColor(white:0.34,alpha:1)

}

}

}

以上方法主要做了以下事情:

1.为每个渲染的frame更新追踪信息。

2.如果中间的点在进行hit 测试时符合existingPlaneUsingExtent类型,就会变成绿色,从而向用户提示获取了高质量的hit test。

在手机上编译运行项目,然后尝试在一些相对比恶劣的光照条件下进行测试。

Session interruptions(进程中断)

有些情况下ARSession会被中断,比如当我们让应用进入后台运行时。这种操作将会切断视频流,从而让ARSession完全失明。当进程被中断后,下次进入应用并继续进程时,设备的位置和旋转朝向等很可能完全发生了变化。此时,我们需要重新启动进程。

RSession通过ARSessionObserver协议向其代理发送所有的进程中断和常见错误信息。ARSCNViewDelegate中已经实现了ARSessionObserver,因此我们只需在HomeHeroViewController.swift中添加ARSCNViewDelegate对于以上方法的实现代码即可。

func session(_ session: ARSession, didFailWithError error: Error) {

//1

showMessage(error.localizedDescription, label: messageLabel, seconds: 2)

}

//2

func sessionWasInterrupted(_ session: ARSession) {

showMessage("Session interrupted", label: messageLabel, seconds: 2)

}

func sessionInterruptionEnded(_ session: ARSession) {

showMessage("Session resumed", label: messageLabel, seconds: 2)

removeAllObjects()

runSession()

}

以上代码会处理大多数遇到的ARSession问题,这里详细解释一下:

1.showMessage(_:label:seconds:)是起始项目中所提供的辅助方法,它将在指定的时间段内以label的形式显示一条信息。

2.sessionWasInterrupted():)将在进程被中断时调用,比如当应用进入后台运行时。

3.当sessionInterruptionEnded(_:)被调用时,我们应删除所有的对象,并通过之前所实现的runSession()方法来重启AR进程。removeAllObjects是起始项目中所提供的辅助方法。

在手机上编译运行项目,并让应用进入后台运行,然后恢复运行应用,看看会发生些神马。

至此,我们这个使用ARKit开发的简单示例教程就到此结束了。

接下来怎么学?

在这个教程之中,我们只是简单介绍了ARKit的核心组成部分。在此之外,我们要借助SceneKit和数学来实现更多的功能。

挑战与练习

在WWDC 2017视频相关的链接中,我们可以找到官方的Demo应用,从而查看如何更精确的使用特征点来放置物体。这里的挑战就是通过阅读ARKit WWDC Demo应用的diamante来优化此前的hit 测试。此外,我们还需要一些三角和代数知识来更好的解决这一问题。

在示例代码的challenge文件夹中有最终的解决方案,可以供大家参考。

结束语

好了,使用ARKit iOS 11 beta SceneKit进行原生开发的教程到此结束。

后续所翻译或原创的教程会趋向于更实际的功能或应用。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

良心推荐

专业制作PPT的办公软件,说到PPT估计很多人都非常熟悉吧,在我们上学的时候计算机基础课程就有关于PPT制作的学习,而且在我们工作之后,一些从事文员工作的也需要制作PPT,那么有没有哪些专业制作PPT的办公软件呢?

相关资讯