iOS 11中如何将3D物体固定放置在水平面指定位置?
解决ARKit中放置3D物体后无法固定位置的问题
这个问题我太熟悉了——ARKit里刚放好的3D物体乱跑,本质是因为ARPlaneAnchor会随着你扫描环境的精度提升,不断更新它的位置、大小和姿态,而你直接把物体加到了场景根节点,没有和平面锚点绑定,所以当锚点更新时,物体就会和平面“脱节”,看起来像是自己在移动。
下面给你一步步的解决方案:
1. 先保存平面锚点与对应节点的关联
首先需要一个字典来记录每个检测到的平面锚点和它对应的可视化平面节点,这样后续锚点更新时我们能找到对应的节点做调整。在你的ViewController里添加这个属性:
var planeAnchorMap = [ARPlaneAnchor: SCNNode]()
2. 修改平面检测代码,绑定锚点与节点
更新renderer(_:didAdd:for:)方法,把创建的平面节点存入字典,方便后续查找:
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { guard let planeAnchor = anchor as? ARPlaneAnchor else { return } // 创建平面几何体 let plane = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z)) let planeNode = SCNNode(geometry: plane) // 设置平面位置与旋转 planeNode.position = SCNVector3(x: planeAnchor.center.x, y: 0, z: planeAnchor.center.z) planeNode.transform = SCNMatrix4MakeRotation(-Float.pi/2, 1, 0, 0) // 设置平面材质 let gridMaterial = SCNMaterial() gridMaterial.diffuse.contents = UIImage(named: "art.scnassets/grid.png") plane.materials = [gridMaterial] // 将平面节点添加到ARKit管理的节点下,并存入字典 node.addChildNode(planeNode) planeAnchorMap[planeAnchor] = planeNode }
这里要注意:ARKit会为每个ARPlaneAnchor创建一个对应的SCNNode,我们把平面节点加到这个节点下,而不是直接加到根节点,这样平面锚点更新时,这个节点会自动变换,平面也会跟着更新。
3. 添加平面锚点更新的处理方法
当ARKit优化平面检测结果时,会调用renderer(_:didUpdate:for:),我们需要在这里更新平面的大小,保持平面显示准确:
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { guard let planeAnchor = anchor as? ARPlaneAnchor, let planeNode = planeAnchorMap[planeAnchor], let planeGeometry = planeNode.geometry as? SCNPlane else { return } // 更新平面大小 planeGeometry.width = CGFloat(planeAnchor.extent.x) planeGeometry.height = CGFloat(planeAnchor.extent.z) // 更新平面位置 planeNode.position = SCNVector3(x: planeAnchor.center.x, y: 0, z: planeAnchor.center.z) }
4. 修改物体放置代码,绑定到平面锚点节点
现在关键的一步:把你要放置的3D物体,加到命中的平面锚点对应的节点下,而不是场景根节点。这样当平面锚点更新时,物体就会跟着平面一起变换,保持固定位置:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let touchLocation = touch.location(in: sceneView) // 只检测已有的平面 let results = sceneView.hitTest(touchLocation, types: .existingPlaneUsingExtent) guard let hitResult = results.first, let planeAnchor = hitResult.anchor as? ARPlaneAnchor, let planeNode = planeAnchorMap[planeAnchor] else { return } // 加载3D物体 guard let boxScene = SCNScene(named: "art.scnassets/porsche.scn"), let boxNode = boxScene.rootNode.childNode(withName: "car", recursively: true) else { print("无法加载3D模型") return } // 设置物体位置(转换为平面节点的本地坐标,绑定更紧密) let worldPosition = SCNVector3( x: hitResult.worldTransform.columns.3.x, y: hitResult.worldTransform.columns.3.y, z: hitResult.worldTransform.columns.3.z ) boxNode.position = planeNode.convertPosition(worldPosition, from: nil) // 将物体添加到平面节点下,而不是根节点 planeNode.addChildNode(boxNode) }
额外说明
如果你希望物体放置后完全固定,不再随平面更新移动,可以在放置后把物体从平面节点移到根节点:
// 放置后执行这行代码 sceneView.scene.rootNode.addChildNode(boxNode) boxNode.removeFromParentNode() // 从平面节点移除,加到根节点
但这种方式可能会在平面更新后,物体和平面出现错位,所以更推荐绑定到平面锚点的方式,保证物体始终在平面上的正确位置。
内容的提问来源于stack exchange,提问作者poojitha
相关产品推荐
相关产品推荐

