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”

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 .

To present the scene open the ViewController.swift file and in the viewDidLoad method set the view’s scene property 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 SCNSphere. SCNSphere 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 rootNode property.

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.

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 SCNView autoenablesDefaultLighting 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.

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

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.

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.

This is how the spheres looks in the coordinate system:

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:

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]

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]

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]

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]

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]

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). [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]

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]

Project with all solutions