unprojectPoint 作用
unprojectPoint
的作用其实很类似hitTest
方法,都是从屏幕指定位置发射射线,与 3D 空间某处相交,得到交点。
不同的是:
hitTest
方法是射线与某个SCNGeometry相交,返回所在的 SCNNode 和交点信息;unprojectPoint
是将屏幕上的点投影到一个3D 空间内的平面上,可脱离 SCNNode 类和 SCNGeometry 类来使用。
注意:SceneKit 中
SCNSceneRenderer
协议(SCNView
实现了这个协议)也提供了一个unprojectPoint
方法,但是这个方法只能将屏幕上的点,投影到视锥体的平面上,z 是 0 则投影到 zNear 平面上,z 是 1 则投影到 zFar 平面上。
ARKit 中提供了两个unprojectPoint
方法:一个在ARSCNView
类下,一个在ARCamera
类下。并且官方文档中说明了,ARSCNView
类下的方法,其实就是调用了ARCamera
类的方法,只是ARSCNView
类自己知道自己的 size 和 orientation,所以少了两个参数。
// 官方文档说明了ontoPlane是指:投影到 4x4 矩阵的 xz 平面上
extension ARCamera {
@available(iOS 12.0, *)
@nonobjc public func unprojectPoint(_ point: CGPoint, ontoPlane planeTransform: simd_float4x4, orientation: UIInterfaceOrientation, viewportSize: CGSize) -> simd_float3?
}
@available(iOS 12.0, *)
extension ARSCNView {
@nonobjc public func unprojectPoint(_ point: CGPoint, ontoPlane planeTransform: simd_float4x4) -> simd_float3?
}
示例用法
当我们想要在 AR 空间确定一个位置时,就可以用unprojectPoint
来处理:
比如,在 3D 空间放置一个小球。我们先创建一个平面,然后从屏幕中点投影到平面上,在交点处放置一个小球:
// plane 在 xy 平面,是竖直的
let planeNode = SCNNode(geometry: SCNPlane(width: 1, height: 1))
planeNode.geometry?.firstMaterial?.diffuse.contents = UIColor(white: 1, alpha: 0.8)
sceneView.scene.rootNode.addChildNode(planeNode)
点击屏幕投影到平面上:
verride func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 绕 x 轴旋转 90 度,将 xz 平面旋转到竖直状态,与显示的平面同一位置/姿态
let planeT = matrix_float4x4(simd_quatf(angle: .pi / 2, axis: simd_float3(1, 0, 0)))
// 或者直接简单粗暴旋转矩阵
let planeT2 = simd_float4x4(
simd_float4(1, 0, 0, 0), // x 轴不变
simd_float4(0, 0, 1, 0), // y 轴指向父的 z 方向
simd_float4(0, -1, 0, 0), // z 轴指向父的 -y 方向
simd_float4(0, 0, 0, 1)
)
// 求交点
let position = sceneView.unprojectPoint(sceneView.center, ontoPlane: planeT)
// 在交点处放置一个红色小球
if let position = position {
let ballNode = SCNNode(geometry: SCNSphere(radius: 0.02))
ballNode.simdPosition = position
ballNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red
sceneView.scene.rootNode.addChildNode(ballNode)
}
}
效果如下图,需要注意的是,unprojectPoint
投影的xz平面是无穷大的