Search code examples
swift.objmetalmetalkit

ModelIO Framework Not Working


I'm trying to import 3D model files and render them with Metal using ModelIO and MetalKit (on OS X 10.11), but the behavior I'm seeing from these frameworks (specifically ModelIO) is not as expected.

I can import .obj files and convert them to MetalKit meshes without causing any errors, but the mesh (at least under rendering), seems to just be a large fan of triangles all emanating from a single point. The model in the screenshot below is supposed to be a subdivided version of the "Suzanne" monkey head: Incorrectly imported .obj file

On inspection, the vertex indices of the MDLSubmesh from the imported file don't make any sense. Successive sets of indices keep referring to the vertex at index 0, sometimes multiple times in the same set of indices, which would explain the appearance during rendering. I have confirmed that this .obj file imports fine into other applications.

I have tried importing other 3D file formats (all officially supported by the framework), but any formats other than .obj cause an uncaught NSException on the call to MDLAsset's init().

I'm working with Xcode 7.2 and targeting OS X 10.11.


Solution

  • I've been also experiencing similar issues, although I'm a rookie in Metal, I figured a few things out.

    I was trying to import the Melita teapot, but I was also having an "explosion" of faces, instead of the iconic tea-brewing device. The solution came to me after reading the documentation of MDLVertexBufferLayout, which reads:

    A mesh may store vertex data in either a structure of arrays model, where data for each vertex attribute (such as vertex position or surface normal) lies in a separate vertex buffer, or in an array of structures model, where multiple vertex attributes share the same buffer.

    • In a structure of arrays, the mesh’s vertexBuffers array contains several MDLMeshBuffer objects, and the mesh’s vertexDescriptor object contains a separate MDLVertexBufferLayout object for each buffer.

    • In an array of structures, the mesh contains a single vertex buffer, and its descriptor contains a single vertex buffer layout object. To identify which bytes in the buffer refer to which vertices and vertex attributes, use the layout’s stride together with the format and offset properties of the descriptor’s vertex attributes.

    By looking at the .layouts and .attributes properties of the default implementation of the MDLVertexDescriptor, they are creating one buffer for each attribute type (like in the quote above, the first case), where I wanted to use the intermixed mode.

    I manually set up the .layouts and .attributes with my own arrays and then, voila I got... half a melita pot?

    Half-baked implementation... get it!?

    class func setup(meshWithDevice device: MTLDevice) -> MTKMesh
    {
        // Allocator
        let allocator = MTKMeshBufferAllocator(device: device)
    
        // Vertex Descriptor, tells the MDLAsset how to layout the buffers
        let vertexDescriptor = MDLVertexDescriptor()
    
        // Vertex Buffer Layout, tells how many buffers will be used, and the stride of its structs
        // (the init(stide: Int) crashes in the Beta)
        let vertexLayout = MDLVertexBufferLayout()
        vertexLayout.stride = MemoryLayout<Vertex>.size
    
        // Apply the Layouts
        vertexDescriptor.layouts = [vertexLayout]
    
        // Apply the attributes, in my case, position and normal (float4 x2)
        vertexDescriptor.attributes =
        [
            MDLVertexAttribute(name: MDLVertexAttributePosition, format: MDLVertexFormat.float4, offset: 0, bufferIndex: 0),
            MDLVertexAttribute(name: MDLVertexAttributeNormal, format: MDLVertexFormat.float4, offset: MemoryLayout<float4>.size, bufferIndex: 0)
        ]
    
        var error : NSError? = nil
    
        // Load the teapot
        let asset = MDLAsset(url: Bundle.main.url(forResource: "teapot", withExtension: "obj")!, vertexDescriptor: vertexDescriptor, bufferAllocator: allocator, preserveTopology: true, error: &error)
    
        if let error = error
        {
            print(error)
        }
    
        // Obtain the teapot Mesh
        let teapotModel = asset.object(at: 0) as! MDLMesh
    
        // Convert into MetalKit Mesh, insted of ModelIO
        let teapot = try! MTKMesh(mesh: teapotModel, device: device)
    
        return teapot
    }
    

    (Swift 3.0 in XCode 8 Beta 6)

    I'll update my post if I manage to render the whole thing.

    Edit: Works Now

    Holy shi---

    Whelp, the bug was on my end, I was wrong in the Index count:

    //// Buffers
    renderPass.setVertexBuffer(mesh.vertexBuffers[0].buffer, offset: 0, at: 0)
    renderPass.setVertexBuffer(uniformBuffer, at: 1)
    
    let submesh   = mesh.submeshes[0]
    let indexSize = submesh.indexType == .uInt32 ? 4 : 2
    
    //// Draw Indices
    renderPass.drawIndexedPrimitives(submesh.primitiveType,
                                     indexCount:  submesh.indexBuffer.length / indexSize,
                                     indexType:   submesh.indexType,
                                     indexBuffer: submesh.indexBuffer.buffer,
                                     indexBufferOffset: 0)
    

    The problem was with let indexSize = submesh.indexType == .uInt32 ? 4 : 2, before I was doing 32 : 16 on the right side, but the .length property comes in Bytes not bits, so dumb.

    Anyway, I managed to load an Obj file with Metal, so the problem is either: the one I mentioned above of the individual buffering per attribute, or an entirely different issue in your code.