ARKit中3D空间点2D投影结果在特定设备位姿下突变问题
问题诱因
- 核心原因是透视投影的近裁剪面翻转特性:你调用的投影接口(不管是
SCNSceneRenderer.projectPoint还是ARCamera.projectPoint)都基于透视投影矩阵计算。当要投影的3D点位于相机近裁剪面后方(即相机视野的背面)时,透视投影计算的齐次坐标w分量会变为负数,导致投影得到的x、y坐标符号翻转;当点刚好接近近裁剪面时,w分量趋近于0,x、y除以w后数值会突变到极大值,就是你日志里看到的先暴涨再翻转的现象。 - 触发场景就是移动设备时,之前记录的静态测量端点(比如第一个点)刚好跑到了当前相机的背面,此时投影结果异常,直接赋值给SpriteKit节点就会出现连线、端点突然翻转的问题。
修复方案
- 先添加投影结果合法性校验:
SCNSceneRenderer.projectPoint返回的SCNVector3的z分量是归一化深度值,范围在[0,1]之间才代表点处于相机的可视范围内(0对应近裁剪面,1对应远裁剪面),z<0说明点在近裁剪面后方,z>1说明点在远裁剪面外,这两种情况投影结果都是无效的。 - 处理无效投影逻辑:只要测量的起止点任意一个投影结果无效,就将整个测量项的SpriteKit节点隐藏,避免异常坐标渲染出翻转的效果;如果业务需要保留部分可见的测量项,也可以对连线做视锥体裁剪,只绘制落在可视范围内的部分。
修改后的代码示例:
func updateComponentPositions(renderer: SCNSceneRenderer) { // 投影起点并校验合法性 let projectedStart = renderer.projectPoint(startPointPosition) guard projectedStart.z > 0 && projectedStart.z < 1 else { // 点不可见,隐藏所有组件 startPointNode.isHidden = true endPointNode.isHidden = true labelBackgroundNode.isHidden = true lineNode.isHidden = true return } // 投影终点并校验合法性 let projectedEnd = renderer.projectPoint(endPointPosition) guard projectedEnd.z > 0 && projectedEnd.z < 1 else { startPointNode.isHidden = true endPointNode.isHidden = true labelBackgroundNode.isHidden = true lineNode.isHidden = true return } // 所有点可见,恢复显示 startPointNode.isHidden = false endPointNode.isHidden = false labelBackgroundNode.isHidden = false lineNode.isHidden = false // 原有坐标更新逻辑 let startPointSKPosition = CGPoint(x: CGFloat(projectedStart.x), y: scene.size.height - CGFloat(projectedStart.y) - startPointNode.frame.height/2) startPointNode.position = startPointSKPosition let endPointSKPosition = CGPoint(x: CGFloat(projectedEnd.x), y: scene.size.height - CGFloat(projectedEnd.y) - endPointNode.frame.height/2) endPointNode.position = endPointSKPosition labelBackgroundNode.position = CGPoint(x: (startPointSKPosition.x + endPointSKPosition.x) / 2, y: (startPointSKPosition.y + endPointSKPosition.y) / 2) lineNode.path = getLinePath(from: startPointSKPosition, to: endPointSKPosition) }
如果使用ARCamera.projectPoint做投影,也可以先将3D点转换到相机坐标系下做合法性校验:
let cameraTransform = arCamera.transform let pointInCameraSpace = simd_inverse(cameraTransform) * simd_float4(x: point.x, y: point.y, z: point.z, w: 1) // ARKit默认近平面为0.001m,pointInCameraSpace.z < 0.001即说明点在近平面后方,投影无效
内容的提问来源于stack exchange,提问作者Ricky
相关产品推荐
相关产品推荐

