Categories

# Using MetalKit part 9

I bet many of you missed the `MetalKit` series, so today we are returning back to it, and we will learn how to draw 3D content in `Metal`. Let’s continue working on our playground and pick up where we left off in part 8 of the series.

We will render a 3D cube by the end of this episode but first let’s draw a 2D square and then we can re-use the square logic for all the other faces of the cube. Let’s modify the `vertex_data` array so that it holds 4 vertices instead of 3 we needed for a triangle:

```let vertex_data = [
Vertex(pos: [-1.0, -1.0, 0.0,  1.0], col: [1, 0, 0, 1]),
Vertex(pos: [ 1.0, -1.0, 0.0,  1.0], col: [0, 1, 0, 1]),
Vertex(pos: [ 1.0,  1.0, 0.0,  1.0], col: [0, 0, 1, 1]),
Vertex(pos: [-1.0,  1.0, 0.0,  1.0], col: [1, 1, 1, 1])
]
```

Here comes the interesting part. Since squares and any other complex geometry is made from triangles, and since most vertices belong to 2 or more triangles, there is no need to create copies of these vertices because we have a way of reusing them via an `index buffer` that keeps track of the order in which the vertices will be used by storing each vertex index from the `vertex buffer`. So let’s create such a list of indexes:

```let index_data: [UInt16] = [
0, 1, 2, 2, 3, 0
]
```

To understand how these indexes are stored, let’s look at this image below:

So for the front face (square) we use vertices stored at positions 0 through 3 in the `vertex_buffer`. Later on we will add the other 4 vertices as well. The front face is made of two triangles. We first draw the triangle that uses vertices 01 and 2 and then we draw the triangle that uses vertices 23 and 0. Notice that two of the vertices are re-used, as expected. Also notice the drawing is done clockwise. This is the default front-facing winding order in `Metal` but it can be changed to `counterclockwise`as well.

Then, we need to create the index_buffer:

```var index_buffer: MTLBuffer!
```

Next, we need to assign the `index_data` to the `index buffer` inside the `createBuffers()`function:

```index_buffer = device!.newBufferWithBytes(index_data, length: sizeof(UInt16) * index_data.count , options: [])
```

Last, inside the `drawRect(:)` function we need to replace the `drawPrimitives` call:

```command_encoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
```

with a drawIndexedPrimitives call:

```command_encoder.drawIndexedPrimitives(.Triangle, indexCount: index_buffer.length / sizeof(UInt16), indexType: MTLIndexType.UInt16, indexBuffer: index_buffer, indexBufferOffset: 0)
```

In the main playground page, see the generated new image:

Now that we know how to draw a square, let’s see how to draw more squares!

```let vertex_data = [
Vertex(pos: [-1.0, -1.0,  1.0, 1.0], col: [1, 0, 0, 1]),
Vertex(pos: [ 1.0, -1.0,  1.0, 1.0], col: [0, 1, 0, 1]),
Vertex(pos: [ 1.0,  1.0,  1.0, 1.0], col: [0, 0, 1, 1]),
Vertex(pos: [-1.0,  1.0,  1.0, 1.0], col: [1, 1, 1, 1]),
Vertex(pos: [-1.0, -1.0, -1.0, 1.0], col: [0, 0, 1, 1]),
Vertex(pos: [ 1.0, -1.0, -1.0, 1.0], col: [1, 1, 1, 1]),
Vertex(pos: [ 1.0,  1.0, -1.0, 1.0], col: [1, 0, 0, 1]),
Vertex(pos: [-1.0,  1.0, -1.0, 1.0], col: [0, 1, 0, 1])
]
let index_data: [UInt16] = [
0, 1, 2, 2, 3, 0,   // front

1, 5, 6, 6, 2, 1,   // right

3, 2, 6, 6, 7, 3,   // top

4, 5, 1, 1, 0, 4,   // bottom

4, 0, 3, 3, 7, 4,   // left

7, 6, 5, 5, 4, 7,   // back

]
```

Now that we have the entire cube geometry ready for rendering, let’s go to `MathUtils.swift` and in `modelMatrix()` comment out the `rotation` and the `translation` calls, and only leave the scaling on for a factor of 0.5. You will most likely see an image like this:

Hmm, but it’s still a square! Yes, it is, because we still don’t have the notion of `depth` and the cube looks just flat. It’s time to tweak some math logic now. We don’t need to use the `Matrix` struct anymore because the simd framework offers us similar data structures and math functions we can readily use. We can easily rewrite our transform functions to work with matrix_float4x4 instead of the custom `Matrix` struct we used.

But how do 3D objects end up on our 2D screens, you might ask. This process takes each pixel through a series of transformations. First the modelMatrix() transforms the pixel from `object space`to `world space`. This matrix is the one we already know, the one responsible for translations, rotations and scaling. With the newly rewritten functions above, the `modelMatrix` could look like this:

```func modelMatrix() -> matrix_float4x4 {
let scaled = scalingMatrix(0.5)
let rotatedY = rotationMatrix(Float(M_PI)/4, float3(0, 1, 0))
let rotatedX = rotationMatrix(Float(M_PI)/4, float3(1, 0, 0))
return matrix_multiply(matrix_multiply(rotatedX, rotatedY), scaled)
}
```

You notice the useful `matrix_multiply` function which we could not use before for the `Matrix`struct. Also, since all these pixels will undergo the same transformation, we want to store the matrix as a Uniform and pass it to the `vertex shader`. For this. let’s create a new struct:

```struct Uniforms {
var modelViewProjectionMatrix: matrix_float4x4
}
```

Back in the `createBuffers()` function, let’s pass the Uniforms to the shader via the buffer pointer we already used to pass the `modelMatrix`:

```let modelViewProjectionMatrix = modelMatrix()
var uniforms = Uniforms(modelViewProjectionMatrix: modelViewProjectionMatrix)
memcpy(bufferPointer, &uniforms, sizeof(Uniforms))
```

In the main playground page, see the generated new image:

Hmm… the cube almost looks right, but something is still missing. The next transformation the pixels need to go through is from `world space` to `camera space`. Everything we see on the screen is viewed by a virtual camera through a frustum (pyramidal shape) that has a near and far planes to limit the view (camera) space:

Back in `MathUtils.swift` let’s create the viewMatrix() as well:

```func viewMatrix() -> matrix_float4x4 {
let cameraPosition = vector_float3(0, 0, -3)
return translationMatrix(cameraPosition)
}
```

The next transformation the pixels need to go through is from `camera space` to `clip space`. Here, all the vertices that are not inside the `clip space` will determine whether the triangle will be `culled`(all vertices outside the clip space) or `clipped to bounds` (some vertices are outside but not all). The projectionMatrix() will help us compute the bounds and determine where the vertices are:

```func projectionMatrix(near: Float, far: Float, aspect: Float, fovy: Float) -> matrix_float4x4 {
let scaleY = 1 / tan(fovy * 0.5)
let scaleX = scaleY / aspect
let scaleZ = -(far + near) / (far - near)
let scaleW = -2 * far * near / (far - near)
let X = vector_float4(scaleX, 0, 0, 0)
let Y = vector_float4(0, scaleY, 0, 0)
let Z = vector_float4(0, 0, scaleZ, -1)
let W = vector_float4(0, 0, scaleW, 0)
return matrix_float4x4(columns:(X, Y, Z, W))
}
```

The last two transformations are from `clip space` to `normalized device coordinates (NDC)`and from `NDC` to `screen space`. These two transformations are handled by the Metal framework for us.

Next, back in the `createBuffers()` function, let’s modify the `modelViewProjectionMatrix` we set before to just the `modelMatrix`:

```let aspect = Float(drawableSize.width / drawableSize.height)
let projMatrix = projectionMatrix(1, far: 100, aspect: aspect, fovy: 1.1)
let modelViewProjectionMatrix = matrix_multiply(projMatrix, matrix_multiply(viewMatrix(), modelMatrix()))
```

In `drawRect(:)` we need to set rules for the culling mode and for front facing, in order to avoid weird artifacts such as cube transparency:

```command_encoder.setFrontFacingWinding(.CounterClockwise)
command_encoder.setCullMode(.Back)
```

In the main playground page, see the generated new image:

This is finally the 3D cube we were all waiting to see! There is one more thing we can do to make it even more realistic and lively looking: give it a spin. First, let’s create a global variable named rotation which we want to update as time goes by:

```var rotation: Float = 0
```

Next, grab all the matrices from inside the `createBuffers()` function and let’s create a new one named update(). Here is where we update `rotation` every frame to create a smooth rotation effect:

```func update() {
let scaled = scalingMatrix(0.5)
rotation += 1 / 100 * Float(M_PI) / 4
let rotatedY = rotationMatrix(rotation, float3(0, 1, 0))
let rotatedX = rotationMatrix(Float(M_PI) / 4, float3(1, 0, 0))
let modelMatrix = matrix_multiply(matrix_multiply(rotatedX, rotatedY), scaled)
let cameraPosition = vector_float3(0, 0, -3)
let viewMatrix = translationMatrix(cameraPosition)
let aspect = Float(drawableSize.width / drawableSize.height)
let projMatrix = projectionMatrix(0, far: 10, aspect: aspect, fovy: 1)
let modelViewProjectionMatrix = matrix_multiply(projMatrix, matrix_multiply(viewMatrix, modelMatrix))
let bufferPointer = uniform_buffer.contents()
var uniforms = Uniforms(modelViewProjectionMatrix: modelViewProjectionMatrix)
memcpy(bufferPointer, &uniforms, sizeof(Uniforms))
}
```

In `drawRect(:)` call the `update` function:

```update()
```

In the main playground page, you should see a similar image:

The source code is posted on Github as usual.

Until next time!