ARKit入门-在AR中处理3D交互和UI控制

翻译自 Handling 3D Interaction and UI Controls in Augmented Reality

遵循AR体验中的视觉反馈、手势交互和现实渲染的最佳实践。

概述

AR为用户提供了在app中与现实和虚拟3D内容交互的新方法。但是,人机界面设计的许多基本原理仍然有效。令人信服的AR幻想同样需要仔细关注3D资源和渲染。iOS人机界面指南包含有关AR人机界面原则的建议。该项目展示了如何应用这些指导方法轻松地创建身临其境、直观的AR体验。

这个示例app提供了一个简单的AR体验,允许用户将一个或多个真实的虚拟对象放置在现实世界的环境中,然后试用直观的手势来排列这些对象。该app提供了用户界面提示,以帮助用户了解AR体验的状态以及交互选项。

以下各节对应于 iOS人机交互指南>AR 中的各个部分,并提供有关该示例app如何实现这些指南的详细信息。有关每个部分的更详细的推理,请参阅iOS人机界面的指南的相应部分。

放置虚拟对象

帮助人们了解何时定位平面和放置虚拟对象。FocusSquare 类在AR View中绘制方形轮廓,为用户提供有关 ARKit 世界跟踪状态的提示。

Demo

正方形改变大小和方向以反映估计的场景深度,并以动效形式在打开和关闭状态间切换,以指示ARKit是否检测到适合放置虚拟对象的平面。在用户放置虚拟对象后,焦点正方形小时,保持隐藏状态,直到用户将相机指向另一个表面。

当用户放置一个虚拟对象时要做出适当的响应。当用户选择要放置的虚拟对象时,示例app的 setPosition(_:relativeTo:smoothMovement) 方法使用 FocusSquare 对象的简单启发式方法将虚拟对象放置于屏幕中间的大致现实的位置,即使ARKit没有在那个地方发现一个平面。

1
2
3
4
5
6
7
8
9
10
11
12
guard let cameraTransform = session.currentFrame?.camera.transform,
let focusSquarePosition = focusSquare.lastPosition else {
statusViewController.showMessage("CANNOT PLACE OBJECT\nTry moving left or right.")
return
}

virtualObjectInteraction.selectedObject = virtualObject
virtualObject.setPosition(focusSquarePosition, relativeTo: cameraTransform, smoothMovement: false)

updateQueue.async {
self.sceneView.scene.rootNode.addChildNode(virtualObject)
}

这个位置可能不是用户想要放置虚拟对象的真实世界表面的精确估计,但它足够接近,以快速地将物体添加到屏幕上。

随着时间的推移,ARKit 会检测平面并提高其位置的估计值,调用 renderer(_:didAdd:for:)renderer(_:didUpdate:for:) 委托方法来报告结果。在这些方法中,示例app调用其 adjustOntoPlaneAnchor(_:using:) 方法来确定之前放置的虚拟对象是否靠近检测到的平面。如果靠近,该方法使用微妙的动画将虚拟对象移动到检测到的平面上,受益于ARKit在该位置对现实世界表面的精确估计,是虚拟对象看起来在用户选择的位置:

1
2
3
4
5
6
7
8
9
10
11
// Move onto the plane if it is near it (within 5 centimeters).
let verticalAllowance: Float = 0.05
let epsilon: Float = 0.001 // Do not update if the difference is less than 1 mm.
let distanceToPlane = abs(planePosition.y)
if distanceToPlane > epsilon && distanceToPlane < verticalAllowance {
SCNTransaction.begin()
SCNTransaction.animationDuration = CFTimeInterval(distanceToPlane * 500) // Move 2 mm per second.
SCNTransaction.animationTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
position.y = anchor.transform.columns.3.y
SCNTransaction.commit()
}

用户与虚拟对象的交互

允许用户使用标准的、熟悉的手势与虚拟对象直接进行交互。该示例app使用单指轻拍,单/双指平移,双指旋转手势识别,以使用户放置和旋转虚拟对象。示例app的代码中 VirtualObjectInteraction 类管理这些手势。

一般来说,要保持交互简单。当拖动一个虚拟对象时(参见 translate(_:basedOn:infinitePlane:) 方法),示例app将虚拟对象的移动限制在它被放置的二维平面上。类似的,由于虚拟对象停留在水平面上,旋转手势(参见 didRotate(_:) 方法)只能绕着垂直轴来旋转对象,以使得对象保持在平面上。

在合理接近交互式虚拟对象时对用户手势做出响应。示例代码的 objectInteracting(with:in:) 方法使用手势识别器提供的触摸位置执行hit测试。通过针对虚拟对象边界框进行hit测试,该方法使得即使用户触摸位置不在虚拟对象的可见内容的点上,用户触摸也有可能影响虚拟对象。通过对多点触摸手势执行多个hit测试,该方法使得用户触摸更有可能影响预期的对象:

1
2
3
4
5
6
7
8
9
10
11
for index in 0..<gesture.numberOfTouches {
let touchLocation = gesture.location(ofTouch: index, in: view)

// Look for an object directly under the `touchLocation`.
if let object = sceneView.virtualObject(at: touchLocation) {
return object
}
}

// As a last resort look for an object under the center of the touches.
return sceneView.virtualObject(at: gesture.center(in: view))

考虑用户启动的对象缩放是否必要。 这个AR app放置了可能自然出现在用户真实环境中的逼真虚拟对象,因此保留虚拟对象的固有尺寸有助于保持真实感。因此,该app没有添加手势或者其他UI来启用虚拟对象缩放。此外,通过不使用缩放手势,示例app防止用户对手势调整虚拟对象大小 或 改变虚拟对象与相机的距离 产生困惑。(如果你选择再app中启动虚拟对象缩放,请使用pinch捏合手势。)

警惕潜在的冲突手势。示例app的 ThresholdPanGesture 类是一个 UIPanGestureRecognizer 子类,它提供了一种方法,延迟手势识别器效果,直到手势正在进行通过指定的移动阈值。示例app的touchesMoved(with:) 方法使用该类来让用户在单指拖动虚拟对象手势和双指旋转对象手势间平滑过渡:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)

let translationMagnitude = translation(in: view).length

// Adjust the threshold based on the number of touches being used.
let threshold = ThresholdPanGesture.threshold(forTouchCount: touches.count)

if !isThresholdExceeded && translationMagnitude > threshold {
isThresholdExceeded = true

// Set the overall translation to zero as the gesture should now begin.
setTranslation(.zero, in: view)
}
}

确保虚拟对象的移动平稳。示例app的 setPosition(_:relativeTo:smoothMovement) 方法在触摸手势引起的拖动虚拟对象位置 和 该对象近期位置之间插值。通过平均 基于相机距离的近期位置,该方法产生平滑的拖动移动,而不会使 被拖动物体滞后于用户的手势:

1
2
3
4
5
6
7
8
9
10
11
12
13
if smoothMovement {
let hitTestResultDistance = simd_length(positionOffsetFromCamera)

// Add the latest position and keep up to 10 recent distances to smooth with.
recentVirtualObjectDistances.append(hitTestResultDistance)
recentVirtualObjectDistances = Array(recentVirtualObjectDistances.suffix(10))

let averageDistance = recentVirtualObjectDistances.average!
let averagedDistancePosition = simd_normalize(positionOffsetFromCamera) * averageDistance
simdPosition = cameraWorldPosition + averagedDistancePosition
} else {
simdPosition = cameraWorldPosition + positionOffsetFromCamera
}

探索更吸引用户的交互方法。在AR体验中,pan平移手势(将手指移动到设备的屏幕上)并不是将虚拟对象拖动到新位置的唯一自然方式。用户也可以在移动设备时直观地将手指一直放置在虚拟对象上,从而将虚拟对象拖到AR场景中的新位置。

即使手势在屏幕上的触摸位置没有改变,示例app也通过在拖动手势时不断的调用其 updateObjectToCurrentTrackingPosition() 方法来支持这种手势。若设备在拖动过程中发生移动,则该方法会计算其对应于触摸位置的新的现实位置,并相应地移动虚拟对象。

进入AR

指示用户何时进行初始化。该示例app通过文本提示显示有关AR session状态,通过浮动的文本视图显示与AR进行交互的说明。示例app的 StatusViewController 类管理此视图,可显示短暂的说明,该说明允许用户在读取它们之后消失,或显示始终保持可见的重要状态信息直至用户纠正问题。

image

解决问题

如果不符合用户的期望可以重置。示例app具有一个始终在UI界面右上角的Reset重置按钮,允许用户重启AR体验不管其状态如何。参见示例代码的 restartExperience() 方法。

仅在支持的设备上提供AR功能。示例App的核心功能需要使用ARKit,因此它在Info.plist文件的 UIRequiredDeviceCapabilities 部分定义了 arkit 键。在部署构建的项目时,该键的设置可以防止在不支持ARKit的设备上安装。

如果你的应用把ARKit作为辅助功能,请使用 isSupported 方法确定是否需要隐藏 ARKit 功能。