我有一个UIViewController,它使用ARSCNView并通过Scenekit向它添加一些元素,如下例所示。一切都工作正常,除非我打电话给当前显示这个视图控制器,它需要很长的时间或延迟才能在屏幕上显示。
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.showsStatistics = DebugSettings.isDebugActive
for (index, coach) in coachPositions.enumerated() {
let coachGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.005)
let coachNode = TrainEngineNode(position: SCNVector3Make(0, Float(index) * 0.1, -0.5), geometry: coachGeometry)
sceneView.scene.rootNode.addChildNode(coachNode)
}
self.sceneView.autoenablesDefaultLighting = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// Run the view's session
sceneView.session.run(configuration)
}
答案 0 :(得分:7)
正如大家所指出的,延迟是你在设置场景时阻止主线程的事实。所有UI更新都发生在主线程上,并且显示刷新60 times per second,除了可以做120Hz的最新iPad专业版。这意味着您所执行的任何同步工作单元必须在不到1/60 = 0.016667秒内完成。
您的初始化程序TrainEngineNode(position:geometry:)
可能正在执行诸如从DAE或OBJ文件加载资源的工作,这可能是所有时间的所在。我这样说是因为如果你改变这一行:
let coachNode = TrainEngineNode(position: SCNVector3Make(0, Float(index) * 0.1, -0.5), geometry: coachGeometry)
这是我需要做的,因为你的问题中没有提供TrainEngineNode
:
let coachNode = SCNNode(geometry: coachGeometry)
然后iPhone 7没有明显的延迟。
SceneKit和ARKit不要求您对任何特定线程进行更改,因此只要您遇到这种情况,您就可以将工作卸载到后台队列,或者最好是您管理的串行队列。在适当的时间进行工作有一些注意事项,例如在ARSession
完全运行后将对象添加到场景中。对于创建ARKit应用程序时可能遵循的模式的一些优秀想法,我建议使用Apple提供的this sample。
简化示例如下:
class SimpleARViewController: UIViewController {
@IBOutlet weak var sceneView: ARSCNView!
weak var activityIndicator: UIActivityIndicatorView?
var coachPositions = [1, 2, 3, 4, 5]
let updateQueue = DispatchQueue(label: "com.example.apple-samplecode.arkitexample.serialSceneKitQueue")
override func viewDidLoad() {
super.viewDidLoad()
sceneView.alpha = 0
view.backgroundColor = .black
sceneView.isPlaying = false
sceneView.session.delegate = self
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
activityIndicator.startAnimating()
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(activityIndicator)
NSLayoutConstraint.activate([view.centerXAnchor.constraint(equalTo: activityIndicator.centerXAnchor, constant: 0),
view.centerYAnchor.constraint(equalTo: activityIndicator.centerYAnchor, constant: 0)])
self.activityIndicator = activityIndicator
sceneView.autoenablesDefaultLighting = true
}
private func loadScene() {
SCNTransaction.begin()
SCNTransaction.disableActions = true
for (index, _) in self.coachPositions.enumerated() {
let coachGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.005)
// Simulates loading time of `TrainEngineNode(position:geometry:)`
usleep(500000)
let coachNode = SCNNode(geometry: coachGeometry)
coachNode.worldPosition = SCNVector3Make(0, Float(index) * 0.1, -0.5)
self.sceneView.scene.rootNode.addChildNode(coachNode)
}
SCNTransaction.commit()
self.isLoading = false
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
var isLoading = true
var hasLoaded = false
}
extension SimpleARViewController: ARSessionDelegate {
func session(_ session: ARSession, didUpdate frame: ARFrame) {
// Waiting until after the session starts prevents objects from jumping around
if hasLoaded == false {
hasLoaded = true
updateQueue.async { [weak self] in
self?.loadScene()
}
} else if isLoading == false {
guard let activityIndicator = self.activityIndicator else { return }
DispatchQueue.main.async {
UIView.animate(withDuration: 0.35, animations: { [weak self] in
self?.sceneView.alpha = 1
activityIndicator.alpha = 0
}, completion: { _ in
activityIndicator.removeFromSuperview()
})
}
}
}
}
此代码会产生以下互动:
正如你在gif中看到的那样,视图控制器的显示没有任何延迟,因为所有的加载工作都是如此。现在在我们的专用串行队列上完成。即使这个人为的例子也可以通过将TrainEngineNode
的创建与它们被添加到场景的时间分离来进一步改进。为了简洁起见,我保持逻辑与您已经拥有的逻辑类似,同时提供相对强大的初始化。对于3D对象加载的更强大/抽象的实现,我建议查看VirtualObjectLoader
以及如何在上述示例项目中使用它。
答案 1 :(得分:1)
如果你的循环需要很长时间,你可以试试这个:
override func viewDidLoad() {
super.viewDidLoad()
// start your loading animator
DispatchQueue.main.async {
self.sceneView.showsStatistics = DebugSettings.isDebugActive
for (index, coach) in self.coachPositions.enumerated() {
let coachGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.005)
let coachNode = TrainEngineNode(position: SCNVector3Make(0, Float(index) * 0.1, -0.5), geometry: coachGeometry)
self.sceneView.scene.rootNode.addChildNode(coachNode)
}
self.sceneView.autoenablesDefaultLighting = true
// stop loading animator
}
}
如果不能工作,你可以像这样延迟时间:
override func viewDidLoad() {
super.viewDidLoad()
// start your loading animator
let delayTimeInterval: Double = 0.1 // 0.1 second
let delayTime = DispatchTime(uptimeNanoseconds: DispatchTime.now().uptimeNanoseconds + UInt64(delayTimeInterval * Double(NSEC_PER_SEC)))
DispatchQueue.main.asyncAfter(deadline: delayTime) {
self.sceneView.showsStatistics = DebugSettings.isDebugActive
for (index, coach) in self.coachPositions.enumerated() {
let coachGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.005)
let coachNode = TrainEngineNode(position: SCNVector3Make(0, Float(index) * 0.1, -0.5), geometry: coachGeometry)
self.sceneView.scene.rootNode.addChildNode(coachNode)
}
self.sceneView.autoenablesDefaultLighting = true
// stop loading animator
}
}