SceneKit Tutorial Part 1
We are going to start a new series of tutorials about SceneKit, the framework that allows you to build and manipulate 3D objects in a 3D scene. SceneKit was introduced for the first time in macOS 10.8 (Mountain Lion) and successively in iOS 8. Recently, SceneKit was added to watchOS 3.0 and tvOS 9.
SceneKit allows developers to create 3D games and add 3D content to apps using high-level scene descriptions. Because of the recent introduction of ARKit, SceneKit is today a very relevant framework. If you want to build Augmented Reality application, you should learn SceneKit. The framework provides a reach set of APIs that make easy to add animations, physics simulation, particle effects, and realistic physically based rendering.
We are going to explore the basics of SceneKit, first. In future posts, I will show you more sophisticated techniques, writing custom shaders and mixing SceneKit with Metal.
Nodes and Scene Graph
SceneKit is based of the concept of nodes. Each 3D object you want to render using SceneKit is a node, an object of type SCNNode
. An SCNNode
object by itself has no visible content when the scene containing is rendered on screen. An SCNNode
is only a model object providing the coordinate space transform (position, orientation, and scale) relative to its parent node.
To build a SceneKit 3D scene, you use a hierarchy of nodes to create its structure, then you add lights, cameras, and geometry to each node to create the visible content. SceneKit implements content as a hierarchical tree structure of nodes, also known as scene graph. You may create a node hierarchy programmatically or load one from a file created using 3D authoring tools, or combine the two approaches.
The rootNode
object in a scene defines the coordinate system of the rendered 3D world. Each child node you add to this root node has its own coordinate system, which is in turn inherited by its own children. This is very similarly to the view hierarchy in UIKit.
SceneKit displays scenes in a SCNView
, processing the scene graph and performing animations before efficiently rendering each frame on the GPU.
Before working with SceneKit, you should be familiar with basic graphics concepts such as coordinate systems and the mathematics of three-dimensional geometry. SceneKit uses a right-handed coordinate system where (by default) the direction of view is along the negative z-axis, as illustrated below.

You add 2D and 3D objects to a scene by attaching SCNGeometry
objects to nodes. Geometries, in turn, have attached SCNMaterial
objects that determine their appearance. To shade the geometries in a scene with light and shadow effects, add nodes with attached SCNLight
objects. To control the viewpoint from which the scene appears when rendered, add nodes with attached SCNCamera
objects. We are going to look at all these classes in this post.
Creating a node
To create a node you use the init(geometry:)
to initialize an object with a specific geometry (see later, Geometry). You can also initialize a node using a Model I/O object using the init(mdlObject:)
method. Model I/O is a relative new Cocoa framework that provides a system-level understanding of 3D model assets and related resources. Model I/O was introduced in iOS 9.
Another way to create a node is to use the SceneKit node editor available in Xcode. The editor appears when you select a SceneKit file. Then, you drag nodes from the object library and assign a name to them.
Managing node’s transformations
Once the node is created, you can assign it a position
, eulerAngles
and scale
to transform the node.
The node’s position
locates it within the coordinate system of its parent, as modified by the node’s pivot
property. The default position is the zero vector, indicating that the node is placed at the origin of the parent node’s coordinate system.
The node’s eulerAngles
represent the node’s orientation, expressed as pitch, yaw, and roll angles, each in radians. The order of components in this vector matches the axes of rotation:
- Pitch (the x component) is the rotation about the node’s x-axis.
- Yaw (the y component) is the rotation about the node’s y-axis.
- Roll (the z component) is the rotation about the node’s z-axis.
SceneKit applies these rotations relative to the node’s pivot
property in the reverse order of the components: first roll, then yaw, then pitch.
The node’s scale
represents the scale factor applied to the node. Each component of the scale vector multiplies the corresponding dimension of the node’s geometry. The default scale is 1.0 in all three dimensions.
The node’s pivot
is similar to the anchorPoint
of a CALayer
in Core Animation. The default pivot
is SCNMatrix4Identity
, specifying that the node’s position
locates the origin of its coordinate system, its rotation
is about an axis through its center, and its scale
is also relative to that center point.
The node’s orientation
is expressed as a quaternion (SCNQuaternion
). The rotation
, eulerAngles
, and orientation
properties all affect the rotational aspect of the node’s transform property. Any change to one of these properties is reflected in the others.
The transform
property of a node is a 4x4 matrix of type SCNMatrix4
. This property combines the node’s rotation
, position
, and scale
properties. The default transformation is SCNMatrix4Identity
. When you set the value of this property, the node’s rotation
, orientation
, eulerAngles
, position
, and scale
properties automatically change to match the new transform, and vice versa. SceneKit can perform this conversion only if the transform
you provide is a combination of rotation
, translation
, and scale
operations. If you set the value of this property to a skew transformation or to a nonaffine transformation, the values of these properties become undefined.
In iOS 11, the SCNNode
class duplicates all the above properties and uses SIMD types to simplify the interoperability between Metal and SceneKit. For example, you can now set the position of a node using the old position
property and the new simdPosition
. In the same way, you can now use simdEulerAngles
, simdScale
, simdOrientation
, simdTransform
and simdPivot
in place of eulerAngles
, scale
, orientation
, transform
and pivot
.
Managing node’s attributes
A node has additional properties you can set:
- name
is a String
you ca use as unique identifier for a node
- light
is a SCNLight
object you can attach to the node to represent a light
- camera
is a SCNCamera
object representing a point of view
- geometry
is a SCNGeometry
object representing the shape of the node
- morpher
is a SCNMorpher
object responsible of blending node’s geometry
- skinner
is a SCNSkinner
object responsible for skeletal animations of a node
- categoryBitMask
is an Int
that defines which category the node belongs to. You can assign each node of a scene to one or more categories.
Modifying the node visibility
You can modify the visibility of a node using the following properties:
- isHidden
is a boolean that determines the visibility of the node
- opacity
is a CGFloat
representing the opacity value of a node
- renderingOrder
is an Int
representing the order the node’s content is drawn respect to other nodes. Default value is zero.
- castsShadow
is a boolean that determines whether SceneKit renders the node’s contents into shadow maps
- movabilityHint
controls how the node contributes to various motion-related effects during rendering. The default value is fixed
. This value is merely a hint that communicates to SceneKit’s rendering system about how you want to move content in your scene; it does not affect your ability to change the node’s position
or add animations or physics to the node.
Managing the node hierarchy
To inspect the scene graph, you can use the parent
property to ask for the parent node. The rootNode
of a scene does not have a parent. Additionally, you can use the childNodes
property to retrieve the array of child nodes for the current node.
The methods addChild(_:)
and insert_:at:)
allow you to add a new node to another node. The method removeFromParentNode()
removes a node from the scene graph. Finally, replaceChildNode(_:with:)
removes a child from the node’s array of children and inserts another node in its place.
Searching the node hierarchy
If you want to search a specific node in the scene graph, you can use methods such as childNode(withName:recursively:)
. This method requires the name of the node you are searching and a boolean value. If the boolean value is true
, SceneKit search the entire node subtree, otherwise it searches only the immediate children.
The childNodes(passingTest:)
method returns every node satisfying a provided test. For example, you can search for empty nodes using a block that returns true
for nodes whose light
, camera
, and geometry
properties are all nil
.
enumerateChildNodes(_:)
applies a closure to the child node and descendant nodes. Finally, enumerateHierarchy(_:)
executes a specified closure for each of the node’s child and descendant nodes, as well as for the node itself.
Customizing node rendering
If you want to customize the appearance of a node, you have different options. This is a very wide topic that I will cover in a future post. However, there is also a simpler solution based on Core Image. The filters
property of a node is an array of CIFilter
s you can assign to a node. The filters are then applied to the rendered contents of the node. SceneKit renders the node (and its child node hierarchy) into an image buffer and then applies the filters before compositing the filters’ output into the rendered scene. The order of the array determines the order of the Core Image filter chain.
You can assign to the node a rendererDelegate
object responsible for rendering custom contents for the node using Metal or OpenGL.
Working with positional audio
You can add an audio player or multiple audio players to a node using the addAudioPlayer(:_)
method or the audioPLayers
property. You pass an SCNAudioPlayer
object to the node. This player is initialized either using an SCNAudioSource
or a AVAudioNode
. The audio player is a controller for playback of a positional audio source in a SceneKit scene.
You can use the removeAudioPlayer(_:)
and removeAllAudioPlayers()
methods to remove the audio player from the node.
Copy a node
It sometimes necessary to copy a node. The clone()
method creates a copy of a node and its children. For a non-recursive copy, use the copy()
method, which creates a copy of the node without any child nodes. Be aware that cloning or copying a node creates a duplicate of the node object, but not the geometries, lights, cameras, and other SceneKit objects attached to it—instead, each copied node shares references to these objects.
If you have node hierarchy, you can use the flattenedClone()
method and obtain a new single node containing the combined geometries and materials of the node and its child node subtree.
Converting between node coordinate space
If you want to convert the position of a node from and to another node, you can use convertPosition(_:from:)
and convertPosition(_:to:)
methods. Instead the methods convertTransform(_:from:)
and convertTransform(_:to:)
convert a transformation matrix from and to the node coordinate space defined by another node.
Scene View
SceneKit uses an SCNView
to render a 3D scene. An SCNView
is a subclass of UIView
on iOS and tvOS or NSView
on macOS. You create an SCNView
using Interface Builder or programmatically using the init(frame:options:)
method. The second argument of the init(frame:options:)
method is a set of options you can pass to the view to define:
- The preferred rendering API. This can be either Metal, OpenGL ES 2.0, Open GL, Open GL 3.2 or Open GL 4.1. Please, check the documentation for the
SCNRenderingAPI
enumeration for more details. - The preferred Metal device. If you selected Metal as the rendering API, you can also select the metal device (
MTLDevice
) you want to use. For example, on a macOS system with multiple GPUs, you can define which GPU will be used by SceneKit. - If your system has multiple GPUs, you can set assign a boolean value to the
preferLowPowerDevice
option.
After creating a SceneKit view, you assign a scene to it, using the view scene
property. The SCNView
class provides different properties and methods. For example, you can use the allowsCameraControl
property to determine whether the user can manipulate the current point of view that is used to render the scene. If you set this property to true
, the user can modify the current point of view with the mouse or trackpad in macOS or with multitouch gestures on iOS. This action does not modify camera objects already existing in the scene graph or the nodes containing them. The default value of this property is false
.
If you need to smooth edges of a rendered scene, you can change the sampling rate of the rendering using the antialiasingMode
property. This property can be set to the following values:
none
(removes the multisampling)multisampling2X
multisampling4X
multisampling8X
multisampling16X
Multisampling renders each pixel multiple times and combines the results, creating a higher quality image at a performance cost proportional to the number of samples it uses.
The SCNView
class also provide the snapshot()
method to render the scene view in an image (UIImage
for iOS and NSImage
for macOS). Notice that this method is thread-safe and may be called at any time.
Build a scene
Let’s see how to build a very simple scene. Let’s create a new Xcode project. I am going to use Xcode 9 for this and next examples. Name the project SimpleScene. The first thing we do is to add a scene view to the storyboard. Open the Main.storyboard file and add a SceneKit view on top of the main view of the view-controller. Add the layout constraints to keep the scene view full screen. Now, go to the ViewController.swift and add import SceneKit
below the import UIKit
statement. Let’s create an outlet for the scene view:
1 2 3 4 5 6 |
@IBOutlet var sceneView: SCNView! { willSet { newValue.allowsCameraControl = true } } |
Connect the outlet to the scene view.
Now, let’s create a scene. In the finder of your mac, create a new folder. Rename the folder scene.scnassets. Drag and drop the folder in the Xcode project. Create a new SceneKit file.

Name it scene.scn and final drag it in to the recently created folder. This folder improves the performance of loading this file at runtime. In the viewDidLoad()
method, add the following lines of code:
1 2 3 4 5 6 7 8 |
guard let scene = SCNScene(named: "scene.scnassets/scene.scn") else { print("Impossible to load the scene") return } sceneView.scene = scene |
Finally, let’s add something to the scene. Open the scene.scn file. Drag and drop a sphere from the Object Library to the scene. If you click on the small button on the bottom left corner of the scene (see next figure), you will unveil the Scene graph. In our Scene graph, we can see 2 nodes: camera and sphere.

The camera is a special node to provide a point of view for the user. Without the camera, you cannot render the scene at runtime. When we created the outlet, we set the property allowsCameraControl
to true
to let the user control the camera using pan and drag gestures. When doing so, we are moving the camera in the 3D space.
Go to the scene and select the camera
node. Then, open the Node Inspector in Xcode (third tab in the Utilities panel). There, set the camera Position, Euler and Scale to the following values:

Now, select the sphere, and set its Position, Euler and Scale to the following values:

Finally, run the application. You should see the sphere on the screen.
The camera
The camera is a very important SceneKit node. Without the camera we cannot visualize the 3D world. To display a scene, you must designate a node whose camera
property contains a camera object as the point of view. Every SCNNode
object has a camera property that defines a point of view—that is, the position and orientation of the camera. The camera’s direction of view is always along the negative z-axis of the node’s local coordinate system. To point the camera at different parts of your scene, use the position
, rotation
, or transform
property of the node containing it.
The camera defines a region of the 3D space called frustum. Figure 6 highlights the important parts of the frustum.

As you can see in Figure 6, the camera is at the apex of the pyramid. You can set the camera field of view using the fieldOfView
property. Instead, you can set the camera near and the far depth limits, using the zNear
and zFar
properties. Starting with iOS 10, Apple introduced also other properties that make your camera simulate more realistic camera features. For example, you can decide to use an High Dynamic Range (HDR) camera and set different properties such as wantsHDR
, minimumExposure
, maximumExposure
, whitePoint
and so on. You can also adjust the contrast, saturation and color grading of the camera (contrast
, saturation
, colorGrading
). In iOS 11, Apple added additional properties such as focalLenght
, focusDistance
, sensorHeight
, fStop
, and more.
Geometry
I would like to highlight that the sphere node we used in the previous example is a node with a specific shape. Indeed, an SCNNode
does not have any specific shape. Instead, you need to set the geometry
property of a node to the specific shape you want. SceneKit provides built-in shapes:
- Floor (
SCNFloor
) - Box (
SCNBox
) - Capsule (
SCNCapule
) - Cone (
SCNCone
) - Cylinder (
SCNCylinder
) - Plane (
SCNPlane
) - Pyramid (
SCNPyramid
) - Sphere (
SCNSphere
) - Torus (
SCNTorus
) - Tube (
SCNTube
) - Text (
SCNText
) - Shape (
SCNShape
).
All these built-in shapes are subclasses of SCNGeometry
.
Let’s give a look at these geometries. The following picture highlights all the shapes available in SceneKit.

SCNFloor
A floor extends in the x- and z-axis dimensions of its local coordinate space, and is located in the plane whose y-coordinate is zero. You can set length and width for a floor, using the length
and width
properties. Other properties, such as reflectivity
, reflectionFalloffStart
, reflectionFalloffEnd
, and reflectionResolutionScaleFactor
, are used to control the reflectivity of the floor.
SceneKit creates a reflection effect by rendering the scene twice. First, it renders the scene into an offscreen buffer, using a point of view whose position is the reflection of the camera’s position. Next, it renders the scene from the camera’s point of view, using the offscreen buffer as a texture map for the floor’s surface. Rendering the scene twice incurs a performance cost. Reducing the resolution of the offscreen buffer reduces this cost but causes the reflected image to appear blurry.
SCNBox
A SCNBox
geometry defines the shape of the box in the x-, y-, and z-axis dimensions of its local coordinate space by setting its width
, height
, and length
properties. If you want to add rounded edges and corners to a box, you can use the chamferRadius
property. To position and orient a box in a scene, attach it to the geometry property of an SCNNode object.

SCNCapsule
A SCNCapsule
geometry defines the size of the two hemispheres forming the ends of a capsule with the capRadius
property. Because the cylindrical body of the capsule stretches between its two hemispherical ends, its circular cross section in the x- and z-axis dimensions has the same radius. You can define the capsule’s extent in the z-axis dimension of its local coordinate space with the height
property. To change the orientation of a capsule, you simply adjust the transform
property of the node containing the capsule geometry.

SCNCone
A cone defines the surface of a solid whose base is a circle and whose side surface tapers to a point centered above its base. You define the size of the cone’s base in the x- and z-axis dimensions of its local coordinate space with its bottomRadius
property, and its extent in the y-axis dimension with its height
property. You can create a cone that tapers to a point by setting its topRadius
property to zero. The following figure highlights cones with different topRadius
values.

SCNCylinder
A cylinder defines the surface of a solid whose every cross section along a linear axis is a circle of equal size. You can define the size of the cylinder’s cross section in the x- and z-axis dimensions of its local coordinate space with the radius
property, and its extent in the y-axis dimension with the height
property.

SCNPlane
A plane defines a flat surface in the x- and y-axis dimensions of its local coordinate space according to its width
and height
properties. To orient a plane differently, adjust the transform
property of the node containing the plane geometry. You can create a rounded rectangular plane using the cornerRadius
property.

SCNPyramid
A pyramid defines the surface of a solid whose base is a rectangle, and whose four triangular side faces converge at a point centered above its base. You define the shape of the pyramid’s base in the x- and z-axis dimensions of its local coordinate space with the width
and length
properties, and its extent in the y-axis dimension with the height
property.

SCNSphere
A sphere defines a surface whose every point is equidistant from its center, which is placed at the origin of its local coordinate space. You define the size of the sphere in all three dimensions using its radius
property.
If you set the sphere’s isGeodesic
property to true, SceneKit constructs the sphere by successively subdividing the triangular surfaces of a regular icosahedron.
SCNTorus
A torus is mathematically defined as a surface of revolution formed by revolving a circle around a coplanar axis. It is the product of two circles: a large ring and a pipe that encircles the ring. SceneKit uses these terms to define the dimensions of a torus geometry in its local coordinate space. The torus’ ringRadius
property defines a circle in the x- and z-axis dimensions, centered at the origin, and its pipeRadius
property defines the width of the surface encircling the ring.

SCNTube
The outer surface of a tube is a cylinder. Define the size of the cylinder’s cross section in the x- and z-axis dimensions of its local coordinate space with the outerRadius
property, and its extent in the y-axis dimension with the height
property.

SCNText
You provide text for the geometry using an NSString
or NSAttributedString
object. In the former case, the properties of the SCNText
object determine the style and formatting of the entire body of text. When you create a text geometry from an attributed string, SceneKit styles the text according to the attributes in the string, and the properties of the SCNText object determine the default style for portions of the string that have no style attributes. SceneKit can create text geometry using any font and style supported by the Core Text framework, with the exception of bitmap fonts (such as those that define color emoji characters).
In the local coordinate system of the text geometry, the origin corresponds to the lower left corner of the text, with the text extending in the x- and y-axis dimensions. The geometry is centered along its z-axis.

SCNShape
SceneKit can create a 3D geometry by extruding a Bézier path, which extends in the x- and y-axis directions of its local coordinate space, along the z-axis by a specified amount.
This geometry is not available in the SceneKit editor. So, you can only build it programatically. You create this geometry using the init(path:extrusionDepth:)
method, passing a Bezier path and the amount of extrusion. After that, you can chamfer the resulting 3D shape in different way using the chamferMode
, chamferProfile
and chamferRadius
properties.

Conclusions
In this post, we look at some of the main classes in SceneKit. We just touched the surface of this framework that offers a huge set of functionalities. We will cover more about this framework in next posts. So, stay tuned.
Geppy
Geppy Parziale (@geppyp) is cofounder of InvasiveCode. He has developed many iOS applications and taught iOS development to many engineers around the world since 2008. He worked at Apple as iOS and OS X Engineer in the Core Recognition team. He has developed several iOS and OS X apps and frameworks for Apple, and many of his development projects are top-grossing iOS apps that are featured in the App Store. Geppy is an expert in computer vision and machine learning.