I'm trying to create a full-screen pixelation effect on SKScene. I've learned that there should be two options to do this:
- Using a custom
SKShader
using GLES 2.0. - Using Core Image filters.
I've tried to add a custom SKShader that should modify the whole screen by pixelating it. I'm not sure that if it's possible, but documentation from SKScene
(which is a subclass of SKEffectNode
) suggests it:
An SKEffectNode object renders its children into a buffer and optionally applies a Core Image filter to this rendered output.
It's possible to assign a SKShader to the SKScene, as in GameScene : SKScene
:
override func didMoveToView(view: SKView) {
let shader = SKShader(fileNamed: "pixelation.fsh")
self.shader = shader
self.shouldEnableEffects = true
}
... but it seems that the rendered buffer is not passed as the u_texture to the GLES:
void main()
{
vec2 coord = v_tex_coord;
coord.x = floor(coord.x * 10.0) / 10.0;
coord.y = floor(coord.y * 10.0) / 10.0;
vec4 texture = texture2D(u_texture, coord);
gl_FragColor = texture;
}
... so the previous shader doesn't work.
If I assign that shader to a texture-based SKSpriteNode
, it works.
So is it possible to modify the whole frame buffer (and for example pixelate it) as a post-processing measure after all the nodes have been rendered?
Edit: I found a way to do the pixelation using Core Image filters in OS X (How do you add a CIPixellate Core Image Filter to a Sprite Kit scene?), but copying that implementation doesn't yield any results on iOS. According to the documents CIPixellate
should be Available in OS X v10.4 and later and in iOS 6.0 and later.
.
In order to get your
.shader
running onSKScene
, you need to setshouldEnableEffects
to true on the scene (same thing goes forSKEffectNode
).While technically, that "works" (the shader is applied), there's a bug in the rendering of the scene afterwards that gets slightly resized.
So using
CoreImage
filters is, so far, the best way to go.I actually had to do the exact same thing for a recent project as a way to transition between levels, and ended up doing a work around for it. Basically, I took a screenshot of the screen in the code then when I loaded the next level, I called a previously saved screenshot of how the level should look when it was loaded. I added the previous level screenshot as an SKSpriteNode and then ran the shader a number of times until it was incredibly pixelated. Then I did the same to the screenshot for that level and replaced the two and then I un-pixelated the second screenshot so it looked like as soon as the level was beaten everything pixelated itself then un-pixelated itself to reveal a new level.
Second Scene
I managed to make it work using Core Image filter
CIPixellate
. I used is as a filter toSKEffectNode
to produce the pixelation effect. Couple of things to note:SKScene
is a subclass ofSKEffectNode
, but applying the filter toSKScene
doesn't work. It'll mess up the background and doesn't do any pixellation.SKEffectNode
and add the nodes to be pixelated under that.Here's the solution based on the code generated when you choose a
Game
type project withSwift
: