Advanced Git, Second Edition

Git is key to great version control and collaboration on software projects.
Stop struggling with Git and spend more time on the stuff that matters!

Home iOS & Swift Tutorials

SceneKit 3D Programming for iOS: Getting Started

Learn how to use SceneKit for 3D graphics programming by building an app modeling the solar system.

4.9/5 9 Ratings

Version

  • Swift 5, iOS 14, Xcode 12

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 starter

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.

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.

New file dialog

Click Next, and name the file Solar Scene.

New file name

Then, click Create. Xcode’s Scene Editor will load your first blank scene file.

Scene editor

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:

Scene graph button in Xcode

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:

Camera node

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:

  1. Create your scene from your scene file.
  2. 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:

  1. Choose the scene for the view to display.
  2. 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.
  3. You can control SceneView‘s behavior using a set of options. Here, .allowsCameraControl lets the user manipulate the camera.
  4. 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.

Running empty scene

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.

Object library

Find the Sphere object and drag it into your scene graph, just below the camera node.

Sphere object

Note: Dragging objects into the scene graph can be tricky. Try selecting 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:

Sun in scene editor

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.

Material 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.

Running the scene, but can't see the sun.

If you don’t see anything, pan the camera until your sphere comes into view.

The sun 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.

The sphere's radius property

Change the radius to 10, which will increase the sphere’s size tenfold. Build and run.

No sun, again

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.

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:

Transforms in Node inspector after update

Zoom out of the Scene Editor until you can clearly see the camera and the sun.

Camera and sun inside scene editor

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.

Z clipping property

Build and run.

The sun in the running scene

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:

  1. Mercury
  2. Venus
  3. Earth
  4. Mars
  5. Saturn

Each is a sphere, like the sun. You’ll change details like each planet’s color, size and position.

Note: Most of the following steps repeat what you did to create the sun. If you need a refresher, look back at the Adding Objects section.

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.

Node inspector for mercury

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.

Mercury in the running app

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:

  1. Add a sphere to the scene graph.
  2. In the Node inspector, change Name to venus. Change Position‘s z value to 35.
  3. In the Attributes inspector, change Radius to 2.
  4. In the Material inspector, change Diffuse to #59B1D6 and Roughness to 1.

Earth

For the planet we call home, follow these steps:

  1. Add a sphere to the scene graph.
  2. In the Node inspector, change Name to earth. Change Position‘s z value to 50.
  3. In the Attributes inspector, change Radius to 2.
  4. In the Material inspector, change Diffuse to #2F5CD6 and Roughness to 1.

Mars

Next, make these changes for the red planet:

  1. Add a sphere to the scene graph.
  2. In the Node inspector, change Name to mars. Change Position‘s z to 75.
  3. In the Material inspector, change Diffuse to #C65B2C and Roughness to 1.

Build and run.

The four planets in the scene editor

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:

  1. Add a sphere to the scene graph.
  2. In the Node inspector, change Name to saturn. Change Position‘s z to 150.
  3. In the Attributes inspector, change Radius to 5.
  4. In the Material inspector, change Name to saturn, Diffuse to #D69D5F and Roughness to 1.
Note: Don’t forget the set Name in both the Node inspector and the Material inspector this time. You’ll use it later.

Pan the Scene Editor to take a closer look at your newest planet.

Saturn in scene editor

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:

The tube as a child of Saturn in the scene graph

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

Sizing properties in Saturn's Attributes inspector

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.

Materials collection for the tube

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.

Empty materials collection

Then, click the plus (+) button to bring up the materials picker.

Materials picker

Find and click the saturn material, then click Done.

The Saturn material in the tube's Material inspector

Now, Saturn’s ring shares the same material as the planet’s body.

Build and run to see all the planets.

All planets in the running app

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.

Right-click context menu on 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.

Light attachment on the sun node

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.

Properties for the sun's light in 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.

Sunlight in the running app

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.

Planet texture maps in the assets catalog

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:

  1. The Planet enumeration lists all planets in the app.
  2. Each planet’s rawValue is the same as the identifiers you’ve been applying to the planets: mercury, venus and so on.
  3. Using the identifier, grab a reference to the planet’s node.
  4. The names of the textures in Assets.xcassets also match the planet identifiers. This creates a UIImage for the appropriate planet.
  5. 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:

The earth texture in the running app

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.

Skybox images in the assets catalog

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:

  1. Create an array of the six skybox images.
  2. 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.

The skybox applied in the running app

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:

  1. ContentView calls setUpCamera(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 using planetNode(planet:).
  2. Create an SCNLookAtConstraint, which focuses on planetNode, then apply it to cameraNode‘s constraints.
  3. 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.
  4. 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.

The completed running app

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.

Average Rating

4.9/5

Add a rating for this content

9 ratings

More like this

Contributors

Comments