Creating a Model Object With a Parametric Mesh

I am getting a sense of the realism that can be created with PBR shading. To explore it properly, I want to properly follow the approach in the Maps and Materials chapter of the book - the Model object containing an array of Submesh objects which in turn contain material information.

There are times when it would be helpful to try a set of assets on a simple parametric mesh - A sphere or a plane perhaps. I have tried to adapt the initializer of the Model object with something like:

let allocator = MTKMeshBufferAllocator(device: device)

let mdlMesh = MDLMesh(sphereWithExtent: [0.5, 0.5, 0.5],
segments: [40, 40],
inwardNormals: false,
geometryType: .triangles,
allocator: allocator)

let mesh = try MTKMesh(mesh: mdlMesh, device: device)

etc.

This will not execute properly and it seems that the single Submesh I was hoping for doesn’t get properly initialized later in the function.

Could there be a way to successfully load a parametric mesh into a Model object and still keep the Renderer happy?

Thanks very much!,
I continue to enjoy all of the concepts presented in the book,
John Lobe

Hi John

When you say it doesn’t execute properly, what happens? I copied your code into Model.swift and it worked for me in Chapter 7 final project code (after fixing the unfortunate typedef problem in Common.h - if you are getting a segmentation fault, that’s the reason. See here for the fix).

49%20pm

Having said that, you might find it hard to adjust material properties with the primitive. I cheat and export from Blender. You can create primitives cubes and cones and spheres there and export them as objs. You can then edit the .mtl file in Xcode to adjust material properties more easily.

The render won’t look great until you introduce reflection and IBL (Image Based Lighting) in Chapter 12, “Environment”.

The best link for understanding PBR is Substance becomes Adobe Substance 3D

P. S. There is a correction to chapter 7 which will hopefully appear in the next edition. If you’re not using maps, roughness and metallic need to be defined in the .mtl file as three floats. In Submesh.swift where you read the roughness and metallic properties, you check for a float3 and then use floatValue as before. Only the first of the three floats is used, but Model I/O won’t recognise the roughness property without it being a float3.

Full init(name:):

  init(name: String) {
    let allocator = MTKMeshBufferAllocator(device: Renderer.device)
    let mdlMesh = MDLMesh(sphereWithExtent: [0.5, 0.5, 0.5],
                          segments: [40, 40],
                          inwardNormals: false,
                          geometryType: .triangles,
                          allocator: allocator)
    mdlMesh.addTangentBasis(forTextureCoordinateAttributeNamed:
      MDLVertexAttributeTextureCoordinate,
                            tangentAttributeNamed: MDLVertexAttributeTangent,
                            bitangentAttributeNamed:
      MDLVertexAttributeBitangent)
    Model.defaultVertexDescriptor = mdlMesh.vertexDescriptor
    let mesh = try! MTKMesh(mesh: mdlMesh, device: Renderer.device)
    self.mesh = mesh
    vertexBuffer = mesh.vertexBuffers[0].buffer
    submeshes = mdlMesh.submeshes?.enumerated().compactMap {index, submesh in
      (submesh as? MDLSubmesh).map {
        Submesh(submesh: mesh.submeshes[index],
                mdlSubmesh: $0)
      }
      }
      ?? []
    samplerState = Model.buildSamplerState()
    super.init()
  }

Caroline, thanks for your thoughts and the tweaked Model.init. I’m afraid that I may have botched up some of the code there or within the Renderer methods while trying things - Then I associated the problems with using the primitive meshes. My draws were mostly generating errors :frowning:

I do understand what you’ve explained about starting with Blender and sticking to the process of bringing in obj files with their assets properly organized. I have been throwing hard coded textures onto entire meshes, but as the lighting has gotten more sophisticated, that kind of goofing around won’t keep up with the textures and materials needed.

Again, thanks so much for your help,
The thought that has gone into the Metal by Tutorials book is really incredible to me,
John Lobe

1 Like

Thank you very much for the kind words - we’re really enjoying putting the book together :smiley:

I think I may have an actual use case for loading a mesh programmatically like the initial question asked. It seems that he found a way to do what he wanted using the built-in meshes.

A little background:
I am trying to port an app I wrote 12 years ago using Objective-C and OpenGLES1 (yes, no shaders) to Swift/Metal. The app uses various polyhedra whose mesh information is stored in a database. Things have sure come a long way since then.

I can of course readily convert the database information into an OBJ that ModelKit can load. The problem is, I need control over every polygon in the polyhedron. There over 100 polygons in some polyhedra. My first thought was to assign each polygon to a different sub-mesh. So, I added g 1, g 2, etc. after every line of indices. This works if there are, say, 5 faces. Going up to 10 faces caused the app to use more than 2GB of RAM. Any more and it simply will not run. Plus, it takes a very long time to load the model.

Thus, I think I am going to have to ditch the overhead of loading each polygon as a separate submesh and do things programmatically as I did before. However, getting the data into the Metal buffers without ModelKit seems like something that isn’t really done as I cannot find any examples of this. I have tried various things to reconstruct what MDLAsset does with little success.

I would be grateful for any suggestions about how to do this, or other ways of doing it.

Thank you for the great book and tutorials. They have made my transition from ObjC/OpenGL to Swift/Metal quite smooth.

Eric

@ericmock - there’s just been some discussion about creating mesh without going through Model I/O.

I created a project that just builds a cube (but obviously the eight points of a cube could be any number of points) here: Face normal vs vertex normal - #9 by caroline.

We were using color as an example, but that could also be any attribute. The second example (without the submesh) should be the one you want. Just set up the buffers and the vertex structs to match, and you should be fine :slight_smile:

Model I/O is really only to make loading models made in 3d apps easier for us. If you’re not doing that, then it will only add complexity.