Сохранить ARFaceGeometry в файл OBJ

В приложении iOS ARKit я пытался сохранить ARFaceGeometryданные в файл OBJ. Я следил за объяснением здесь: Как сделать 3D-модель из 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://stackru.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

Я ожидал бы большего количества вершин, и значения индекса выглядели бы неверно.

1 ответ

Решение

Основная проблема в том, что данные о ваших вершинах описаны неправильно. Когда вы предоставляете дескриптор вершины для ввода-вывода модели при построении сетки, он представляет фактический макет данных, а не желаемый макет. Вы предоставляете два буфера вершин, но ваш дескриптор вершин описывает чередующийся макет данных только с одним буфером вершин.

Самый простой способ исправить это - исправить дескриптор вершины, чтобы он отражал данные, которые вы предоставляете:

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 выделит необходимое пространство и обновит дескриптор вершины, чтобы отразить новые данные. Поскольку вы не выполняете рендеринг из данных, а вместо этого сразу их экспортируете, внутренний макет, который он выбирает для нормалей, не важен.

Другие вопросы по тегам