SpriteKit Desaturation Shader

May 29, 2016

SpriteKit is built on top of a lower level graphics API like OpenGL. The goal of the SpriteKit framework is to make implementing 2D games easier as compared to using OpenGL directly. In iOS 8.X, Apple introduced a fragment shader feature that makes it possible to use a custom shader to render a SpriteKit node. Using a shader, a developer can execute custom code on the GPU to save CPU cycles.

Background on iOS Shaders

A few nice examples of what a shader can do, at shadertoy.com:

For additional background on shaders and how they can be used in SpriteKit, see the following:

Desaturation Shader

Before getting into all the implementation details, let's examine the expected result:

    iPhone Image

This example shows a background node displayed in full color and a second node rendered over the color node. The overlay node transforms the color pixels using a desaturation like filter for a specific 2D subregion. A simple animation expands the width and height of the overlay node to demonstrate that the shader is dynamically processing a specific 2D subregion.

Desaturation Shader Source

The full shader implementation can be found in shader_bw.fsh, it contains the following GLSL source code:

void main(void)
{
  vec4 texColor =
    texture2D(u_texture, v_tex_coord);
  float luma =
    texColor.r * 0.299 +
    texColor.g * 0.587 +
    texColor.b * 0.114;
  gl_FragColor = vec4(luma, luma, luma, 1.0);
}

The code above converts each RGB pixel to a grayscale pixel and writes the output with alpha=1.0 (aka 0xFF) to indicate a fully opaque pixel. Download the example project and run on an actual device to see the full 60 FPS output at full resolution.

Shaders and iOS 9

A new SpriteKit feature in iOS 9.X is both an OpenGL and a Metal render mode. Under iOS 9.x, the default settings have been changed to the Metal render mode. This can be a problem for existing applications that depend on the OpenGL mode to function properly. An example of a non-trivial shader that depends on the OpenGL mode can be found here.

If a specific shader does not work in Metal render mode, the developer will need to add the PrefersOpenGL=YES flag to Info.plist to keep an existing shader working in OpenGL mode.

Go forth and multiply!

This new shader functionality in SpriteKit makes it possible to implement highly optimized custom graphic rendering using the GPU. With this approach, your code executes in parallel on the GPU instead of consuming precious CPU cycles in your game.