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
You are currently viewing page 5 of 5 of this article. Click here to view the first page.

A Rotating Cube

Now you’ll modify the project so it rotates your cube over time.

To do this, open Node.swift and add the following new property:

var time:CFTimeInterval = 0.0

This is simply a property that tracks how long the node has been around.

Now, add the following method to the end of the class:

func updateWithDelta(delta: CFTimeInterval){
    time += delta
}

Next open ViewController.swift and add this new property:

var lastFrameTimestamp: CFTimeInterval = 0.0

Next, change the following line:

timer = CADisplayLink(target: self, selector: #selector(ViewController.gameloop))

To this:

timer = CADisplayLink(target: self, selector: #selector(ViewController.newFrame(displayLink:)))

And replace this:

func gameloop() {
  autoreleasepool {
    self.render()
  }
}

With this:

// 1
func newFrame(displayLink: CADisplayLink){
    
  if lastFrameTimestamp == 0.0
  {
    lastFrameTimestamp = displayLink.timestamp
  }
    
  // 2
  let elapsed: CFTimeInterval = displayLink.timestamp - lastFrameTimestamp
  lastFrameTimestamp = displayLink.timestamp
    
  // 3
  gameloop(timeSinceLastUpdate: elapsed)
}
  
func gameloop(timeSinceLastUpdate: CFTimeInterval) {
    
  // 4
  objectToDraw.updateWithDelta(delta: timeSinceLastUpdate)
    
  // 5
  autoreleasepool {
    self.render()
  }
}

Nice work! Here’s what you did, step-by-step:

  1. The display link now calls newFrame() every time the display refreshes. Note that the display link is passed as a parameter.
  2. You calculate time between this frame and the previous one. Note that it’s inconsistent because some frames might be skipped.
  3. You call gameloop() with the time interval since the last update.
  4. You update your node by using updateWithDelta() before rendering.
  5. Now, when a node is updated, you can call the render method.

Finally, you need to override updateWithDelta in Cube class. Open Cube.swift and add this new method:

override func updateWithDelta(delta: CFTimeInterval) {
    
  super.updateWithDelta(delta: delta)
    
  let secsPerMove: Float = 6.0
  rotationY = sinf( Float(time) * 2.0 * Float(M_PI) / secsPerMove)
  rotationX = sinf( Float(time) * 2.0 * Float(M_PI) / secsPerMove)
}

Nothing fancy here; all you’re doing is calling super() to update the time property. Then you set the cube rotation properties to be a function of sin, which basically means that your cube will rotate to a point and then rotate back.

Build and run, and enjoy your rotating cube!

IMG_2456

Your cube should now have a little life to it, spinning back and forth like a little cube-shaped dancer.

Fixing the Transparency

The last part is to fix the cube’s transparency — but first, you should understand the cause of the issue. It’s really quite simple and a little peculiar: Metal sometimes draws pixels of the back face of the cube before the front face.

So, how do you fix it?

There are two ways to fix this:

  1. One approach is depth testing. With this method, you store each point’s depth value, so that when the two points are drawn at the same point on the screen, only the one with the lower depth is drawn.
  2. The second approach is backface culling. This means that every triangle drawn is visible from only one side. In effect, the back face isn’t drawn until it turns toward the camera. This is based on the order you specify the vertices of the triangles.

You’re going to use backface culling to solve the problem here, as it’s a more efficient solution when there is only one model. The rule you must keep is this: all triangles must be drawn counter-clockwise, otherwise, they won’t be rendered.

Lucky for you, the cube vertices have been set up so every triangle is indeed specified as counter-clockwise, so you can just focus on learning how to use backface culling.

Open Node.swift and add find this in render():

let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)

Add this right below:

//For now cull mode is used instead of depth buffer
renderEncoder.setCullMode(MTLCullMode.front)

Build and run to see how your transparency issue looks:

IMG_2447

Now your cube should be free of transparency. What a beautiful cube!

Where to Go From Here?

Here is the final example project from this iOS Metal Tutorial.

Congratulations, you have learned a ton about moving to 3D in the new Metal API! You now have an understanding of model, view, projection, and viewport transformations, matrices, passing uniform data, backface culling, and more.

In the meantime, be sure to check out some great resources from Apple:

You also might enjoy the Beginning Metal course on our site, where we explain these same concepts in video form, but with even more detail.

If you have questions, comments or discoveries to share, please leave them in the comments below!

Andrew Kharchyshyn

Contributors

Andrew Kharchyshyn

Author

Over 300 content creators. Join our team.