ARKit中将ARAnchor转为带纹理SCNGeometry时纹理拉伸问题
解决ARKit WorldTracking中锚点更新时纹理拉伸问题
核心问题
每次更新ARMeshAnchor时,直接用当前相机图像覆盖整个网格纹理,导致不在当前视野内的网格区域因错误映射出现拉伸。以下是几种可行的解决方法:
方法一:仅对视野内的锚点更新纹理
通过判断锚点是否在相机视锥体内,决定是否更新纹理,视野外的锚点保留原有材质。
实现步骤
- 添加锚点可见性判断函数:
func isAnchorVisible(anchor: ARMeshAnchor, camera: ARCamera) -> Bool { // 获取锚点世界坐标 let anchorWorldPos = SIMD3<Float>(anchor.transform.columns.3.x, anchor.transform.columns.3.y, anchor.transform.columns.3.z) // 转换到相机空间 let cameraSpacePos = camera.transform.inverse * SIMD4<Float>(anchorWorldPos, 1) // 排除相机后方的锚点 guard cameraSpacePos.z < 0 else { return false } // 转换到NDC空间(标准化设备坐标) let ndcPos = camera.projectionMatrix * cameraSpacePos // 判断是否在视锥体范围内 return abs(ndcPos.x) <= 1 && abs(ndcPos.y) <= 1 }
- 修改更新锚点的回调:
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) { guard let frame = self.sceneView.session.currentFrame else { return } guard let meshAnchor = anchor as? ARMeshAnchor else { return } let camera = frame.camera // 判断锚点是否在当前视野内 guard isAnchorVisible(anchor: meshAnchor, camera: camera) else { // 视野外锚点不更新纹理,保留原有材质 let geometry = self.scanUpdatedGeometory(frame: frame, anchor: meshAnchor, node: node, needTexture: false) node.geometry = geometry return } // 视野内锚点更新纹理 let geometry = self.scanUpdatedGeometory(frame: frame, anchor: meshAnchor, node: node, needTexture: true, cameraImage: captureCamera()) node.geometry = geometry }
方法二:为顶点生成正确的纹理坐标(彻底解决拉伸)
问题根源是纹理未正确映射到每个顶点,导致整个网格强行适配单张图像。通过为每个顶点计算对应相机图像的UV坐标,实现精准纹理映射。
实现步骤
- 扩展SCNGeometry,支持从ARMeshGeometry生成带正确UV的几何体:
extension SCNGeometry { convenience init(arMeshGeometry: ARMeshGeometry, camera: ARCamera, modelMatrix: simd_float4x4, needTexture: Bool) { let vertices = arMeshGeometry.vertices let normals = arMeshGeometry.normals let faces = arMeshGeometry.faces // 创建顶点数据源 let vertexSource = SCNGeometrySource(buffer: vertices.buffer, vertexFormat: vertices.format, semantic: .vertex, vertexCount: vertices.count, dataOffset: vertices.offset, dataStride: vertices.stride) // 创建法线数据源 let normalSource = SCNGeometrySource(buffer: normals.buffer, vertexFormat: normals.format, semantic: .normal, vertexCount: normals.count, dataOffset: normals.offset, dataStride: normals.stride) // 生成UV坐标数据 var uvData = Data() if needTexture { for index in 0..<vertices.count { let vertex = vertices[index] // 顶点从局部空间转世界空间 let worldVertex = modelMatrix * simd_float4(vertex, 1) // 世界空间转相机空间 let cameraVertex = camera.transform.inverse * worldVertex // 相机空间转NDC空间 let ndcVertex = camera.projectionMatrix * cameraVertex // NDC坐标转UV(转换为0-1范围) let uv = simd_float2((ndcVertex.x + 1)/2, (1 - ndcVertex.y)/2) uvData.append(Data(bytes: &uv, count: MemoryLayout<simd_float2>.stride)) } } // 创建UV数据源 let uvSource = SCNGeometrySource(data: uvData, semantic: .texcoord, vectorCount: vertices.count, usesFloatComponents: true, componentsPerVector: 2, bytesPerComponent: MemoryLayout<Float>.stride, dataOffset: 0, dataStride: MemoryLayout<simd_float2>.stride) // 创建面元素 let faceData = Data(buffer: faces.buffer) let faceElement = SCNGeometryElement(data: faceData, primitiveType: .triangles, primitiveCount: faces.count, bytesPerIndex: faces.indexCountPerPrimitive == 3 ? MemoryLayout<UInt32>.stride : MemoryLayout<UInt16>.stride) self.init(sources: [vertexSource, normalSource, uvSource], elements: [faceElement]) } }
- 修改纹理应用函数,添加纹理包裹模式避免拉伸:
func scanGeometory(frame: ARFrame, anchor: ARMeshAnchor, node: SCNNode, needTexture: Bool = false, cameraImage: UIImage? = nil) -> SCNGeometry { let camera = frame.camera let geometry = SCNGeometry(arMeshGeometry: anchor.geometry, camera: camera, modelMatrix: anchor.transform, needTexture: needTexture) if let image = cameraImage, needTexture { geometry.firstMaterial?.diffuse.contents = image // 设置纹理包裹模式为clamp,超出UV范围的区域使用边缘颜色,避免拉伸 geometry.firstMaterial?.diffuse.wrapS = .clamp geometry.firstMaterial?.diffuse.wrapT = .clamp } else { geometry.firstMaterial?.diffuse.contents = UIColor(red: 0.5, green: 1.0, blue: 0.0, alpha: 0.7) } node.geometry = geometry return geometry }
方法三:多纹理叠加(进阶方案)
如果需要保留不同视角的纹理信息,可以维护一个纹理缓存,记录每次扫描的图像,然后根据顶点的视角方向选择对应的纹理采样。这种方案适合需要更完整纹理重建的场景,但实现复杂度较高,需结合顶点颜色或额外的UV通道来区分不同视角的区域。
内容的提问来源于stack exchange,提问作者Zia ur Rehman
相关产品推荐
相关产品推荐

