SceneKit 3D Programming for iOS: Getting Started
Learn how to use SceneKit for 3D graphics programming by building an app modeling the solar system. By Keegan Rush.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
SceneKit 3D Programming for iOS: Getting Started
30 mins
- Getting Started
- Exploring Solar Scenes
- Creating Your First Scene
- Loading a Scene
- Adding Objects
- Modifying Materials
- Setting Up the Camera Node
- Creating Planets
- Adding Saturn’s Ring
- Adding the Ring
- Reusing Material
- Attaching a Light
- Adding Textures
- Replacing Colors with Textures
- Using Skybox Textures
- Working With the Camera
- Using Constraints
- Animating With Actions
- Focusing on the Selected Planet
- Where to Go From Here?
Have you ever wanted to make your own video game? Or maybe you want to make your iOS app stand out by adding beautiful 3D graphics? The world of 3D graphics programming can be intimidating. Between shaders, samplers, mipmaps and tessellation, it’s hard to know where to start. Fortunately, on iOS, you can hit the ground running with SceneKit, Apple’s high-level API for 3D programming!
SceneKit handles a lot of the tedious work of 3D programming for you, allowing you to concentrate on the content that makes your game or app unique. In low-level APIs like Metal, you’re left to grapple with physics and mathematics. SceneKit, on the other hand, abstracts away a lot of this complexity, making it easy to program your scene in code.
Alternatively, you can use Xcode’s powerful Scene Editor to lay out scenes, similar to using Interface Builder for storyboards. What’s more, SceneKit integrates smoothly with everything you might need to create your blockbuster video game: Metal, GameplayKit, Model I/O and more!
In this tutorial, you’ll propel a bland 2D solar system app into an interactive 3D world using SceneKit. Along the way, you’ll learn about:
- The fundamentals of 3D programming.
- Creating and modifying scenes in code or in the Scene Editor.
- Nodes, geometries and materials.
- How lighting determines the look of your scene.
- Changing what the user sees with cameras and constraints.
Get ready to step into the world of 3D graphics!
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.
Open the starter project in Xcode, then build and run.
Solar Scenes is a beautiful, informative app designed to teach people facts about the solar system they live in. At least, it will be when you’re done with it.
Right now, it’s missing the beautiful part. It’s your job to add it. :]
Solar Scenes covers only five planets so far:
- Mercury
- Venus
- Earth
- Mars
- Saturn
When you’re finished with this tutorial, you’ll be ready to add the missing planets — and maybe a few extra creations of your own!
Exploring Solar Scenes
In Xcode, take a look at the structure of the app in the Project navigator.
Here’s what each group does:
- Data Model: Hosts the different planets and some facts about them.
- UI: Contains various SwiftUI controls that style the current UI.
- ContentView.swift: With help from ViewModel.swift, this is the body of the app., Here, you’ll load the SceneKit scene that makes the app come to life.
Creating Your First Scene
To create your scene, you’ll use Xcode’s Scene Editor to manage a scene file. In Xcode’s menu bar, select File ▸ New ▸ File… and select the SceneKit Scene File template.
Click Next, and name the file Solar Scene.
Then, click Create. Xcode’s Scene Editor will load your first blank scene file.
If you don’t see the camera node listed on the left, open the Screen graph by selecting the Scene Graph View button in the lower-left corner of the scene:
SceneKit uses scene graphs to organize scenes as a tree of nodes. The scene graph starts with a root node, and you add any content for your scene as child nodes under that root node.
A node is nothing more than a location; it has no behavior or appearance. Despite this, nodes are the heart of your scene. To add anything to your scene, you’ll need to attach it to a node.
Your scene graph already comes with one child node, which has one attachment:
The camera node has a camera object attached to it. You can have as many cameras in your scene as you want, but you’ll need at least one to see anything.
Loading a Scene
To see your scene in action inside the app, you’ll need to create a SceneView. Open ContentView.swift.
First, import SceneKit at the top of the file:
import SceneKit
Next, add this inside ContentView
:
// 1
static func makeScene() -> SCNScene? {
let scene = SCNScene(named: "Solar Scene.scn")
return scene
}
// 2
var scene = makeScene()
In the code above, you:
- Create your scene from your scene file.
- Call
makeScene()
to load your scene.
Now, you need to get a reference to your camera node. Below body
, just before the closing brace of ContentView
, add this:
func setUpCamera(planet: Planet?) -> SCNNode? {
let cameraNode = scene?.rootNode
.childNode(withName: "camera", recursively: false)
return cameraNode
}
You’ll expand on this later. For now, setUpCamera(planet:)
simply grabs a reference to your camera node.
Finally, you’re ready to create your scene view. Near the start of body
, remove ColorPalette.secondary
and its view modifier. Then, add this:
SceneView(
// 1
scene: scene,
// 2
pointOfView: setUpCamera(planet: viewModel.selectedPlanet),
// 3
options: [.allowsCameraControl]
)
// 4
.background(ColorPalette.secondary)
.edgesIgnoringSafeArea(.all)
You’ve replaced the blank background of the app with a SceneView
. Here’s what’s happening, step by step:
- Choose the scene for the view to display.
- The scene view’s
pointOfView
is the camera that will display the scene. Some games swap between multiple cameras by using this property to change the current point of view. - You can control
SceneView
‘s behavior using a set of options. Here,.allowsCameraControl
lets the user manipulate the camera. - Set the scene view’s background color, which you’ll see while the scene loads, and stretch the scene across the entire window.
At last, you’re ready to see your first scene! Build and run.
It’s not much, but it’s pretty impressive considering how little code you’ve written so far. Solar Scene.scn provides a default background and camera, and your scene view’s allowsCameraControl
option lets you scroll and zoom around the scene freely. Best of all, you accomplished this without having to write a single line of code.
Right now, you have a camera, but nothing to see. Next, you’ll add a sun to test your scene.
Adding Objects
Open Solar Scene.scn. At the top right of the Scene Editor, click the plus (+) button to access the Object Library.
Find the Sphere object and drag it into your scene graph, just below the camera node.
camera
before dragging if your new sphere won’t stick.With sphere
selected, click it and change its name to sun. If you can’t see it in the scene editor, zoom in or out to to get a good view of your sun:
It doesn’t look like much. Right now, your sun is a simple node with a geometry object attached.
Remember, nodes have no appearance or behavior by default. SceneKit includes a variety of geometric shapes that you can use to give nodes an appearance, but you’re not limited. Using Metal or an external 3D modeling tool, you can create your own custom geometries.
While you’ve made a sphere, it doesn’t look much like a sun. So, to give your sun a more fitting appearance, you’ll modify its material.
Modifying Materials
On the right of the Scene Editor, in the Inspectors panel, select the Materials inspector.
An object’s material is a set of properties that, when combined with the material’s lighting model, determine how to render each pixel of a geometry. Here, you can change the properties that determine the color of the sun’s sphere geometry.
Next, click the Diffuse color to bring up the Color picker. Navigate to the Sliders tab, then set the drop-down to RGB Sliders. Finally, set the Hex Color # to #F2FF2C.
You can think of a material’s diffuse as the “base color” of an object.
Next, set the color of Illumination to white: #FFFFFF. A material’s illumination lets it define how light hits the object. Even if the geometry is obscured from a light source, setting illumination causes the material to color itself as if it were receiving light.
Build and run.
If you don’t see anything, pan the camera until your sphere comes into view.
Behold, the center of our solar system: the sun!
However, it’s a little dainty at the moment. Next, you’ll make it bigger to provide some scale for the other planets.
In the Inspectors panel, click the Attributes inspector. Here, you can control a variety of properties for the node and any of its attachments, like a sphere’s radius.
Change the radius to 10
, which will increase the sphere’s size tenfold. Build and run.
Wait, where’d it go? Panning the camera won’t help this time. You’ve increased the size of the sun, and it’s engulfed the camera. You’ll need to move the camera to a safe distance.
Setting Up the Camera Node
In the scene graph, click the camera node. Then, in the Inspectors panel, click the Node inspector.
Here, you can set the position, shape and orientation of the node. How you orient the node will affect anything attached to that node. So, changing the position of the camera node will change the position of the camera attached to it.
Every node in your scene has the following properties, which the Node inspector can edit:
- Identity: The node’s name, used to access the node in code.
- Position: Where the node is placed in the scene relative to its parent.
- Euler: The rotation of the node relative to its parent.
- Scale: Allows you to transform the node, transforming its size along each axis.
To correctly reposition the camera, change Position to x: -55, y: 65 and z: -68.
Then, change Euler to x: 145, y: -13 and z: -158. This orients the camera to point at your sun. It also creates an empty space that your planets will fill.
Now, your transforms will look like this:
Zoom out of the Scene Editor until you can clearly see the camera and the sun.
Your camera is pointing at your scene, but its viewing depth isn’t far enough to see your sun. In the Inspector panel, switch to the Attributes inspector. Here, you’ll find the Z Clipping properties, which let you increase the viewing depth.
Under Z Clipping, change Far to 300.
Build and run.
Now, you can see the sun, along with plenty of space for more planets!
Creating Planets
Next, you’ll add five planets to your solar system:
- Mercury
- Venus
- Earth
- Mars
- Saturn
Each is a sphere, like the sun. You’ll change details like each planet’s color, size and position.
Mercury
First, add a new sphere geometry from the Objects Library.
Then, in the Node inspector, change the Name to mercury. Change the Position to x: 0, y: 0 and z: 25.
In the Material inspector, change the Diffuse to #BBBBBB by using the color picker. Then, change Roughness to 1. By changing a material’s roughness, you can make it more or less shiny. Setting a roughness close to 0 makes it shiny and setting a roughness close to 1 makes it less reflective.
Build and run.
SceneKit renders your first planet, Mercury, close to the sun. Next, you’ll add the rest of the planets in order, following the steps you followed for Mercury.
Venus
To create Venus, do the following:
- Add a sphere to the scene graph.
- In the Node inspector, change Name to venus. Change Position‘s z value to 35.
- In the Attributes inspector, change Radius to 2.
- In the Material inspector, change Diffuse to #59B1D6 and Roughness to 1.
Earth
For the planet we call home, follow these steps:
- Add a sphere to the scene graph.
- In the Node inspector, change Name to earth. Change Position‘s z value to 50.
- In the Attributes inspector, change Radius to 2.
- In the Material inspector, change Diffuse to #2F5CD6 and Roughness to 1.
Mars
Next, make these changes for the red planet:
- Add a sphere to the scene graph.
- In the Node inspector, change Name to mars. Change Position‘s z to 75.
- In the Material inspector, change Diffuse to #C65B2C and Roughness to 1.
Build and run.
Your solar system is taking shape, but it’s still a bit bland. For some variation, you’ll add everyone’s favorite gas giant: Saturn!
Adding Saturn’s Ring
Saturn follows a similar pattern to the previous planets, but you’ll take it one step further: Saturn needs a ring.
First, create the body of the planet, just as you did for the previous planets:
- Add a sphere to the scene graph.
- In the Node inspector, change Name to saturn. Change Position‘s z to 150.
- In the Attributes inspector, change Radius to 5.
- In the Material inspector, change Name to saturn, Diffuse to #D69D5F and Roughness to 1.
Pan the Scene Editor to take a closer look at your newest planet.
Saturn is missing its trademark ring. To add the ring, you’ll use another of SceneKit’s primitive shapes: a tube.
Adding the Ring
From the Object Library, find Tube and add it to the scene graph. Then, drag the tube so it nests under saturn
, like so:
While all the planets are child nodes of the scene’s root node, you’ve added the tube as a child node of saturn
. Now, you can position and animate the tube relative to its parent.
Think of this as adding car seats as children to a car node: if the car moves, the child nodes go along for the ride.
In the Node inspector, change the tube’s Position to x: 0, y: 0 and z: 0. This will center it on Saturn’s position.
Because it’s smaller than Saturn’s sphere, you won’t be able to see the tube. To size the tube appropriately, open the Attributes inspector. Then, make the following changes:
- Inner radius: 7
- Outer radius: 9
- Height: 0.1
Now, your ring is positioned correctly, but it doesn’t match the rest of the planet. You could modify the material to match, or you could reuse the material you already styled for Saturn.
Reusing Material
When you’re making a large-scale scene, the ability to reuse objects is critical. In SceneKit, you can reuse nodes, geometries and materials. That way, if something has to change across the scene, you’ll only need to make the change once.
Right now, if you wanted to change Saturn’s color or anything else related to its material, you’d have to duplicate the changes in both the body of the planet and its ring. By sharing a material object, the planet and its ring will share the same appearance.
With the tube selected in the scene graph, open the Material inspector. At the top, you’ll find a collection of materials.
For Saturn’s ring, you only want one material that matches that of the planet’s body. So, click the minus (—) button to remove the tube’s material.
Then, click the plus (+) button to bring up the materials picker.
Find and click the saturn material, then click Done.
Now, Saturn’s ring shares the same material as the planet’s body.
Build and run to see all the planets.
Your planets look great, but something’s missing. Your scene has light, but it looks a bit off — there’s no light coming from the sun. Next, you’ll fix that by adding a light to the sun node.
Attaching a Light
When you create a scene from a scene file, as you did with Solar Scene.scn, SceneKit gives you a running start by giving you a default camera, light and background. All you need to do is add your geometry.
In the scene graph, right-click the sun node.
In this menu, you can choose between a collection of attachments: lights, cameras, physics bodies and more. For now, click Add Light.
That little sun icon next to the sun
node (how appropriate!) indicates the node has a light attachment. Similarly, the cube icon indicates a geometry attachment.
Click the sun
node. Then, in the Inspectors panel, click the Attributes inspector.
Earlier, you only had properties applicable to the attached geometry, but now you also have a new section for the attached light.
First, change the light’s Type to Omni, short for “omnidirectional”. This is a light type that shines in all directions. Then, change the Intensity to 20000
. It is the sun, after all! :]
Build and run.
After that change, your whole solar system is basking in the sun’s warm glow.
You’ve gone as far as you need to in Solar Scene.scn. To really unlock the power of SceneKit, it’s time to start adding to your scene in your Swift code.
Adding Textures
So far, you’ve colored each planet with a single, simple color. Earth looks a little bland as a result, despite being known as the “blue planet.” To kick things up a notch, you’ll apply a texture to each planet instead of a color.
Textures are images that you wrap around geometries, such as your planet spheres. Remember those various color properties in the Material inspector? You can apply texture images to each of those properties as well.
In Xcode, open Assets.xcassets and look at the planets group.
Each planet has an artificial map of its surface. When creating your scene, you’ll use these images to apply textures to each planet.
Replacing Colors with Textures
First, open ContentView.swift. Then, add this below makeScene()
:
static func applyTextures(to scene: SCNScene?) {
// 1
for planet in Planet.allCases {
// 2
let identifier = planet.rawValue
// 3
let node = scene?.rootNode
.childNode(withName: identifier, recursively: false)
// Images courtesy of Solar System Scope https://bit.ly/3fAWUzi
// 4
let texture = UIImage(named: identifier)
// 5
node?.geometry?.firstMaterial?.diffuse.contents = texture
}
}
Here’s what’s happening, step by step:
- The
Planet
enumeration lists all planets in the app. - Each planet’s
rawValue
is the same as the identifiers you’ve been applying to the planets:mercury
,venus
and so on. - Using the identifier, grab a reference to the planet’s node.
- The names of the textures in Assets.xcassets also match the planet identifiers. This creates a
UIImage
for the appropriate planet. - Finally, set the image as the planet’s
diffuse
on the node’s material, thereby replacing the color that served as its base appearance.
To apply your textures, add this to makeScene()
, right before returning the scene:
applyTextures(to: scene)
This calls applyTextures(to:)
while creating your scene.
Build and run. Then, take a closer look at each planet:
That looks much better. Textures are a great way to add realism to your scene. Next, to really get that “space” feeling, you’ll add one more texture.
Using Skybox Textures
Your planets are in great shape, but looking at the rest of your scene, it doesn’t look much like space. In Xcode, open Assets.xcassets and look in the skybox group.
There’s not one, not two, but six images of a starry sky. Perfect for the scene’s background! But, why six? Well, SceneKit can use these six images for a common 3D programming technique known as a skybox. By using these images as the scene’s background, SceneKit makes a cube out of the six images and essentially puts the entire scene in the cube.
At the bottom of applyTextures(to:)
, add this:
// 1
let skyboxImages = (1...6).map { UIImage(named: "skybox\($0)") }
// 2
scene?.background.contents = skyboxImages
Here’s what’s going on:
- Create an array of the six skybox images.
- Use those images as the scene’s background contents.
SceneKit will handle the rest from here, as the framework knows to apply an array of six images as a skybox.
Finally, build and run to see what those two lines of code can do.
No matter how you scroll or pan the image, you’re surrounded by a deep, starry sky. By using a skybox texture, you’ve shot your scene into outer space!
Your scene is finally taking shape. All that remains are some tweaks in how the app interfaces with SceneKit. For instance, the original app that shows planet info has no real link to the SceneKit scene. It’s time to change that.
Working With the Camera
When you select a different planet in Solar Scenes’ planet switcher, it’d be great if the scene focused on the selected planet. At the moment, SceneKit controls the camera, but you can move it programmatically as well. To focus the camera on the selected planet, you’ll use a constraint.
Using Constraints
Similar to Auto Layout constraints, SceneKit’s SCNConstraint
and its subclasses let you specify rules to apply to the position and orientation of any node.
For example, SCNDistanceConstraint
can force a node to maintain a specified distance from another node. Using SCNBillboardConstraint
, you can force a node to face the camera, like a creepy painting that seems to follow you wherever you stand.
For the selected planet, you’ll use SCNLookAtConstraint
to force the camera to look at the selected planet node. To smooth the camera’s change in orientation, you’ll animate it using an action.
Animating With Actions
Actions are a method of performing simple animations. By running an action, a node can smoothly change its position and orientation, scale to a different size and more.
Next, you’ll use an action to animate the camera’s position change when switching planets in Solar Scenes, and you’ll use a constraint to focus on the planet.
Focusing on the Selected Planet
First, add this to ContentView.swift just before the closing brace:
func planetNode(planet: Planet) -> SCNNode? {
scene?.rootNode.childNode(withName: planet.rawValue, recursively: false)
}
This grabs the planet node corresponding to a Planet
model object.
Next, find setUpCamera(planet:)
. Add this before the return
statement at the end:
// 1
if let planetNode = planet.flatMap(planetNode(planet:)) {
// 2
let constraint = SCNLookAtConstraint(target: planetNode)
cameraNode?.constraints = [constraint]
// 3
let globalPosition = planetNode
.convertPosition(SCNVector3(x: 50, y: 10, z: 0), to: nil)
// 4
let move = SCNAction.move(to: globalPosition, duration: 1.0)
cameraNode?.runAction(move)
}
Here’s what’s happening:
-
ContentView
callssetUpCamera(planet:)
when setting up the view body, passing the currently selected planet from the view model. Here, you’re getting a reference to that planet’s node usingplanetNode(planet:)
. - Create an
SCNLookAtConstraint
, which focuses onplanetNode
, then apply it tocameraNode
‘s constraints. - Nodes define their own coordinate space. So, use
convertPosition(_:to:)
to convert a position that’s close to the planet into the same position in the global coordinate space. - Using the global position, create and run an action to update
cameraNode
‘s position.
Finally, when the camera is focusing on a planet, you should disable SceneKit’s automatic camera control. Go to the initialization of SceneView
in body
. Find options
and replace it with this:
options: viewModel.selectedPlanet == nil ? [.allowsCameraControl] : []
Here, you’re removing automatic control of the camera if you selected a planet.
Build and run. Flip through the planets in the planet switcher and enjoy that smooth animation as the camera focuses on the selected planet.
Great work!
Where to Go From Here?
You can download the final project by using the Download Materials button at the top or bottom of this page.
SceneKit is a powerful, yet accessible, entry point into 3D programming. If you want to learn more about SceneKit, Apple’s documentation and WWDC videos will be your guide.
In this tutorial, you took a simple app and added a fully interactive 3D scene. You made visual representations for planets in the solar system rather than just using a plain old 2D interface.
Next, why not add the missing planets to Solar Scenes? Or, you could bring the scene into the real world by supporting augmented reality. Look at our book, Apple Augmented Reality by Tutorials, to learn how.
If you have questions or comments, or if you’d like to share the cool scenes you’re making in SceneKit, please join the discussion below.