Reputation: 8365
I'm trying to create a full-screen pixelation effect on SKScene. I've learned that there should be two options to do this:
SKShader
using GLES 2.0.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.
.
Upvotes: 3
Views: 3603
Reputation: 103
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.
UIGraphicsBeginImageContextWithOptions(UIScreen.mainScreen().bounds.size, false, 0);
self.view!.drawViewHierarchyInRect(view!.bounds, afterScreenUpdates: true)
let image:UIImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//UIGraphicsEndImageContext()
protoImage = SKSpriteNode(texture: SKTexture(CGImage: image.CGImage!))
protoImage.size = CGSizeMake(self.frame.size.width, self.frame.size.height)
node.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2)
protoImage.zPosition = 9000
Second Scene
let shader: SKShader = SKShader(fileNamed: "RWTGradient2.fsh")
let ratioX: Float = divisor/Float(protoImage.frame.size.width)
let ratioY: Float = divisor/Float(protoImage.frame.size.height)
shader.uniforms = [
SKUniform(name: "ratioX", float: ratioX),
SKUniform(name: "ratioY", float: ratioY),
]
protoImage.shader = shader;
Upvotes: 0
Reputation: 31
In order to get your .shader
running on SKScene
, you need to set shouldEnableEffects
to true on the scene (same thing goes for SKEffectNode
).
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.
Upvotes: 3
Reputation: 8365
I managed to make it work using Core Image filter CIPixellate
. I used is as a filter to SKEffectNode
to produce the pixelation effect. Couple of things to note:
SKScene
is a subclass of SKEffectNode
, but applying the filter to SKScene
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 with Swift
:
import SpriteKit
class GameScene: SKScene {
var effectNode : SKEffectNode = SKEffectNode.node()
override func didMoveToView(view: SKView) {
let filter = CIFilter(name: "CIPixellate")
filter.setDefaults()
filter.setValue(5.0, forKey: "inputScale")
self.effectNode.filter = filter
self.effectNode.shouldEnableEffects = true
self.addChild(effectNode)
}
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
let sprite = SKSpriteNode(imageNamed:"Spaceship")
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.position = location
let action = SKAction.rotateByAngle(CGFloat(M_PI), duration:1)
sprite.runAction(SKAction.repeatActionForever(action))
self.effectNode.addChild(sprite)
}
}
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
}
}
Upvotes: 3