将ARFaceGeometry保存为OBJ文件

9
在iOS ARKit应用中,我一直在尝试将ARFaceGeometry数据保存到OBJ文件中。我按照这里的解释进行操作:如何从AVDepthData创建3D模型?。然而,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://dev59.com/Oq_la4cB1Zd3GeqPq06k
        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

我期望有更多的顶点,而且索引值看起来不正确。


你能否将面部几何数据导出为obj文件。我得到的子网格未初始化,因此它会崩溃。请问你能指导我如何导出这些数据吗? - Chaitu
@Daniel 有运气吗? - Mayank Jain
在你想知道Data(fromArray: ..)是从哪里来的时候,可以参考这个链接:https://github.com/opentok/ARFrameMetadata/blob/master/ARFrameMetadata/Data%2BfromArray.swift - Balazs Banyai
1个回答

9

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

最简单的解决方法是修复顶点描述符以反映您提供的数据:

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(...)时,Model I/O将分配必要的空间并更新顶点描述符以反映新数据。由于您不是从数据进行渲染,而是立即导出它,因此它选择法线的内部布局并不重要。 正确导出的ARKit面部网格

请问,我们能否捕捉一系列面部表情并将其导出到文件中,然后在使用某些面部网格加载此文件时复制相同的表情?如果可以的话,您能否指导我如何实现这个功能? - Chaitu
1
当然可以。只需在您的会话代理中实现 session:didUpdateAnchors:,获取面部锚点,并使用上述技术每帧编写一个模型文件。您可能希望使用更节省空间的格式,如USDC或Alembic,因为OBJ非常冗长。 - warrenm
非常感谢您的建议。我已经成功将一系列面部Anchor转换为usdc格式。我们能否将这些系列分组为usdz并播放动画? - Chaitu
ARFaceGeometry没有任何纹理或材质属性。你的意思是要导出属于ARSCNFaceGeometry实例的材质属性的纹理吗?请考虑提出一个更详细的单独问题。 - warrenm
您可以在 MDLMesh 上设置 vertexDescriptor 属性以更改其属性和布局。这可能是一个昂贵的操作,因为它可能会在内部分配新的缓冲区,但这是一种简单的方法来“重塑”顶点数据以满足您的应用程序需求。 - warrenm
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接