如何在ARKit中旋转或缩放3D模型SCN文件?附渲染代码
在ARKit中对SCN格式3D模型实现旋转与缩放
Hey,看了你这段加载SCN模型的ARKit代码,想给模型加旋转和缩放功能其实挺简单的,咱们一步步来实现:
第一步:先获取目标模型的SCNNode
你现在直接把整个场景赋值给了sceneView,但要操作具体的模型,得先拿到对应的SCNNode节点。修改你的viewDidLoad方法,加载场景后定位到模型节点:
override func viewDidLoad() { super.viewDidLoad() // 加载SCN场景,增加空值判断避免崩溃 guard let scene = SCNScene(named: "duck.scn", inDirectory: "models.scnassets/furniture") else { fatalError("无法加载模型文件,请检查路径或文件名是否正确") } sceneView.scene = scene // 获取模型节点(这里的"duck"要和你SCN文件里的节点名称完全一致) guard let duckNode = scene.rootNode.childNode(withName: "duck", recursively: true) else { fatalError("找不到模型节点,请检查SCN文件中的节点名称") } // 可选:设置初始缩放,比如缩小到原尺寸的一半 duckNode.scale = SCNVector3(0.5, 0.5, 0.5) }
第二步:实现缩放操作
有两种常见方式:代码直接设置固定缩放比例,或者通过捏合手势让用户交互缩放。
方式1:代码直接设置缩放
直接修改节点的scale属性,SCNVector3的三个值分别对应X/Y/Z轴的缩放比例,比如:
// 缩小到原尺寸的一半 duckNode.scale = SCNVector3(0.5, 0.5, 0.5) // 放大到原尺寸的2倍 duckNode.scale = SCNVector3(2.0, 2.0, 2.0)
方式2:捏合手势实现交互缩放
给sceneView添加捏合手势识别器,让用户可以通过双指捏合来缩放模型:
首先在viewDidLoad里添加手势:
// 添加捏合手势 let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:))) sceneView.addGestureRecognizer(pinchGesture)
然后实现手势处理方法:
@objc private func handlePinch(_ gesture: UIPinchGestureRecognizer) { guard let duckNode = sceneView.scene.rootNode.childNode(withName: "duck", recursively: true) else { return } // 根据手势的缩放比例调整节点尺寸 let scaleFactor = gesture.scale duckNode.scale = SCNVector3( duckNode.scale.x * scaleFactor, duckNode.scale.y * scaleFactor, duckNode.scale.z * scaleFactor ) // 重置手势的scale值,避免累积导致缩放速度失控 gesture.scale = 1.0 }
第三步:实现旋转操作
同样有两种方式:代码固定旋转角度,或者通过旋转手势让用户交互控制。
方式1:代码直接设置旋转
可以通过eulerAngles(欧拉角)或rotation属性来设置旋转,单位都是弧度(比如π/2对应90度):
// 方式1:欧拉角绕Y轴旋转90度(Y轴通常是垂直方向) duckNode.eulerAngles.y += .pi/2 // 方式2:rotation属性,格式为SCNVector4(x轴分量, y轴分量, z轴分量, 旋转弧度) // 下面的代码也是绕Y轴旋转90度 duckNode.rotation = SCNVector4(0, 1, 0, .pi/2)
方式2:旋转手势实现交互旋转
给sceneView添加旋转手势识别器,用户可以通过双指旋转来控制模型方向:
先在viewDidLoad里添加手势:
// 添加旋转手势 let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotation(_:))) sceneView.addGestureRecognizer(rotationGesture)
然后实现手势处理方法:
@objc private func handleRotation(_ gesture: UIRotationGestureRecognizer) { guard let duckNode = sceneView.scene.rootNode.childNode(withName: "duck", recursively: true) else { return } // 绕Y轴旋转,若需要绕X或Z轴,修改eulerAngles的x或z属性即可 duckNode.eulerAngles.y += gesture.rotation // 重置手势的rotation值,避免累积导致旋转速度失控 gesture.rotation = 0 }
完整整合后的代码
把上面的功能整合到你的SimpleViewController里,完整代码如下:
import UIKit import ARKit class SimpleViewController: UIViewController { @IBOutlet var sceneView: ARSCNView! override func viewDidLoad() { super.viewDidLoad() // 加载SCN场景 guard let scene = SCNScene(named: "duck.scn", inDirectory: "models.scnassets/furniture") else { fatalError("无法加载模型文件,请检查路径是否正确") } sceneView.scene = scene // 初始化模型节点(可选:设置初始缩放) if let duckNode = scene.rootNode.childNode(withName: "duck", recursively: true) { duckNode.scale = SCNVector3(0.5, 0.5, 0.5) } // 添加所有手势识别器 setupGestureRecognizers() } 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() } private func setupGestureRecognizers() { // 捏合缩放手势 let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:))) sceneView.addGestureRecognizer(pinchGesture) // 旋转手势 let rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotation(_:))) sceneView.addGestureRecognizer(rotationGesture) } @objc private func handlePinch(_ gesture: UIPinchGestureRecognizer) { guard let duckNode = sceneView.scene.rootNode.childNode(withName: "duck", recursively: true) else { return } let scaleFactor = gesture.scale duckNode.scale = SCNVector3( duckNode.scale.x * scaleFactor, duckNode.scale.y * scaleFactor, duckNode.scale.z * scaleFactor ) gesture.scale = 1.0 } @objc private func handleRotation(_ gesture: UIRotationGestureRecognizer) { guard let duckNode = sceneView.scene.rootNode.childNode(withName: "duck", recursively: true) else { return } duckNode.eulerAngles.y += gesture.rotation gesture.rotation = 0 } }
几个注意点
- 确保你获取节点时用的名称(比如*"duck"*)和SCN文件里的节点名称完全一致,不然会找不到节点。你可以在Xcode的Scene编辑器里查看节点的名称。
- 如果你的模型由多个子节点组成,建议把所有子节点放到一个父节点下,这样操作父节点就能统一控制整个模型的缩放和旋转。
- 手势处理里一定要重置
gesture.scale和gesture.rotation,不然每次手势触发都会累积之前的数值,导致缩放/旋转速度越来越快,体验很差。
内容的提问来源于stack exchange,提问作者S.M_Emamian
相关产品推荐
相关产品推荐

