Class

SKShader

An object that allows nodes to be rendered or filtered with a custom OpenGL ES fragment shader.

Overview

An SKShader object holds a custom OpenGL ES fragment shader. Shader objects are used to customize the drawing behavior of many different kinds of nodes in SpriteKit.

To use a custom shader, create an SKShader object and provide the source for the custom fragment shader. If your shader needs to provide uniform state to the shader, create one or more SKUniform objects and associate them with the shader object. If your shader needs to provide per-node state to the shader, create one or more SKAttribute objects and associate them with the relevant nodes. Then, assign the shader object to the shader property of any sprites that need the custom behavior.

Compiling a shader and the uniform data associated with it can be expensive. Because of this, you should:

  • Initialize shader objects when your game launches, not while the game is running.

  • Avoid changing the shader’s source or changing the list of uniforms or attributes while your game is running. Either of these things recompiles the shader.

  • Share shader objects whenever possible. If multiple sprites need the same behavior, create one shader object and associate it with every sprite that needs that behavior. Do not create a separate shader for each sprite.

Creating a Custom Fragment Shader

Your fragment shader should be written in the OpenGL ES 2.0 shading language. SpriteKit automatically does the necessary work to make sure this shader compiles correctly in both iOS and macOS. The shader object compiles your shader by appending the source code you provide to a preamble created by SpriteKit. This preamble declares all of the standard SpriteKit variables and functions, including declarations for any uniforms you have associated with the shader object. Table 1 describes the SpriteKit symbols made available to your shader.

Table 1

Predefined custom shader symbols

Symbol declaration

Type

Description

sampler2D u_texture;

Uniform

A sampler associated with the texture used to render the node.

float u_time;

Uniform

The elapsed time in the simulation.

float u_path_length;

Uniform

This uniform is provided only when the shader is attached to an SKShapeNode object’s strokeShader property. The total length of the path in points.

vec2 v_tex_coord;

Varying

The coordinates used to access the texture. These are normalized so that the point (0.0,0.0) is in the bottom-left corner of the texture.

vec4 v_color_mix;

Varying

The premultiplied color value for the node being rendered.

float v_path_distance;

Varying

This varying is provided only when the shader is attached to an SKShapeNode object’s strokeShader property. The distance along the path in points.

vec4 SKDefaultShading()

Function

A function that provides the default behavior used by SpriteKit.

Since the removal of u_sprite_size, if your shader requires the size of the sprite in pixels, you are responsible for creating the necessary uniform or attribute. In the case where your existing shader code uses the deprecated u_sprite_size and you don’t want to change it, the following code shows how you can define and set the value of of a SKUniform object.

Listing 1

Setting sprite size with a uniform.

let uniformBasedShader = SKShader(fileNamed: "UsingUniform.fsh")
 
let sprite = SKSpriteNode()
sprite.shader = uniformBasedShader	
 	 
let spriteSize = vector_float2(Float(sprite.frame.size.width),
                               Float(sprite.frame.size.height))
uniformBasedShader.uniforms = [    
    SKUniform(name: "u_sprite_size", vectorFloat2: spriteSize)
]

The preferred method would be to pass the size to the shader as an SKAttribute. Using attributes rather than uniforms allows a single SKShader to be shared between sprites with different sizes. The following code shows how you can pass the size of a sprite to a shader as an attribute.

Listing 2

Setting sprite size with an attribute.

let attributeBasedShader = SKShader(fileNamed: "UsingAttributes.fsh")
attributeBasedShader.attributes = [
    SKAttribute(name: "a_sprite_size", type: .vectorFloat2)
]
 
let sprite = SKSpriteNode()
sprite.shader = attributeBasedShader
 
let spriteSize = vector_float2(Float(sprite.frame.size.width),
                               Float(sprite.frame.size.height))
sprite.setValue(SKAttributeValue(vectorFloat2: spriteSize),
                forAttribute: "a_sprite_size")

Your shader should define a main() function. This function must set the gl_FragColor variable to a color value to use in the blend stage; typically the color value you return in this variable should already be premultiplied by the fragment’s alpha value.

Listing 3 shows a very simple fragment shader that provides the default shading behavior.

Listing 3

Example shader

void main()
{
   gl_FragColor = SKDefaultShading();
}

Executing Shaders in Metal and OpenGL

On devices that support it, shader code in an SKShader is implemented in Metal. On older devices, shader code is implemented in OpenGL. It's important to be mindful of this for debugging purposes because some issues may exhibit in one renderer but not the other. Listing 4 shows how you can display the active renderer in the bottom right hand corner of an SKView.

Listing 4

Displaying the current renderer in a SpriteKit view

let defaults = UserDefaults.standard
var skDefaultsDictionary = [String: Any]()
skDefaultsDictionary["debugDrawStats_SKContextType"] = true
defaults.set(skDefaultsDictionary,
             forKey: "SKDefaults")

You can specify which renderer your device uses, for example, forcing a Metal enabled device to use OpenGL for debugging purposes, by adding a PrefersOpenGL key with a value of true to your app's Info.plist as shown in Figure 1.

Figure 1

Specifying the SpriteKit renderer

Specifying the SpriteKit renderer

Topics

Creating and Initializing New Shader Objects

init(fileNamed: String)

Creates a new shader object by loading the source for a fragment shader from a file stored in the app’s bundle.

init(source: String, uniforms: [SKUniform])

Initializes a new shader object using the specified source and uniform data.

init(source: String)

Initializes a new shader object using the specified source code.

Using Uniform Data in a Shader

func addUniform(SKUniform)

Adds a uniform to the shader object.

func removeUniformNamed(String)

Removes a uniform from the shader.

var uniforms: [SKUniform]

The list of uniforms associated with the shader.

func uniformNamed(String)

Returns the uniform object corresponding to a particular uniform variable.

Working with Shader Source Code

var source: String?

The source code for the shader.

Instance Properties

var attributes: [SKAttribute]

The list of attributes associated with the shader.

Relationships

Inherits From

See Also

Working with Shaders

class SKAttribute

A specification for dynamic per-node data used with a custom shader.

class SKAttributeValue

A container for dynamic shader data associated with a node.

class SKUniform

A container for uniform shader data.