Metal Integration With SceneKit: SCNShadable Fragment Injection

I spent a year working on The Metal Programming Guide for Pearson. Working on this book was a tremendous privilege and I feel quite fortunate to have had the opportunity to work on it. However, while I worked on the book, I kept feeling there had to be a better way to do a lot of the things I wrote about in the book. I didn’t think you should need a hundred lines of code to output a box to the screen.

Everyone I talk to (myself included) who thinks about learning Metal tends to want to do so because we like to think of ourselves as low level programmers. We’ve been told Metal is difficult and we want to make it our bitch. We feel that way until we realize just how many things you have to set up that are the same for nearly everything you are doing. You spend a week trying to get a triangle on the screen. It seems excessive and you wonder why you need to keep writing the same set up code over and over again. Why hasn’t someone created an open source framework to make all this crap easier?

No one has created an open source framework because it already exists.

Apple has two game frameworks that do 2D and 3D graphics specifically for game development. SpriteKit was announced with iOS 7 and SceneKit was originally announced for the Mac. Neither has gotten quite the amount of traction I think they both should have gotten and now they’re old news and not new and shiny any longer. However, they do serve an important purpose.

One major advantage of SceneKit is that it takes care of a lot of boring low level stuff that is tedious to do in Metal. It is fairly simple to set up a lot of work easily in SceneKit and drop down to Metal only when you need to. SceneKit has a lot of functionality that is easily extended by knowing a little bit of Metal.

There are three levels of Metal shader integration with SceneKit:

  • SCNShadable
  • SCNProgram
  • SCNTechnique

SCNShadable is the easiest way to quickly integrate Metal shader code into a SceneKit project. SCNShadable is a protocol that all built in SCNGeometry objects conform to. SceneKit has an extensive library of primitive SCNGeometry objects that you may use individually or combined to create more complex shapes. This means that if you want to insert a small bit of Metal code to a cone or a plane, you can create one in SceneKit and inject shader code into the rendering pipeline without being responsible for setting up the whole thing.

SCNShaderModifierEntryPoint allows you to customize SceneKit at four different points in the rendering pipeline:

  • Geometry
  • Surface
  • Lighting Model
  • Fragment

The remainder of this blog post will focus on the final option, which is the fragment shader. Not only is the fragment shader the most focused on point in the rendering pipeline, it also happens to be the simplest one for us to hack.

Setting up SceneKit

Since most people are relatively unfamiliar with SceneKit, I will go over the steps you need to take to put together about the most basic SceneKit project possible. The first property you must have for a SceneKit project is an SCNView:

// Properties
var scnView: SCNView!

I like to create functions to set up the various properties on their own, so here is the code to set up the SCNView:

// Setup Functions
func setupView() {
    scnView = self.view as! SCNView
    scnView.showsStatistics = true
    scnView.allowsCameraControl = true
    scnView.autoenablesDefaultLighting = true

The bottom three method calls are not really necessary, but they are nice to add unless you know you absolutely don’t need them. For example, if you were going to roll your own lighting, you probably would not want to auto-enable default lighting. Since that’s a more advanced move, let’s just let SceneKit handle that for now.

Next, you need an SCNScene:

var scnScene: SCNScene!

As before, you need to set up this scene in a method:

func setupScene() {
    scnScene = SCNScene()
    scnView.scene = scnScene
    scnScene.background.contents = UIColor.purple

Here, you are initializing the SCNScene and ensuring that the scnView object has set the newly initialized scnScene as its scene. I am also specifying for the background color to be purple. If you don’t want your background to be purple, then there is nothing I can do to help your taste level or bring joy to your life.

Finally, we need to be able to see what is going on in our scene, so we need a camera. The camera is an attribute on SCNNode so rather than initialize a straight SCNCamera you need to create a node for it to be attached to:

var cameraNode: SCNNode!

Finally you set up the camera node:

func setupCamera() {
    cameraNode = SCNNode() = SCNCamera()
    cameraNode.position = SCNVector3(x: 0, y:0, z: 10)

The camera is placed directly in front of but slightly up from the origin of the scene. It is then added as a child to the scnScene.

If you run this code right now none of this shows up. We haven’t called any of these set up methods yet, but more importantly, we haven’t create any geometry yet. That is what we’re doing next.

Adding the SCNGeometry Object and Custom Fragment Shader

SceneKit has a large variety of built in primitive objects. These include:

  • Boxes
  • Cones
  • Capsules
  • Pyramids
  • Spheres

For this demo, I will simply be creating a box. An SCNBox has four required properties for initialization:

  • Width
  • Height
  • Length
  • Chamfer(Corner) Radius

You need to specify how long, tall, and wide you want your box to be. It could be a square box or it could be a long and skinny box. It’s up to you. You also need to specify how rounded you want the corners to be.

Once you have your SCNGeometry object, you can now manipulate its shader modifiers property. Each type of shader modifier has its own data structures. The SCNShaderOutput structure for fragment shaders has only one property, which is color:

struct SCNShaderOutput {
   vec4 color;
} _output;

Color is represented by a four element vector that has a space for a red, green, blue, and alpha value. By creating a fragment injection you can highjack whatever color would have been determined by the internal fragment shader and substitute your own. To keep things simple, this snippet simply sets the color of the box to red.

func addBox() {
    let box = SCNBox(width: 2.0, height: 2.0, length: 2.0, chamferRadius: 0.0)
    // Pure red box in Metal (step 1)
    box.shaderModifiers = [.fragment:
	 _output.color.rgb = float3(1.0, 0.0, 0.0);
    let geometryNode = SCNNode(geometry: box)

Finally, now that you have all of your setup methods in place, you can call them in the viewDidLoad() function:

override func viewDidLoad() {

Since you are dealing with Metal, this won’t build in the simulator. You will need to run it on device to see anything.


Making a solid red box isn’t particularly impressive, but if you’ve ever wrestled with Metal directly then you know that being able to throw something like this together in a few lines of code is verging on miraculous.

You can of course create more complex shaders, which I will detail in future blog posts. Right now this is a simple proof of concept. I know a lot of people who are just interested in shaders and don’t really care about the rest of the Metal rendering pipeline. This functionality exists for someone who just wants to muck around with shaders who doesn’t care about all the other fidgety stuff. It’s nice to have the fidgety stuff exposed if and when you need to drop down to that level, but it’s also nice to be able to just get something working quickly without having to set up the entire rendering pipeline.

Leave a Reply

Your email address will not be published. Required fields are marked *