ARKit 中用好 projectPoint: 效率又双叒叕提升一倍

2,077 阅读1分钟

projectPoint 用法

projectPoint方法的作用,是将 3D 空间内的点,投影到屏幕所在的平面上。这个方法也有两个:

一个是在 SceneKit 框架内,SCNView通过实现SCNSceneRenderer协议,也就实现了这个方法,所以 ARKit 也可以使用它。如果在 zFar 平面上,则 z 坐标为 1;如果在 zNear 平面上,则 z 坐标为 0;其余则处于 0~1 之间。一般情况下 AR 中用这个就足够了。

func projectPoint(_ point: SCNVector3) -> SCNVector3

另一个是 ARKit 框架中,ARCamera类中,这个类重新实现了projectPoint方法和unprojectPoint方法。由于ARCamera类中不能像SCNView一样,直接获取orientation和viewportSize,所以多了几个参数:

open func projectPoint(_ point: simd_float3, orientation: UIInterfaceOrientation, viewportSize: CGSize) -> CGPoint

示例

在 AR 中,可以用这个方法来实现一个引导标签的功能:
当某个物体出现在你的镜头内时,正常显示虚拟物体;当手机移动/转动时,在屏幕上显示一个标签,引导用户去看虚拟物体。

var airplane: SCNNode!
var redView: UIView!

override func viewDidLoad() {
    super.viewDidLoad()
    
    // Set the view's delegate
    sceneView.delegate = self
    
    // Show statistics such as fps and timing information
    sceneView.showsStatistics = true
    
    // Create a new scene
    let scene = SCNScene(named: "art.scnassets/ship.scn")!
    // 调整飞机模型位置
    airplane = scene.rootNode.childNode(withName: "ship", recursively: true)!
    airplane.simdPosition = simd_float3(0, 0, -1)
    
    // 显示标签
    redView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
    redView.backgroundColor = UIColor.red
    view.addSubview(redView)
    // Set the scene to the view
    sceneView.scene = scene
}


func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
    let position = sceneView.projectPoint(airplane.position)
    DispatchQueue.main.async {
        // 标签位置不超过屏幕显示范围
        let width = self.view.bounds.size.width
        let height = self.view.bounds.size.height
        let x = simd_clamp(Double(position.x), 0.0, Double(width))
        let y = simd_clamp(Double(position.y), 0.0, Double(height))
        self.redView.center = CGPoint(x: x, y: y)
        
        // 出现在镜头内为绿色半透明,未在镜头内为红色
        if let pov = self.sceneView.pointOfView {
            let insideFrustum = self.sceneView.isNode(self.airplane, insideFrustumOf: pov)
            
            self.redView.backgroundColor = insideFrustum ? UIColor.green.withAlphaComponent(0.5) : UIColor.red
            
        }
    }
    
}