MDLMesh based on your own vertex data

As written in the docs for MDLMesh: "Typically, you obtain meshes by traversing the object hierarchy of a

MDLAsset
object, but you can also create meshes from your own vertex data or create parametric meshes"


The bold part is what I aim to do, create meshes from my own vertex data. Specifically I have a half-edge datastructure that is updated frequently (though not every frame). I currently use geometryWithSources to generate a SCNGeometry object and assign that to an SCNNode. I want to utilize the normal generation and subdivision and file export options etc from ModelIO. I can load the SCNGeometry into an MDLMesh, create another one based on that using initMeshBySubdividingMesh and then generate normals and then turn it into SCNGeometry again. Aside from the memory leaks in subdivision (another topic...) that works, but it's obviously not efficient and affects performance negavtively.


During rebuilding of the geometry (based on the half edge data structure's data) I end up with basic arrays for vertex positions and indices and colors. I then use geometrySourceWithVertices to create the geometry source, and then use geometrySourceWithData to create the SCNGeometryElement from the incides and another SCNGeometrySource for the color data (on a side note it appears there could or should be a geometrySourceWithColors too).


The few code samples I could find cover at best how to create an MDLMesh from an asset, I would very much appreciate an (obj c) sample of how to use MDLMesh with programmatically generated vertex data instead. Specifcally it seems nobody (as a matter of speaking, based on google search) uses initWithVertexBuffer of MDLMesh.


Long story short, how can I go from SCNVector vertexpos[] and int indices[] (currently stored as described here: https://developer.apple.com/documentation/scenekit/scngeometryprimitivetype/scngeometryprimitivetypepolygon z) to an MDLMesh directly.

To put it in yet a different way, I'd like to see some more examples/sample code from Apple (or anyone else of course) on creating and rendering (in scenekit or metal) an MDLMesh not loaded from an asset like the fighter jet but from vertex data for a simple cube or 4-vertex plane even.

Accepted Reply

Hi,


I used initWithVertex data for a plane mesh that I generated programatically. However I was *not* able to get the normal generation working, I ended up having to generate my normals manually as before so I am not sure how useful it is really, probably not useful at all tbh, the only thing I like about it is it makes the renderer code a bit cleaner because all of my meshes end up being MTKMesh.


I think Model I/O is meant to read from a obj file directly then that way it has full control over how the buffers are created on the device.


If you still want to do it try to follow these steps and let me know if you have any problems.


1. Create the MTLVertexDescriptor object that describes how your programatically generated verticies and normals are laid out in memory

2. Use this object to initialize the MDLVertexDescriptor object via MTKModelIOVertexDescriptorFromMetal

3. Create your position vertex, normal, and index buffers like so:


        MTKMeshBufferAllocator * allocator = [[MTKMeshBufferAllocator alloc] initWithDevice:device];
        NSData * vertexData = [NSData dataWithBytesNoCopy:_positions length:sizeof(vector_float3) * plane.vertexCount];
        MTKMeshBuffer * vertexBuffer = (MTKMeshBuffer *)[allocator newBufferWithData:vertexData type:MDLMeshBufferTypeVertex];
     
        NSData * normalData = [NSData dataWithBytesNoCopy:_normals length:normalBufferLength];
        MTKMeshBuffer * normalBuffer = (MTKMeshBuffer *)[allocator newBufferWithData:normalData type:MDLMeshBufferTypeVertex];
     
        NSData * indexData = [NSData dataWithBytesNoCopy:_triangleStripIndicies length:sizeof(UInt16) * plane.indexCount];
        MTKMeshBuffer * indexBuffer = (MTKMeshBuffer *)[allocator newBufferWithData:indexData type:MDLMeshBufferTypeIndex];


4. Initialize your submesh with the index buffer: [[MDLSubmesh alloc] initWithIndexBuffer:indexBuffer indexCount:plane.indexCount indexType:MDLIndexBitDepthUInt16 geometryType:MDLGeometryTypeTriangleStrips material:nil];


5. Initialize and return your parent mesh object: mesh = [[MDLMesh alloc] initWithVertexBuffers:@[vertexBuffer, normalBuffer] vertexCount:plane.vertexCount descriptor:mdlVertexDescriptor submeshes:@[submesh]];

Replies

Hi,


I used initWithVertex data for a plane mesh that I generated programatically. However I was *not* able to get the normal generation working, I ended up having to generate my normals manually as before so I am not sure how useful it is really, probably not useful at all tbh, the only thing I like about it is it makes the renderer code a bit cleaner because all of my meshes end up being MTKMesh.


I think Model I/O is meant to read from a obj file directly then that way it has full control over how the buffers are created on the device.


If you still want to do it try to follow these steps and let me know if you have any problems.


1. Create the MTLVertexDescriptor object that describes how your programatically generated verticies and normals are laid out in memory

2. Use this object to initialize the MDLVertexDescriptor object via MTKModelIOVertexDescriptorFromMetal

3. Create your position vertex, normal, and index buffers like so:


        MTKMeshBufferAllocator * allocator = [[MTKMeshBufferAllocator alloc] initWithDevice:device];
        NSData * vertexData = [NSData dataWithBytesNoCopy:_positions length:sizeof(vector_float3) * plane.vertexCount];
        MTKMeshBuffer * vertexBuffer = (MTKMeshBuffer *)[allocator newBufferWithData:vertexData type:MDLMeshBufferTypeVertex];
     
        NSData * normalData = [NSData dataWithBytesNoCopy:_normals length:normalBufferLength];
        MTKMeshBuffer * normalBuffer = (MTKMeshBuffer *)[allocator newBufferWithData:normalData type:MDLMeshBufferTypeVertex];
     
        NSData * indexData = [NSData dataWithBytesNoCopy:_triangleStripIndicies length:sizeof(UInt16) * plane.indexCount];
        MTKMeshBuffer * indexBuffer = (MTKMeshBuffer *)[allocator newBufferWithData:indexData type:MDLMeshBufferTypeIndex];


4. Initialize your submesh with the index buffer: [[MDLSubmesh alloc] initWithIndexBuffer:indexBuffer indexCount:plane.indexCount indexType:MDLIndexBitDepthUInt16 geometryType:MDLGeometryTypeTriangleStrips material:nil];


5. Initialize and return your parent mesh object: mesh = [[MDLMesh alloc] initWithVertexBuffers:@[vertexBuffer, normalBuffer] vertexCount:plane.vertexCount descriptor:mdlVertexDescriptor submeshes:@[submesh]];

Hey, I no longer have the situation I described in the post above, but I do appreciate the reply.


I was reminded of this thread after reading another example basically answering the same question at https://stackoverflow.com/questions/46804603/programmatic-generation-of-mdlmesh-objects-using-initwithvertexbuffers/46817825#46817825

I also encountered the same problem, but from