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:

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:

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 0, 1 and 2 and then we draw the triangle that uses vertices 2, 3 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:

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

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

with a drawIndexedPrimitives call:

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!

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:

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:

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

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:

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:

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`:

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:

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:

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:

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

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

The source code is posted on Github as usual.

Until next time!