Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Programmatic generation of MDLMesh objects using initWithVertexBuffers

If you're using iOS Metal, and you have custom programmatic object models, and (like me) your models stopped working with the advent of Metal2 and iOS 11, then you've probably started looking into how to programmatically generate an MDLMesh.

Apple documentation says, "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." Unfortunately, it offers no instructions or sample code.

You do quickly find the twin MDLMesh initialization calls, initWithVertexBuffer, and initWithVertexBuffers. Just as quickly you find no sample code or discussion on the web... at least I was not successful finding any.

As it is not intuitively obvious to this casual observer how that should be done, code samples are herewith requested.

like image 584
Mike Roberts Avatar asked Oct 18 '17 07:10

Mike Roberts


1 Answers

There are plenty of examples creating an MDLMesh using one of the factory parametric methods for e.g., a cube:

[MDLMesh newBoxWithDimensions:...

Using the most simple of these, for a "plane" (a rectangle), I generated a 1x1 rect with the minimum number of vertices:

MDLMesh *mdlMesh = [MDLMesh newPlaneWithDimensions:(vector_float2){1.0, 1.0}
                                          segments:(vector_uint2){1, 1}
                                      geometryType:MDLGeometryTypeTriangles
                                         allocator:metalAllocator];

I then used the Xcode debugger to investigate what the resulting MDLMesh looked like, as a way to guide my creation of an even simpler object, a programmatic equilateral triangle.

The following code works for me. I'm sure folks with more Metal savvy than me can offer better solutions. But this will hopefully get you started, in some semblance of the right direction...

So until there is a new factory parametric method for

[MDLMesh newEquilateralTriangleWithEdgeLength:...

the following code seems to do the trick...

static const float equilateralTriangleVertexData[] =
{
      0.000000,  0.577350,  0.0,
     -0.500000, -0.288675,  0.0,
      0.500000, -0.288675,  0.0,
};

static const vector_float3 equilateralTriangleVertexNormalsData[] =
{
    { 0.0,  0.0,  1.0 },
    { 0.0,  0.0,  1.0 },
    { 0.0,  0.0,  1.0 },
};

static const vector_float2 equilateralTriangleVertexTexData[] =
{
    { 0.50, 1.00 },
    { 0.00, 0.00 },
    { 1.00, 0.00 },
};

int numVertices = 3;

int lenBufferForVertices_position          = sizeof(equilateralTriangleVertexData);
int lenBufferForVertices_normal            = numVertices * sizeof(vector_float3);
int lenBufferForVertices_textureCoordinate = numVertices * sizeof(vector_float2);

MTKMeshBuffer *mtkMeshBufferForVertices_position          = (MTKMeshBuffer *)[metalAllocator newBuffer:lenBufferForVertices_position          type:MDLMeshBufferTypeVertex];
MTKMeshBuffer *mtkMeshBufferForVertices_normal            = (MTKMeshBuffer *)[metalAllocator newBuffer:lenBufferForVertices_normal            type:MDLMeshBufferTypeVertex];
MTKMeshBuffer *mtkMeshBufferForVertices_textureCoordinate = (MTKMeshBuffer *)[metalAllocator newBuffer:lenBufferForVertices_textureCoordinate type:MDLMeshBufferTypeVertex];

// Now fill the Vertex buffers with vertices.

NSData *nsData_position          = [NSData dataWithBytes:equilateralTriangleVertexData        length:lenBufferForVertices_position];
NSData *nsData_normal            = [NSData dataWithBytes:equilateralTriangleVertexNormalsData length:lenBufferForVertices_normal];
NSData *nsData_textureCoordinate = [NSData dataWithBytes:equilateralTriangleVertexTexData     length:lenBufferForVertices_textureCoordinate];

[mtkMeshBufferForVertices_position          fillData:nsData_position          offset:0];
[mtkMeshBufferForVertices_normal            fillData:nsData_normal            offset:0];
[mtkMeshBufferForVertices_textureCoordinate fillData:nsData_textureCoordinate offset:0];

NSArray <id<MDLMeshBuffer>> *arrayOfMeshBuffers = [NSArray arrayWithObjects:mtkMeshBufferForVertices_position, mtkMeshBufferForVertices_normal, mtkMeshBufferForVertices_textureCoordinate, nil];

static uint16_t indices[] =
{
    0,  1,  2,
};

int numIndices = 3;

int lenBufferForIndices = numIndices * sizeof(uint16_t);
MTKMeshBuffer *mtkMeshBufferForIndices = (MTKMeshBuffer *)[metalAllocator newBuffer:lenBufferForIndices type:MDLMeshBufferTypeIndex];

NSData *nsData_indices = [NSData dataWithBytes:indices length:lenBufferForIndices];
[mtkMeshBufferForIndices fillData:nsData_indices offset:0];

MDLScatteringFunction *scatteringFunction = [MDLPhysicallyPlausibleScatteringFunction new];
MDLMaterial *material = [[MDLMaterial alloc] initWithName:@"plausibleMaterial" scatteringFunction:scatteringFunction];

// Not allowed to create an MTKSubmesh directly, so feed an MDLSubmesh to an MDLMesh, and then use that to load an MTKMesh, which makes the MTKSubmesh from it.
MDLSubmesh *submesh = [[MDLSubmesh alloc] initWithName:@"summess" // Hackspeke for @"submesh"
                                           indexBuffer:mtkMeshBufferForIndices 
                                            indexCount:numIndices 
                                             indexType:MDLIndexBitDepthUInt16 
                                          geometryType:MDLGeometryTypeTriangles 
                                              material:material];

NSArray <MDLSubmesh *> *arrayOfSubmeshes = [NSArray arrayWithObjects:submesh, nil];

MDLMesh *mdlMesh = [[MDLMesh alloc] initWithVertexBuffers:arrayOfMeshBuffers
                                              vertexCount:numVertices
                                               descriptor:mdlVertexDescriptor
                                                submeshes:arrayOfSubmeshes];
like image 115
Mike Roberts Avatar answered Sep 20 '22 10:09

Mike Roberts