Search code examples
iosswiftmetalmetalkit

Indexed drawing with metal


I am trying to load a model (form .OBJ) and draw it to the screen on iOS with MetalKit. The problem is that instead of my model, I get some random polygons... Here is the code that is tend to load the model(The code is based on a tutorial from raywenderlich.com:

        let allocator               = MTKMeshBufferAllocator(device: device)
        let vertexDescriptor        = MDLVertexDescriptor()
        let vertexLayout            = MDLVertexBufferLayout()
        vertexLayout.stride         = sizeof(Vertex)
        vertexDescriptor.layouts    = [vertexLayout]
        vertexDescriptor.attributes = [MDLVertexAttribute(name: MDLVertexAttributePosition, format: MDLVertexFormat.Float3, offset: 0, bufferIndex: 0),
                                       MDLVertexAttribute(name: MDLVertexAttributeColor, format: MDLVertexFormat.Float4, offset: sizeof(float3), bufferIndex: 0),
                                       MDLVertexAttribute(name: MDLVertexAttributeTextureCoordinate, format: MDLVertexFormat.Float2, offset: sizeof(float3)+sizeof(float4), bufferIndex: 0),
                                       MDLVertexAttribute(name: MDLVertexAttributeNormal, format: MDLVertexFormat.Float3, offset: sizeof(float3)+sizeof(float4)+sizeof(float2), bufferIndex: 0)]
        var error: NSError?
        let asset = MDLAsset(URL: path, vertexDescriptor: vertexDescriptor, bufferAllocator: allocator, preserveTopology: true, error: &error)
        if error != nil{
            print(error)
            return nil
        }

        let model = asset.objectAtIndex(0) as! MDLMesh
        let mesh  = try MTKMesh(mesh: model, device: device)

And here is my drawing method:

    func render(commandQueue: MTLCommandQueue, pipelineState: MTLRenderPipelineState,drawable: CAMetalDrawable,projectionMatrix: float4x4,modelViewMatrix: float4x4, clearColor: MTLClearColor){

    dispatch_semaphore_wait(bufferProvider.availibleResourcesSemaphore, DISPATCH_TIME_FOREVER)

    let renderPassDescriptor = MTLRenderPassDescriptor()
    renderPassDescriptor.colorAttachments[0].texture = drawable.texture
    renderPassDescriptor.colorAttachments[0].loadAction = .Clear
    renderPassDescriptor.colorAttachments[0].clearColor = clearColor
    renderPassDescriptor.colorAttachments[0].storeAction = .Store

    let commandBuffer = commandQueue.commandBuffer()
    commandBuffer.addCompletedHandler { (buffer) in
        dispatch_semaphore_signal(self.bufferProvider.availibleResourcesSemaphore)
    }

    let renderEncoder = commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor)
    renderEncoder.setCullMode(MTLCullMode.None)

    renderEncoder.setRenderPipelineState(pipelineState)
    renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, atIndex: 0)
    renderEncoder.setFragmentTexture(texture, atIndex: 0)
    if let samplerState = samplerState{
        renderEncoder.setFragmentSamplerState(samplerState, atIndex: 0)
    }


    var nodeModelMatrix = self.modelMatrix()
    nodeModelMatrix.multiplyLeft(modelViewMatrix)
    uniformBuffer = bufferProvider.nextUniformsBuffer(projectionMatrix, modelViewMatrix: nodeModelMatrix, light: light)
    renderEncoder.setVertexBuffer(self.uniformBuffer, offset: 0, atIndex: 1)
    renderEncoder.setFragmentBuffer(uniformBuffer, offset: 0, atIndex: 1)
    if indexBuffer != nil{
        renderEncoder.drawIndexedPrimitives(.Triangle, indexCount: self.indexCount, indexType: self.indexType, indexBuffer: self.indexBuffer!, indexBufferOffset: 0)
    }else{
        renderEncoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: vertexCount, instanceCount: vertexCount/3)
    }
    renderEncoder.endEncoding()

    commandBuffer.presentDrawable(drawable)
    commandBuffer.commit()
}

Here is my vertex shader:

struct VertexIn{
   packed_float3 position;
   packed_float4 color;
   packed_float2 texCoord;
   packed_float3 normal;
};

struct VertexOut{
    float4 position [[position]];
    float3 fragmentPosition;
    float4 color;
    float2 texCoord;
    float3 normal;
};

struct Light{
    packed_float3 color;
    float ambientIntensity;
    packed_float3 direction;
    float diffuseIntensity;
    float shininess;
    float specularIntensity;
};

struct Uniforms{
    float4x4 modelMatrix;
    float4x4 projectionMatrix;
    Light light;
};

vertex VertexOut basic_vertex(
                          const device VertexIn* vertex_array [[ buffer(0) ]],
                          const device Uniforms&  uniforms    [[ buffer(1) ]],
                          unsigned int vid [[ vertex_id ]]) {

   float4x4 mv_Matrix = uniforms.modelMatrix;
   float4x4 proj_Matrix = uniforms.projectionMatrix;

    VertexIn VertexIn = vertex_array[vid];

    VertexOut VertexOut;
    VertexOut.position = proj_Matrix * mv_Matrix * float4(VertexIn.position,1);
    VertexOut.fragmentPosition = (mv_Matrix * float4(VertexIn.position,1)).xyz;
    VertexOut.color = VertexIn.color;
    VertexOut.texCoord = VertexIn.texCoord;
    VertexOut.normal = (mv_Matrix * float4(VertexIn.normal, 0.0)).xyz;
    return VertexOut;
}

And here is how it looks like:

link

Actually I have an other class that is completely written by me to load models. It works fine, the problem is that it is not using indexing so f I try to load models that are more complex than a low-poly sphere, the GPU crashes... Anyways I tried to modify it to use indexing and I got the same result.. than I added hardcoded indices for testing and I got a really weird result. When I had 3 indices it drew a triangle, when I added 3 more, it drew the same triangle and after 3 more vertices it drew 2 triangles...

Edit: Here is my Vertex structure:

struct Vertex:Equatable{
    var x,y,z: Float
    var r,g,b,a: Float
    var s,t: Float
    var nX,nY,nZ:Float

    func floatBuffer()->[Float]{
         return [x,y,z,r,g,b,a,s,t,nX,nY,nZ]
    }
}

Solution

  • I see a couple of potential issues here.

    1) Your vertex descriptor does not map exactly to your Vertex struct. The position variables (x, y, z) occupy 12 bytes, so the color variables start at an offset of 12 bytes. This matches the packed_float3 position field in your shader's VertexIn struct, but in the vertex descriptor you provide to Model I/O, you use sizeof(Float3), which is 16, as the offset of the color attribute. Because you're packing the position field, you should use sizeof(Float) * 3 for this value instead, and likewise in the subsequent offsets. I suspect this is the main cause of your problems.

    More generally, it's a good idea to use strideof rather than sizeof to account for alignment, though--by chance--it wouldn't make a difference here.

    2) Model I/O is allowed to use a single MTLBuffer to store both vertices and indices, so you should use the offset member of each MTKMeshBuffer when setting the vertex buffer or specifying the index buffer in each draw call, rather than assuming the offsets to be 0.