Introduction To SceneKit – Part 1

TL;DR We’ll add SceneKit to a project and build a simple scene

What is SceneKit?

SceneKit is a powerfull high level framework for adding 3D graphics to your apps. You can use it to build games, interactive visualizations, 3D user interfaces or any kind of gaming or non-gaming appliclications. SceneKit has been around since 2012 but was previously only available on OSX. Before SceneKit iOS Developers had to learn OpenGL or use 3rd party Frameworks which often did not interact well with the rest of iOS or had significant limitations.

Creating a new SceneKit Project

Let’s look at creating a new project that uses SceneKit. Apple provides a template for SceneKit projects(Application -> Games) but for this tutorial we’ll be adding SceneKit to an existing project to get a better understanding on how it works.

Open Xcode and create a new Single View Application (iOS -> Application -> Single View Application). Make sure to select Swift as the Language and iPad as the Devices. This tutorial is build for iPad because of the larger screen size.

SceneKit isn’t included by default in our project so we’ll have to add it.

  • Select your project from the organizer on the left.
  • Select your project’s target under “TARGETS”.
  • Select the “Build Phases” tab
  • Expand “Link Binary With Libraries” and click the plus button on the bottom
  • Search for “scenekit”, select it and click “Add”

Link Binary

Setting up the view

Classes in the SceneKit library are prefixed with SCN. SCNView is a sublass of UIView that is used to display your 3D Scene.

The actual contents of your 3D Scene are stored in an instance of the SCNScene class which can be created programmatically or loaded from Collada(dae) or Alembic(abc)(Only works for OS X apps) files.

It’s a good idea to create a custom subclass of SCNScene so that you make the scene’s logic self contained. This also helps when you start having multiple scenes in your app.

SCNScenes are displayed by SCNViews. Let’s create a custom scene for our project. Create a new class named PrimitivesScene (File -> New File -> Cocoa Touch Class), make sure to subclass SCNScene

We’ll have to change the class of the ViewController’s view to SCNView so that we can display our PrimitivesScene. Open the “Main.storyboard” File. Select ViewController -> View go to “Identity Inspector” and change the Class field to SCNView.

Custom View

To present the scene open the ViewController.swift file and in the viewDidLoad method set the view’s sceneproperty to a new instance of PrimitivesScene. Notice that we have to cast the ViewController view to SCNView to access the scene property. We also set the backgroundColor to black.

let scnView = self.view as! SCNView   
scnView.scene = PrimitivesScene()
scnView.backgroundColor = UIColor.blackColor()

Make sure to also import SceneKit at the top of the file.

import SceneKit

Running the app right now will just produce a black screen, that’s because our scene is currently empty. Let’s add a sphere to it. Open the PrimitivesScene.swift file. We’ll initialize the contents of our scene in the init method:

override init() {
    super.init()
}

We also have to provide an init with coder method because SCNScene conforms to the NSCoder protocol which has an required init method. We’ll just leave the default implementation because we don’t plan on loading our scene from a file.

required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

Note:  You should also add “import SceneKit” to the top of the “PrimitivesScene.swift” file if it’s not added automatically for you.

Adding a sphere

Each scene manages a hierarchy of nodes called the scene graph. Nodes can contain child nodes, and each node except the topmost has a parent. It’s simillar to the way other hierarchical nested structures work like the hierarchy of UIViews or file systems. We add 3D objects to our scene by creating new nodes that contain geometry information.

To create a sphere you have to create a new instance of SCNNode using a geometry of type SCNSphereSCNSphere is a subclass of SCNGeometry and provides the 3D data needed to draw the node’s geometry.

let sphereGeometry = SCNSphere(radius: 1.0)
let sphereNode = SCNNode(geometry: sphereGeometry)

A scene’s root node is the node at the top of the node hierarchy and can be accessed using the scene’s rootNodeproperty.

To add the sphereNode to the scene we use the addChildNode method on the rootNode.

self.rootNode.addChildNode(sphereNode)

If you run the app right now you’ll see a white sphere at the center of the screen.

Flat Sphere

Let there be light

The sphere is indistinguishable from a circle because no lights are enabled in the scene. SceneKit provides some default lighting that can be enabled by setting the SCNViewautoenablesDefaultLighting property to true. In the ViewController class add the following line at the end of the viewDidLoad method

scnView.autoenablesDefaultLighting = true

The sphere will now appear shaded.

Shaded Sphere

Moving around

To get a better view of the scene it’s useful to be able to change the camera of the scene. The camera controls the point of view and the direction of view of the scene. Like with lighting SceneKit provides a helpful default camera that can be enabled by setting the scnView.allowsCameraControl to true in the ViewController class

scnView.allowsCameraControl = true

Camera Sphere

The point of view of the scene can now be modified. The camera can be controlled in the following ways:

  • rotate the scene around the camera’s target by panning with one finger.
  • move the scene by panning with 2 fingers
  • zoom the scene by pinching with 2 fingers
  • tilt the camera by performing a rotation gesture with 2 fingers

More spheres!

Let’s add a second sphere to the scene to get a better idea of how the camera behaves. We’ll have to position the second sphere so that it doesn’t overlap with the first one. We can set a SCNNode‘s position property to specify it’s position in 3D space. SceneKit uses a right handed coordinate system where the x axis points right, the y axis points up and the z axis points towards you. Positions are expressed using an instance of SCNVector3 where the x,y,z components specify the displacement along each axis.

Here’s a picture for better understanding. We have one sphere of radius 1.0 centered at (0.0,0.0,0.0) – the origin of our coordinate system. Nodes are positioned at the origin by default. Sphere camera

We’ll create another sphere centered at (3,0,0) with a radius of 0.5 In the init method of the PrimitivesScene class add the following code:

let secondSphereGeometry = SCNSphere(radius: 0.5)
let secondSphereNode = SCNNode(geometry: secondSphereGeometry)
secondSphereNode.position = SCNVector3(x: 3.0, y: 0.0, z: 0.0)
self.rootNode.addChildNode(secondSphereNode)

The app should display 2 spheres now. SpriteKit automatically adjusts the camera so that all objects in the scene are visible. Moving around will give you a better sense of depth. 2 Spheres

This is how the spheres looks in the coordinate system:

2 Spheres coordinates

Adding Color

To change the color of an object we have to assign a new UIColor instance to the contents of the diffuse property of a geometry’s firstMaterial. That’s quite a handfull and we’ll go into more details on this in a future tutorial on materials and lighting. For now lets make the first sphere orange and the second sphere purple. Add the following line below the declaration of sphereGeometry

sphereGeometry.firstMaterial?.diffuse.contents = UIColor.redColor()

and the following line below the declaration of secondSphereGeometry

secondSphereGeometry.firstMaterial?.diffuse.contents = UIColor.greenColor()

The scene should look like this now:

2 Spheres

Source Code

In Part 2 we’ll look at the different types of primitives that SceneKit provides, animations and nesting nodes.

Challenges

These challenges are intended to give you a better understanding of positioning objects in 3D space.

1) Create a third sphere with a radius of 1.5, red color and position it above the orange sphere

Solution

let thirdSphereGeometry = SCNSphere(radius: 1.5)
thirdSphereGeometry.firstMaterial?.diffuse.contents = UIColor.redColor()
let thirdSphereNode = SCNNode(geometry: thirdSphereGeometry)
thirdSphereNode.position = SCNVector3(x: 0.0, y: 3.0, z: 0.0)
self.rootNode.addChildNode(thirdSphereNode)

[collapse]

3 Spheres

2) Create a line of 20 spheres with radius 1.0, with distance 1.0 between each other along the xAxis (coordinates (0,0,0),(3,0,0),(6,0,0)…) Make their colors alternate between red and green.

Hint1

Create spheres in a for loop using an index

 

[collapse]
Hint2

Color the sphere orange if the index is even, purple if it’s odd

[collapse]
Solution

var x:Float = 0.0
var radius:CGFloat = 1.0
let numberOfSpheres = 20

for i in 1...numberOfSpheres {
    let sphereGeometry = SCNSphere(radius: radius)

    if (i % 2 == 0) {
        sphereGeometry.firstMaterial?.diffuse.contents = UIColor.orangeColor()
    } else {
        sphereGeometry.firstMaterial?.diffuse.contents = UIColor.purpleColor()
    }

    let sphereNode = SCNNode(geometry: sphereGeometry)
    sphereNode.position = SCNVector3(x: x, y: 0.0, z: 0.0)

    self.rootNode.addChildNode(sphereNode)

    x += 3 * Float(radius)
}

[collapse]

Sphere Line

3) Create a line of 20 spheres tangent (just touching) to each of their neighbors. Make the radius of each sphere 0.05 smaller then the sphere to it’s left.

Hint

The distance between the centers of 2 tangent spheres of radii r1 and r2 is r1 + r2

[collapse]
Solution

var x:Float = 0.0
var radius:CGFloat = 1.0
let numberOfSpheres = 20

for i in 0..<numberOfSpheres {

    let sphereGeometry = SCNSphere(radius: radius)

    if (i % 2 == 0) {
        sphereGeometry.firstMaterial?.diffuse.contents = UIColor.orangeColor()
    } else {
        sphereGeometry.firstMaterial?.diffuse.contents = UIColor.purpleColor()
    }

    let sphereNode = SCNNode(geometry: sphereGeometry)
    sphereNode.position = SCNVector3(x: x, y: 0.0, z: 0.0)

    self.rootNode.addChildNode(sphereNode)

    x += Float(radius)
    radius -= 0.05
    x += Float(radius)
}

[collapse]

Decreasing Sphere Line

4) Make a grid of 20×20 spheres of radius 1 in the XY plane. Make each sphere tangent to it’s neighbors. Color the spheres in a grid pattern with orange and purple.

Solution

var y:Float = 0.0
var radius:CGFloat = 1.0
let yCount = 20
let xCount = 20
for row in 0..<yCount {
    var x:Float = 0.0
    for column in 0..<xCount {
        let sphereGeometry = SCNSphere(radius: radius)

        if (row + column) % 2 == 0 {
            sphereGeometry.firstMaterial?.diffuse.contents = UIColor.orangeColor()
        } else {
            sphereGeometry.firstMaterial?.diffuse.contents = UIColor.purpleColor()
        }

        let sphereNode = SCNNode(geometry: sphereGeometry)
        sphereNode.position = SCNVector3(x: x, y: y, z: 0.0)

        self.rootNode.addChildNode(sphereNode)

        x += 2 * Float(radius)
    }
    y += 2 * Float(radius)
}

[collapse]

Sphere Checker Board

5) Make a grid like in the previous exercise. Assume that each sphere has a row and column number from 0 to 19. Use the formula sphereColor = i & j != 0 ? UIColor.purpleColor() : UIColor.orangeColor()

Here’s some more information about these types of patterns:
Bitwise And Fractals
Sierpinki in Pascal’s Triangle

Solution

var y:Float = 0.0
var radius:CGFloat = 1.0
let yCount = 20
let xCount = 20
for row in 0..<yCount {
    var x:Float = 0.0
    for column in 0..<xCount {
        let sphereGeometry = SCNSphere(radius: radius)

        sphereGeometry.firstMaterial?.diffuse.contents = row & column != 0 ? UIColor.purpleColor() : UIColor.orangeColor()


        let sphereNode = SCNNode(geometry: sphereGeometry)
        sphereNode.position = SCNVector3(x: x, y: y, z: 0.0)

        self.rootNode.addChildNode(sphereNode)

        x += 2 * Float(radius)
    }
    y += 2 * Float(radius)
}

[collapse]

Sphere Fractal

6) Make a triangle out of spheres.
BONUS: Make the spheres tangent to each other

Hint

Increase the y coordinate at each step by sqrt(3) (the height of an equilateral triangle of side 2).

Equilateral Triangle

[collapse]
Solution

var y:Float = 0.0
var radius:CGFloat = 1.0
let yCount = 20
let xCount = 20
for row in 0..<yCount {
    var x:Float = Float(radius) * Float(row)
    for column in row..<xCount {
        let sphereGeometry = SCNSphere(radius: radius)

        if ((row + column) % 2 == 0) {
            sphereGeometry.firstMaterial?.diffuse.contents = UIColor.orangeColor()
        } else {
            sphereGeometry.firstMaterial?.diffuse.contents = UIColor.purpleColor()
        }

        let sphereNode = SCNNode(geometry: sphereGeometry)
        sphereNode.position = SCNVector3(x: x, y: y, z: 0.0)

        self.rootNode.addChildNode(sphereNode)

        x += 2 * Float(radius)
    }
    y += sqrt(3.0) * Float(radius)
}

[collapse]

Sphere Triangle

7) Make a 7x7x7 cube out of spheres. Alternate each level of the cube with orange and purple.
BONUS: Don’t add any spheres inside the cube

Solution

var y:Float = 0.0

var radius:CGFloat = 1.0

let yCount = 7
let zCount = 7
let xCount = 7

for row in 0..<yCount {
    var z:Float = 0.0
    for depth in 0..<zCount {
        var x:Float = 0.0
        for column in 0..<xCount {

            let sphereGeometry = SCNSphere(radius: radius)

            if (row % 2 == 0) {
                sphereGeometry.firstMaterial?.diffuse.contents = UIColor.orangeColor()
            } else {
                sphereGeometry.firstMaterial?.diffuse.contents = UIColor.purpleColor()
            }

            let sphereNode = SCNNode(geometry: sphereGeometry)
            sphereNode.position = SCNVector3(x: x, y: y, z: z)


            self.rootNode.addChildNode(sphereNode)

            x += 2.0 * Float(radius)

        }
        z += 2 * Float(radius)
    }
    y += 2 * Float(radius)
}

 

[collapse]

Sphere Cube

Project with all solutions

  35 comments for “Introduction To SceneKit – Part 1

  1. October 29, 2014 at 9:15 pm

    I can’t find any information on importing an Alembic file in the documentation. Do you know how to do this?

    • October 29, 2014 at 11:26 pm

      Unfortunately this only works for OSX apps.
      Updated the article.

      • October 30, 2014 at 7:09 pm

        Ok, thanks. So would I be able to load an .abc file through the SCNScene::scenenamed or SCNNode? SCNGeometrySource?

        I can see preview the .abc in xcode but I can’t figure out how to load it in a scene.

        example .abc file:
        https://alembic.googlecode.com/files/Alembic_Octopus_Example.tgz

        • November 3, 2014 at 10:07 pm

          You use the SCNScene::scenenamed method to load models. Don’t forget to add the “.abc” extension to the file you want to load.
          For example:
          let scene = SCNScene(named: “alembic_octopus.abc”)
          Then you can traverse the node hierarchy starting from the rootNode to get the node you want.
          http://imgur.com/R5MorG6

  2. Jesse
    October 31, 2014 at 10:38 pm

    Thanks so much for this tutorial and your time with it. I greatly appreciate it as a newbie trying to learn.

  3. A guy
    November 2, 2014 at 11:39 am

    What a fantastic tutorial! I have a question: how do we adjust position of camera? Can we customize camera control?

    • November 3, 2014 at 10:23 pm

      Hey,
      To have greater control of the camera you need to create a new instance of SCNCamera and attach it to a node

      let camera = SCNCamera()
      let cameraNode = SCNNode()
      cameraNode.camera = camera
      cameraNode.position = SCNVector3Make(0, 0, 5.0)
      rootNode.addChildNode(cameraNode)

      You can then move/rotate the node to position the camera.
      You don’t necessarily have to attach the camera to the rootNode. For example you could create a 3rd person camera by adding the camera behind some object in your 3D Scene and moving the object.

  4. ste mc
    November 3, 2014 at 9:53 pm

    Great tutorial. Can’t wait for part 2. Will there be anything on SCNShape?

    • November 3, 2014 at 10:11 pm

      Hi,
      I’m currently working on part 2, should be ready by Thursday. Unfortunately I won’t cover SCNShape and SCNText as they are more advanced and specialized primitives.

  5. siddharth shekar
    November 6, 2014 at 3:02 am

    hi. Awesome tutorial. Will you be also showing how to make different scene files like mainmenu, gameplay and option scene???

    • November 7, 2014 at 2:57 pm

      You can bet on it! We are going to make more SceneKit tutorials. The first series will introduce basic concepts.

  6. David Karlsson
    November 11, 2014 at 8:10 pm

    “change the class of the ViewController’s view to SCNView” does not work and just creating the file : “PrimitivesScene.swift” with

    “//
    // PrimitivesScene.swift
    // SceneKitTut1
    //
    // Created by David Karlsson on 2014-11-11.
    // Copyright (c) 2014 davidkarlsson. All rights reserved.
    //

    import UIKit

    class PrimitivesScene: SCNScene {

    }

    Does not work, the class field just switches back and the PrimitivesScene file complaints that SCNScene is undeclared, Is there missing some import step?

    • November 12, 2014 at 7:06 pm

      Make sure to select the “View” in interface builder and not “View Controller”.
      Reference

      In the PrimitivesScene class add “import SceneKit” at the top of the file, below “import UIKit”. This should happen automatically but it seams that Xcode is bugged.

  7. Deepak
    November 20, 2014 at 3:28 pm

    Thanks for tutorial. It’s really helpful.

    And I have a doubt. I had to cast PrimitiveScene to remove the error: ‘init()’ is unavailable: superseded by import of -[NSObject init].

    scnView.scene = PrimitiveScene() as SCNScene

    Can you explain why!!

    • November 20, 2014 at 7:45 pm

      Seems to be some issue in recent versions of Swift. (This error was not present at the time of writing of the tutorial).

      • Scott
        December 18, 2014 at 4:52 pm

        SO is there anyway to fix this error? It’s impeding any progress. :-o

        • Scott
          December 18, 2014 at 5:03 pm

          Found the error.

          Above you had written:

          scnView.scene = PrimitivesScene()

          When it should be:

          let scene = PrimitivesScene()

          Now it works! ;-)

          • Steve
            December 26, 2014 at 11:14 am

            Confirmed! At 26 dec 2014, the code:
            scnView.scene = PrimitivesScene()
            doesn’t work anymore, probably because Apple has changed something in the Swift.

            But it works, as Scott provided:
            let scene = PrimitivesScene()

            Also, after adding a single sphere (“If you run the app right now you’ll see a white sphere at the center of the screen.”) I see a black screen. Looks like without light in the scene it can be displayed.

          • ToL
            February 12, 2015 at 3:47 pm

            and then you need scnView.scene = scene

            Found it eventually in the project download!
            Great tutorial, thanks for taking the time to explain the basics….

  8. Deepak
    November 20, 2014 at 4:19 pm

    Spheres in my View are getting out of screen. Why would that happen. I’m new and learning. Can you please help.

    • November 20, 2014 at 7:46 pm

      Yeah, that sometimes happens with the default camera controls in SceneKit. Seems to be a SceneKit issue.

      • Deepak
        November 21, 2014 at 4:59 am

        Can we do something with the camera settings so that the scene fit in screen. Please guide.

        • Deepak
          November 21, 2014 at 1:24 pm

          Got it. Read you previous comment and rearranged the camera position. I could have done that before commenting :-(

  9. Chris
    December 11, 2014 at 2:59 am

    I have the latest Xcode. Got some errors on a couple of lines:

    let sphereNode = SCNNode(geometry: sphereGeometry)
    – ‘PrimitivesScene.Type’ does not have a member named ‘sphereGeometry’

    self.rootNode.addChildNode(sphereNode)
    – expect declaration

    Any thoughts?

    Here are my codes so far:

    import UIKit
    import SceneKit

    class PrimitivesScene: SCNScene {

    let sphereGeometry = SCNSphere(radius: 1.0)

    let sphereNode = SCNNode(geometry: sphereGeometry)
    self.rootNode.addChildNode(sphereNode)

    override init() {
    super.init()
    }

    required init(coder aDecoder: NSCoder) {
    fatalError(“init(coder:) has not been implemented”)
    }

    }

    • December 13, 2014 at 1:47 am

      Code should look like this:
      override init() {
      super.init()
      let sphereGeometry = SCNSphere(radius: 1.0)

      let sphereNode = SCNNode(geometry: sphereGeometry)
      self.rootNode.addChildNode(sphereNode)
      }

      I’ll update the article soon to avoid confusion on where all the pieces of code go.

  10. Jagruti
    December 22, 2014 at 9:32 am

    I am new to animation. I created single view application with swift, now i am trying to create “PrimitivesScene” but it is not giving me SCNScene in subclass list. I recently installed xcode 6.1.1

    Please help.

    • Jagruti
      December 22, 2014 at 10:01 am

      will this work with iPhone? What about Collada or Alembic?

    • December 22, 2014 at 10:56 am

      You have to link the SceneKit framework. Follow the steps or download the starter project from here.

    • Nikhil
      January 11, 2015 at 12:39 am

      Even if it does not give you SCNScene in the list you can still manually type in the name in the “subclass of” field and it will still work as expected. Make sure not to make a typo though “SCNScene”. Code completion in swift is wonky at times.

  11. Nikhil
    January 11, 2015 at 12:40 am

    Hey guys,

    Loving this set of lessons, thanks a lot for taking the time to put this up.

    -Nikhil.

  12. Eric Vaughan
    July 12, 2015 at 7:03 pm

    This no longer seems to work in Xcode 6.4. The compiler requires you to use “as!” here:
    let scnView = self.view as! SCNView
    to fix compiler error.

    But even when that is fixed all you get is a black view. I even tried the Towers of Hanoi starter project. Same thing happens. All black.
    Something must have changed..

  13. Eric Vaughan
    July 12, 2015 at 7:15 pm

    ok got it working. Just need to fix compiler error by using “as!”.

  14. February 4, 2016 at 11:58 pm

    Must be camera angles or whatnot, the first sphere appears dead center, but when I add the second sphere the first one goes half off the screen to the left, and the new one half off the screen to the right.
    Plus the radius doesn’t seem to do anything on my first sphere, it’s always huge when by itself.

    Weirdness, but really good tutorial, I’m finding my way just a bit better now.

  15. Cheer
    August 19, 2016 at 6:17 am

    It’s a really good tutorial,but maybe code could be more “Swift”…


    for i in 0..<20
    {
    let sphereGeometry = SCNSphere(radius: 1)
    sphereGeometry.firstMaterial?.diffuse.contents = i % 2 == 0 ? UIColor.orangeColor() : UIColor.purpleColor()

    let sphereNode = SCNNode(geometry: sphereGeometry)
    sphereNode.position = SCNVector3(i * 3,0,0)

    rootNode.addChildNode(sphereNode)
    }
    "

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe
We send about one email per week with our latest tutorials and updates
Never display this again :)