将自定义SKShader应用于SKScene,使用Swift在iOS 8 SpriteKit中对整个渲染场景进行像素化处理

时间:2014-09-27 07:07:03

标签: ios opengl-es swift sprite-kit opengl-es-2.0

我正在尝试在SKScene上创建全屏像素化效果。我了解到应该有两种选择:

  • 使用GLES 2.0自定义SKShader
  • 使用核心图像过滤器。

我尝试添加一个自定义SKShader,它应该通过像素化来修改整个屏幕。我不确定是否可能,但来自SKSceneSKEffectNode的子类)的文档建议:

  

SKEffectNode对象将其子项呈现为缓冲区和   可选地将Core Image过滤器应用于此渲染输出。

可以将SKShader分配给SKScene,如GameScene : SKScene

override func didMoveToView(view: SKView) {
    let shader = SKShader(fileNamed: "pixelation.fsh")
    self.shader = shader
    self.shouldEnableEffects = true
}

...但似乎渲染的缓冲区不作为u_texture传递给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;
}

...所以之前的着色器不起作用。

如果我将该着色器指定给基于纹理的SKSpriteNode,则它可以正常工作。

在渲染完所有节点后,是否可以修改整个帧缓冲区(例如像素化它)作为后处理测量?

编辑:我找到了一种在OS X(How do you add a CIPixellate Core Image Filter to a Sprite Kit scene?)中使用Core Image过滤器进行像素化的方法,但复制该实现并不会在iOS上产生任何结果。根据{{​​3}} CIPixellate应为Available in OS X v10.4 and later and in iOS 6.0 and later.

3 个答案:

答案 0 :(得分:3)

为了让.shaderSKScene上运行,您需要在场景中将shouldEnableEffects设置为true(同样适用于SKEffectNode)。

虽然从技术上来说,"工作" (应用着色器),之后渲染场景中的错误会稍微调整一下。

到目前为止,使用CoreImage过滤器是最好的方法。

答案 1 :(得分:2)

我设法使用核心图像过滤器CIPixellate使其工作。我使用的是SKEffectNode的过滤器来产生像素化效果。有几点需要注意:

  • SKSceneSKEffectNode的子类,但将过滤器应用于SKScene不起作用。这会弄乱背景并且不会进行任何像素化。
  • 您需要创建一个SKEffectNode并添加要在其下进行像素化的节点。

以下是基于使用Game选择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 */
    }
}

答案 2 :(得分:0)

我实际上必须为最近的项目做同样的事情,作为在各级之间转换的方法,最后为它做一个解决方案。基本上,我在代码中截取了屏幕截图,然后当我加载下一个级别时,我调用了之前保存的屏幕截图,显示了加载时关卡的外观。我将之前的级别屏幕截图添加为SKSpriteNode,然后多次运行着色器,直到它出现难以置信的像素化。然后我对该级别的屏幕截图做了相同的操作并替换了两个,然后我将第二个屏幕截图取消像素化,所以它看起来就像水平被击败所有像素化自身然后非像素化以显示一个新的水平。 / p>

          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

第二场景

           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;