3D Graphics with Metal · Render a Model | raywenderlich.com


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/1258241-3d-graphics-with-metal/lessons/11
1 Like

Hi,

Could you please help me understand how the loop statement from the video handles obj files with more than one vertexBuffer? I’m stuck trying to understand how it handles a situation like below:

Situation: what happens if there were two vertexBuffers, the loop is currently working through the first vertexBuffer, and while we are looping through each submesh, we encounter a submesh that leverages the second vertexBuffer but of course the draw call is called nonetheless.

Thanks!

@codinghobbies - I’m not quite sure what I was thinking when wrapping the Submesh loop inside the vertex buffer loop. In this case, where we’re using Model I/O and vertex descriptors, this is actually an error. Thank you for pointing it out.

In this situation, where you’re importing obj files and there’s only one vertex buffer, it won’t actually make a difference.

The vertex descriptor describes the layout. Position is going into buffer[0]. If you’re going to have Color, you might decide to place that attribute in buffer[1], and in that case, the for loop as I have it, will crash.

Let me add some code for that, to explain further. You can put this code into that project and experiment.

This changes the vertex descriptor in Extensions.swift to add a color attribute:

extension MTLVertexDescriptor {
  static func defaultVertexDescriptor() -> MTLVertexDescriptor {
    let vertexDescriptor = MTLVertexDescriptor()
    vertexDescriptor.attributes[0].format = .float3
    vertexDescriptor.attributes[0].offset = 0
    vertexDescriptor.attributes[0].bufferIndex = 0
    
    vertexDescriptor.layouts[0].stride = MemoryLayout<float3>.stride
    
    vertexDescriptor.attributes[1].format = .float3
    vertexDescriptor.attributes[1].offset = 0
    vertexDescriptor.attributes[1].bufferIndex = 1
    vertexDescriptor.layouts[1].stride = MemoryLayout<float3>.stride
    return vertexDescriptor
  }
}

extension MDLVertexDescriptor {
  static func defaultVertexDescriptor() -> MDLVertexDescriptor {
    let vertexDescriptor = MTKModelIOVertexDescriptorFromMetal(.defaultVertexDescriptor())
    
    let attributePosition = vertexDescriptor.attributes[0] as! MDLVertexAttribute
    attributePosition.name = MDLVertexAttributePosition
    let attributeColor = vertexDescriptor.attributes[1] as! MDLVertexAttribute
    attributeColor.name = MDLVertexAttributeColor
    
    return vertexDescriptor
  }
}

The shader structure:

struct VertexIn {
  float4 position [[attribute(0)]];
  float3 color [[attribute(1)]];
};

struct VertexOut {
  float4 position [[position]];
  float3 color;
};

vertex VertexOut vertex_main(VertexIn vertexBuffer [[stage_in]]) {
  VertexOut out {
    .position = vertexBuffer.position,
    .color = vertexBuffer.color
  };
  out.position.y -= 0.5;
  return out;
}

The code as I have written it in Renderer will now crash :woman_facepalming:.

But change the loop to this in Renderer:

for mtkMesh in train.mtkMeshes {
  for (index, vertexBuffer) in mtkMesh.vertexBuffers.enumerated() {
    commandEncoder.setVertexBuffer(vertexBuffer.buffer, offset:0, index: index)
  }
  for submesh in mtkMesh.submeshes {
    // draw call
    commandEncoder.drawIndexedPrimitives(type: .triangle,
                                         indexCount: submesh.indexCount,
                                         indexType: submesh.indexType,
                                         indexBuffer: submesh.indexBuffer.buffer,
                                         indexBufferOffset: submesh.indexBuffer.offset)
  }
}   

Now we’re setting all the vertex buffers onto the GPU at once with different buffer indices, and the submeshes index into the correct vertex buffer to extract position and color.

The train will render in black.

2 Likes

Worked like a charm! So by the way we set up the vertexDescriptor here, can I assume that in all obj files, the ‘mtkMesh.vertexBuffers.enumerated()’ will serve the position buffer first and the color buffer second always?

And as an extension of that, does this order for buffers change with other 3d files such as fbx or does it stay the same?

Oh good :slight_smile:

In Model.swift, you read in the file like this:

let vertexDescriptor = MDLVertexDescriptor.defaultVertexDescriptor()
let asset = MDLAsset(url: assetUrl,
                     vertexDescriptor: vertexDescriptor,
                     bufferAllocator: allocator)

In Renderer.swift, you specify the pipeline vertex descriptor:

pipelineStateDescriptor.vertexDescriptor = MTLVertexDescriptor.defaultVertexDescriptor()

They are both essentially the same - one using the Model I/O vertex descriptor, and the other using the MetalKit vertex descriptor which is created from the Model I/O one.

So that’s how you’re specifying what you’re expecting in each buffer for each model. All models read in this way, no matter what format, will have two vertex buffers, the first position, and the second color. It’s Model I/O that’s responsible for allocating the vertex buffers, so you’re dependent upon the formats that Model I/O handles.

Model I/O doesn’t read fbx files, but you should be able to convert them to .usdz with the new USD tools and usdzconvert. You can download that from the bottom of this page: Quick Look Gallery - Augmented Reality - Apple Developer

You can then change the extension in Model.swift from obj to usdz and load the files in.

Warning: reading in some of the supplied sample usdz files on that page probably won’t work, as this model loader is rather naive, and the usdz files have multiple groups and meshes and skeletons and animations on them. The simpler ones like the teapot should work after you’ve done the next section and you can scale them down. They are very big.

Here’s the train as a usdz file for you to test the different format: train.zip (40.7 KB)

I’m now leaning towards usdz more than other formats, as USD is gaining traction in other 3d apps such as Houdini. Especially as usdzconvert will convert :crossed_fingers: the glTF that SketchFab has standardized on.

2 Likes

OK!! Will give it a go!

ps: I also heard that Adobe is adopting usdz formats and are working with Pixar to be able to send models in iMessages and other basic comms platforms with usdz, so it does seem like it’s certainly gaining traction!

1 Like