Scene Kit Tutorial: Getting Started
Learn how to easily create 3D scenes in your iOS apps or games in this Scene Kit tutorial! By Ricardo Rendon Cepeda.
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
Scene Kit Tutorial: Getting Started
30 mins
Lights, Camera, Action!
The built-in SCNView
features are very useful, but also rather limited. In order to fully customize and control your scene, you must learn how to create and add multiple nodes to your scene graph, of various different types.
As film directors would say, “Lights, camera, action!” :]
Add the following lines to sceneSetup
, just below let scene = SCNScene()
:
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = SCNLightTypeAmbient
ambientLightNode.light!.color = UIColor(white: 0.67, alpha: 1.0)
scene.rootNode.addChildNode(ambientLightNode)
Here, you’re creating a new ambient light node with a 67% white color, then adding it to your scene.
Ambient light is a basic form of lighting that illuminates all objects in a scene evenly, with a constant intensity from all directions, much like cloud-filtered sunlight. It’s a great addition to most scenes when you want a more natural look. Otherwise, objects in shadow would be pure black and that’s not the desired effect in most cases – especially when you build a model-viewer type of app, like the one in this tutorial.
Although ambient light is part of the lighting equation, it’s not very useful on its own because it does little to illuminate surface details. So, add one more light to your scene with the following lines, just after your previous addition:
let omniLightNode = SCNNode()
omniLightNode.light = SCNLight()
omniLightNode.light!.type = SCNLightTypeOmni
omniLightNode.light!.color = UIColor(white: 0.75, alpha: 1.0)
omniLightNode.position = SCNVector3Make(0, 50, 50)
scene.rootNode.addChildNode(omniLightNode)
This new type of light is called an omnidirectional light, or point light. It’s similar to ambient light in that it has constant intensity, but differs because it has a direction. The light’s position relative to other objects in your scene determines its direction. Your box node’s default position is (0, 0, 0), so positioning your new light node at (0, 50, 50) means that it’s above and in front of your box.
Note: Don’t worry too much about units at this point. All units are relative to the scene and defined by your own set of rules. A position of (0, 50, 50) doesn’t mean 50 meters, miles, or light years; it’s simply 50 units. You’ll define a proper unit of measurement shortly.
Note: Don’t worry too much about units at this point. All units are relative to the scene and defined by your own set of rules. A position of (0, 50, 50) doesn’t mean 50 meters, miles, or light years; it’s simply 50 units. You’ll define a proper unit of measurement shortly.
Now that you’ve set up some lights, delete the following line from sceneSetup
:
sceneView.autoenablesDefaultLighting = true
Build and run! Pinch to zoom out, and you’ll find your box properly illuminated:
Behold your amazing and talented box! For this tutorial, these two sources will do just fine, but be sure to experiment with directional (SCNLightTypeDirectional
) and spot (SCNLightTypeSpot
) lights in future projects.
Next, set up a camera by adding the following lines just below where you setup the lights in sceneSetup
:
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 0, 25)
scene.rootNode.addChildNode(cameraNode)
Just like the light nodes, a camera node is just as easy – if not easier – to set up. The default projection settings are spot-on for a basic scene, so you don’t have to modify the camera’s field of view, focal range or other properties. The only thing you need to define is its position, conveniently set right in front of your box at (0, 0, 25).
Build and run to “get behind the lens.”
Note: The default projection type of a SCNCamera
is perspective, but you can easily change this to orthographic projection by enabling usesOrthographicProjection
and setting an appropriate orthographicScale
value.
Note: The default projection type of a SCNCamera
is perspective, but you can easily change this to orthographic projection by enabling usesOrthographicProjection
and setting an appropriate orthographicScale
value.
Now, if you navigate your scene with the default camera controls (rotation, specifically), you’ll notice an odd effect: you might expect the box to rotate and the lighting to stay in place but, in fact, it’s actually the camera rotating around the scene. That’s why you see the box from different points of view.
The default camera controls are very powerful, but not customizable. In order to gain total control, you’ll create your own. First, add the following variables to ViewController.swift, just above viewDidLoad
:
// Geometry
var geometryNode: SCNNode = SCNNode()
// Gestures
var currentAngle: Float = 0.0
Dealing with a single box is simple enough, but once you have more complex models with multiple geometry sources, it’ll be much easier to manage them as a single node – hence the addition of geometryNode
. Furthermore, currentAngle
will help modify the y-axis rotation of geometryNode
exclusively, leaving the rest of your scene nodes untouched.
Next up, add the following function below sceneSetup
:
func panGesture(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view!)
var newAngle = (Float)(translation.x)*(Float)(M_PI)/180.0
newAngle += currentAngle
geometryNode.transform = SCNMatrix4MakeRotation(newAngle, 0, 1, 0)
if(sender.state == UIGestureRecognizerState.Ended) {
currentAngle = newAngle
}
}
Whenever sceneView
detects a pan gesture, this function will be called, and it transforms the gesture’s x-axis translation to a y-axis rotation on the geometry node (1 pixel = 1 degree).
In this implementation, you modify the transform property of geometryNode
by creating a new rotation matrix, but you could also modify its rotation
property with a rotation vector. A transformation matrix is better because you can easily expand it to include translation and scale.
Next, add the following lines to sceneSetup
, just above sceneView.scene = scene
:
geometryNode = boxNode
let panRecognizer = UIPanGestureRecognizer(target: self, action: "panGesture:")
sceneView.addGestureRecognizer(panRecognizer)
These lines finalize the setup by connecting your new functions and variables. Finally, remove the default camera controls by deleting the following line:
sceneView.allowsCameraControl = true
Build, run, action! Now try rotating your cube with a one-finger pan.
Much better :]
Note: A full 3D transformation engine with gestures is far too complex for this tutorial, but definitely worth a look. Luckily, for you, it’s something we’ve covered on our site before! Check out OpenGL ES Transformations with Gestures for the implementation details.
Note: A full 3D transformation engine with gestures is far too complex for this tutorial, but definitely worth a look. Luckily, for you, it’s something we’ve covered on our site before! Check out OpenGL ES Transformations with Gestures for the implementation details.