swift - 将 ARFaceGeometry 保存到 OBJ 文件

标签 swift scenekit arkit metal scenekit-modelio

在 iOS ARKit 应用程序中,我一直在尝试保存 ARFaceGeometry数据到 OBJ 文件。我按照这里的解释:How to make a 3D model from AVDepthData? .但是,未正确创建 OBJ。这是我所拥有的:

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        guard let faceAnchor = anchor as? ARFaceAnchor else { return }
        currentFaceAnchor = faceAnchor

        // If this is the first time with this anchor, get the controller to create content.
        // Otherwise (switching content), will change content when setting `selectedVirtualContent`.
        if node.childNodes.isEmpty, let contentNode = selectedContentController.renderer(renderer, nodeFor: faceAnchor) {
            node.addChildNode(contentNode)
        }

        // https://stackoverflow.com/questions/52953590/how-to-make-a-3d-model-from-avdepthdata
        let geometry = faceAnchor.geometry        
        let allocator = MDLMeshBufferDataAllocator()
        let vertices = allocator.newBuffer(with: Data(fromArray: geometry.vertices), type: .vertex)
        let textureCoordinates = allocator.newBuffer(with: Data(fromArray: geometry.textureCoordinates), type: .vertex)
        let triangleIndices = allocator.newBuffer(with: Data(fromArray: geometry.triangleIndices), type: .index)
        let submesh = MDLSubmesh(indexBuffer: triangleIndices, indexCount: geometry.triangleIndices.count, indexType: .uInt16, geometryType: .triangles, material: MDLMaterial(name: "mat1", scatteringFunction: MDLPhysicallyPlausibleScatteringFunction()))

        let vertexDescriptor = MDLVertexDescriptor()
        // Attributes
        vertexDescriptor.addOrReplaceAttribute(MDLVertexAttribute(name: MDLVertexAttributePosition, format: .float3, offset: 0, bufferIndex: 0))
        vertexDescriptor.addOrReplaceAttribute(MDLVertexAttribute(name: MDLVertexAttributeNormal, format: .float3, offset: MemoryLayout<float3>.stride, bufferIndex: 0))
        vertexDescriptor.addOrReplaceAttribute(MDLVertexAttribute(name: MDLVertexAttributeTextureCoordinate, format: .float2, offset: MemoryLayout<float3>.stride + MemoryLayout<float3>.stride, bufferIndex: 0))
        // Layouts
        vertexDescriptor.layouts.add(MDLVertexBufferLayout(stride: MemoryLayout<float3>.stride + MemoryLayout<float3>.stride + MemoryLayout<float2>.stride))

        let mdlMesh = MDLMesh(vertexBuffers: [vertices, textureCoordinates], vertexCount: geometry.vertices.count, descriptor: vertexDescriptor, submeshes: [submesh])
        mdlMesh.addNormals(withAttributeNamed: MDLVertexAttributeNormal, creaseThreshold: 0.5)
        let asset = MDLAsset(bufferAllocator: allocator)
        asset.add(mdlMesh)

        let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let exportUrl = documentsPath.appendingPathComponent("face.obj")
        try! asset.export(to: exportUrl)
    }

生成的 OBJ 文件如下所示:
# Apple ModelIO OBJ File: face
mtllib face.mtl
g 
v -0.000128156 -0.0277879 0.0575149
vn 0 0 0
vt -9.36008e-05 -0.0242016
usemtl material_1
f 1/1/1 1/1/1 1/1/1
f 1/1/1 1/1/1 1/1/1
f 1/1/1 1/1/1 1/1/1
... and many more lines

我希望有更多的顶点,并且索引值看起来是错误的。

最佳答案

核心问题是您的顶点数据没有正确描述。当您在构建网格时向 Model I/O 提供顶点描述符时,它表示数据实际具有的布局,而不是您想要的布局。您提供了两个顶点缓冲区,但您的顶点描述符描述了一个只有一个顶点缓冲区的交错数据布局。

解决此问题的最简单方法是修复顶点描述符以反射(reflect)您提供的数据:

let vertexDescriptor = MDLVertexDescriptor()
// Attributes
vertexDescriptor.attributes[0] = MDLVertexAttribute(name: MDLVertexAttributePosition,
                                                    format: .float3,
                                                    offset: 0,
                                                    bufferIndex: 0)
vertexDescriptor.attributes[1] = MDLVertexAttribute(name: MDLVertexAttributeTextureCoordinate,
                                                    format: .float2,
                                                    offset: 0,
                                                    bufferIndex: 1)
// Layouts
vertexDescriptor.layouts[0] = MDLVertexBufferLayout(stride: MemoryLayout<float3>.stride)
vertexDescriptor.layouts[1] = MDLVertexBufferLayout(stride: MemoryLayout<float2>.stride)

当您稍后调用 addNormals(...) 时, 模型 I/O 将分配必要的空间并更新顶点描述符以反射(reflect)新数据。由于您不是从数据渲染而是立即导出数据,因此它为法线选择的内部布局并不重要。

A correctly exported ARKit face mesh

关于swift - 将 ARFaceGeometry 保存到 OBJ 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59475201/

相关文章:

swift - 透明 SCNFloor() 上的 SceneKit 阴影

ios - 将位置和天气信息添加到 HealthKit Workout Session

ios - Swift:自定义单元格中的 IBoutlets 为零

ios - 在 SceneKit 上画一条线在设备上不起作用

Swift SceneKit 物理变得疯狂

swift - 如何在 SwiftUI 应用程序中保存和加载 ARWorldMap?

ios - 使用Swift/Firebase/Microsoft处理登录

ios - 我可以开发一个所有元素都用WebKit开发的App吗?

SceneKit:理解SCNNode的pivot属性

ios - 在 iOS 中使用相机检测对象并使用 ARKit 定位 3D 对象