# Metal Tutorial with Swift 3 Part 2: Moving to 3D

In this second part of our Metal tutorial series, learn how to create a rotating 3D cube using Apple’s built-in 3D graphics API. By Andrew Kharchyshyn.

Leave a rating/review
Save for later
Share

## Metal Tutorial with Swift 3 Part 2: Moving to 3D

40 mins

Update: This tutorial has been updated for Xcode 8.2 and Swift 3.

Welcome back to our iOS Metal tutorial series!

In the first part of the series, you learned how to get started with Metal and render a simple 2D triangle.

In this second part of the series, you’ll learn how to set up a series of matrix transformations to move to full 3D. In the process, you will learn:

• How to use model, view, and projection transformations
• How to use matrices to transform geometry
• How to use backface culling to optimize your drawing

Get ready to rock – it’s time for more Metal!

## Getting Started

First, download the starter project – this is in the same state where you left it off in the previous tutorial.

Build and run on your Metal-compatible device, and make sure you see the same triangle as before.

Next, download this Matrix4 helper class that I have created for you, and add it to your project. When prompted if you’d like to configure an Objective-C bridging header, click Yes.

You’ll learn more about matrices later in this tutorial, so for now just enjoy this short overview of `Matrix4`.

There’s a built-in library on iOS called GLKit, which contains a library of handy 3D math routines named `GLKMath`. This includes a class `GLKMatrix4` to work with 3D matrices.

You’re going to do a lot of work with matrices in this tutorial, so it would be nice if you could use this class. However, `GLKMatrix4` is a C struct, so you can’t use it directly from Swift — that’s why I created this class for you. It’s a simple Objective-C class wrapper around the C struct that lets you use `GLKMatrix4` from Swift. Here’s an illustration of the setup:

Again, you’ll learn more about matrices later; this just gives you a quick overview of the class.

## Refactoring to a Node Class

For now, the starter project has everything set up in ViewController.swift. Although this was the easiest way to get started, it won’t scale well as your app gets larger.

In this section, you’ll refactor your project through the following five steps:

1. Creating a Vertex Structure
2. Creating a Node Class
3. Creating a Triangle Subclass

Time to get started!

Note: This section is optional, since at the end of this section you’ll be right back where you started, albeit with a cleaner project and a colored triangle. If you’d like go straight to the 3D stuff, feel free to skip ahead to the “Creating a Cube” section. A starter project will be waiting there for you.

### 1) Creating a Vertex Structure

Create a new class with the iOS\Source\Swift File template and name it Vertex.swift.

Open Vertex.swift and replace its contents with the following:

```struct Vertex{

var x,y,z: Float     // position data
var r,g,b,a: Float   // color data

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

}
```

This is a structure to store the position and color of each vertex. `floatBuffer()` is a handy method that returns the vertex data as an array of Floats in strict order.

### 2) Create a Node Class

Create a new class with the iOS\Source\Swift File template and name it Node.swift.

Open Node.swift and replace the contents with the following:

```import Foundation
import Metal
import QuartzCore

class Node {

let device: MTLDevice
let name: String
var vertexCount: Int
var vertexBuffer: MTLBuffer

init(name: String, vertices: Array<Vertex>, device: MTLDevice){
// 1
var vertexData = Array<Float>()
for vertex in vertices{
vertexData += vertex.floatBuffer()
}

// 2
let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0])
vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: [])

// 3
self.name = name
self.device = device
vertexCount = vertices.count
}

}
```

Going over this code section by section:

Since `Node` represents an object to draw, you need to provide it with the vertices it contains, a name for convenience, and a device to create buffers and render later on.

1. Go through each vertex and form a single buffer with floats, which will look like this:
2. Then, ask the device to create a vertex buffer with the float buffer you created above.
3. Finally, you set the instance variables.

Nothing fancy, eh?

Next, you need to move some of the render code that is currently in `ViewController` to `Node`. Specifically, you want to move the code responsible for rendering a particular buffer of vertices.

To do this, add this method to Node.swift:

```func render(commandQueue: MTLCommandQueue, pipelineState: MTLRenderPipelineState, drawable: CAMetalDrawable, clearColor: MTLClearColor?){

let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].clearColor =
MTLClearColor(red: 0.0, green: 104.0/255.0, blue: 5.0/255.0, alpha: 1.0)
renderPassDescriptor.colorAttachments[0].storeAction = .store

let commandBuffer = commandQueue.makeCommandBuffer()

let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, at: 0)
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertexCount,
instanceCount: vertexCount/3)
renderEncoder.endEncoding()

commandBuffer.present(drawable)
commandBuffer.commit()
}
```

This code should be a review from the previous tutorial. You can see that it’s been copied from `ViewController`‘s `render()` method, but updated to use the vertex data for this node.

That’s it for the node class for now – time to create a subclass for the triangle!

### 3) Create a Triangle Subclass

Create a new class with the iOS\Source\Swift File template and name it Triangle.swift.

Open Triangle.swift and replace the contents with the following:

```import Foundation
import Metal

class Triangle: Node {

init(device: MTLDevice){

let V0 = Vertex(x:  0.0, y:   1.0, z:   0.0, r:  1.0, g:  0.0, b:  0.0, a:  1.0)
let V1 = Vertex(x: -1.0, y:  -1.0, z:   0.0, r:  0.0, g:  1.0, b:  0.0, a:  1.0)
let V2 = Vertex(x:  1.0, y:  -1.0, z:   0.0, r:  0.0, g:  0.0, b:  1.0, a:  1.0)

let verticesArray = [V0,V1,V2]
super.init(name: "Triangle", vertices: verticesArray, device: device)
}

}
```

Here you subclass the `Node` class you just wrote for the triangle. In the initializer, you define the three vertices that make up the triangle, and pass that data to the superclass’s initializer.

It’s looking nice and clean already!

### 4) Refactor your View Controller

Now that you have all of the pieces in place, let’s refactor your view controller to use your new `Triangle` class.

Open ViewController.swift and delete the following line:

```var vertexBuffer: MTLBuffer! = nil
```

The node object holds `vertexBuffer`, so you won’t need it here.

Next, replace:

```let vertexData:[Float] = [
0.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0]
```

with the following:

```var objectToDraw: Triangle!
```

And replace:

```// 1
let dataSize = vertexData.count * sizeofValue(vertexData[0])
// 2
vertexBuffer = device.newBufferWithBytes(vertexData, length: dataSize, options: nil)
```

with:

```objectToDraw = Triangle(device: device)
```

Now, when `objectToDraw` initializes and is ready to go, the only thing that’s missing a call to the draw method from `objectToDraw` in the ViewController render method.

Finally, replace the `render()` method with the following:

```func render() {
guard let drawable = metalLayer?.nextDrawable() else { return }
objectToDraw.render(commandQueue: commandQueue, pipelineState: pipelineState, drawable: drawable, clearColor: nil)
}
```

Build and run, and… uh-oh, where’d the triangle go?

Do you have any ideas why that might be? Try to think of an answer, and then check to see if you are correct. Hint: take a look at the data inside the `Vertex` structure.

[spoiler]`Vertex` now contains color components, but in your vertex shader, you only expect three components for x, y and z.[/spoiler]

Andrew Kharchyshyn

## Contributors

Andrew Kharchyshyn

Author