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:
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.
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?
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.
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.