SpriteKit问题与Swift中的SKShader动画

时间:2018-06-16 05:04:40

标签: swift sprite-kit shader

对于我的生活,我无法理解为什么这个着色器动画没有运行。任何帮助表示赞赏。请参阅下面的代码...着色器正确显示,但没有动画。谢谢。

注意:Xcode 9.4.1,Swift 4.1,iOS 11.4

import SpriteKit

class GameScene: SKScene {
    static let marquee: SKShader = {
        let shader = SKShader(
            source: "void main() {" +
                "float np = mod(a_path_phase + v_path_distance / u_path_length, 1.0);" +
                "vec3 hsb = vec3(np, 1.0, 1.0);" +
                "vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);" +
                "vec3 p = abs(fract(hsb.xxx + K.xyz) * 6.0 - K.www);" +
                "vec3 rgb = hsb.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsb.y);" +
                "vec4 df = SKDefaultShading();" +
                "gl_FragColor = vec4(rgb, df.z);" +
            "}"
        )
        shader.attributes = [SKAttribute(name: "a_path_phase", type: .float)]
        return shader
    } ()

    private let beat: TimeInterval = 0.05
    private var phase: Float = 0

    override func didMove(to view: SKView) {
        let node = SKShapeNode(rectOf: CGSize(width: 256, height: 256), cornerRadius: 16)
        node.fillColor = .clear
        node.strokeColor = .white
        node.strokeShader = GameScene.marquee
        node.lineWidth = 8
        node.glowWidth = 4
        node.run(
            SKAction.repeatForever(
                SKAction.sequence(
                    [
                        SKAction.wait(forDuration: beat),
                        SKAction.run() { [weak self] in
                            if let weak = self {
                                weak.phase = fmodf(weak.phase + Float(weak.beat), 1)
                                node.setValue(SKAttributeValue(float: weak.phase), forAttribute: "a_path_phase")
                            }
                        }
                    ]
                )
            )
        )
        addChild(node)
    }
}

1 个答案:

答案 0 :(得分:0)

我在使用.setValue(forAttribute:)时也遇到了麻烦。它似乎没有用。经调查,我的结论是...

我认为SKShapeNode.setValue(forAttribute:)无法正常工作。

SKSpriteNode.setValue(forAttribute:)在相同条件下可以正常工作的事实使我得出结论,这几乎肯定是SKShapeNode中iOS中的一些错误。

  

事实是,我可能会在其中设置任何SKAttribute   SKShapeNode.fillShaderSKShapeNode.strokeShader保留了   无论我做什么,都将值设置为0.0

     

但是,SKSpriteNode.shader却没有发生。

所以我不得不找到一种方法来规避这个错误。 某种骇客。 幸运的是,就我而言,我确实找到了一种方法。

幸运的是,这似乎也足以解决您的问题。


在这种情况下,您可以做的是将 阶段 的值作为strokeColor的组成部分之一传递,例如, 红色组件。

为此,您必须将 阶段 标准化为0.01.0之间的值。 您可以使用 shader.uniform 传递阶段 乘数 ,以帮助使其规范化。

然后从SKDefaultShading().x(红色分量)中读取着色器内的 phase 值。

  

请确保设置广告的 alpha 组件   strokeColor1.0,因为SKDefaultShading()的颜色   组件值已经预乘以其alpha。

现在,由于您的 strokeColor 已经用于传递 phase ,因此您可以将描边线条的实际颜色作为 vec4 shader.uniform


  • 就我而言,我还利用u_time帮助执行我的 动画。像fract(u_time)这样的东西可能非常有用。

这是更正后的代码的样子:

>注意:有一个更简单的解决方案。看看这篇文章的结尾。

import SpriteKit

class GameScene: SKScene {
    private typealias Me = GameScene
    static let phaseMultiplier: Float = 1000

    static let marquee: SKShader = {
        let shader = SKShader(
            source: "void main() {" +
                "float red = SKDefaultShading().x;" +
                "float phase = red * u_phase_multiplier;" +
                "float np = mod(phase + v_path_distance / u_path_length, 1.0);" +
                "vec3 hsb = vec3(np, 1.0, 1.0);" +
                "vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);" +
                "vec3 p = abs(fract(hsb.xxx + K.xyz) * 6.0 - K.www);" +
                "vec3 rgb = hsb.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), hsb.y);" +
                "vec4 df = u_stroke_color;" +
                "gl_FragColor = vec4(rgb, df.z);" +
            "}"
        )
        let white = vector_float4.init(1, 1, 1, 1)
        shader.uniforms = [
            SKUniform(name: "u_phase_multiplier", float: Me.phaseMultiplier), 
            SKUniform(name: "u_stroke_color", vectorFloat4: white)]
        return shader
    } ()

    private let beat: TimeInterval = 0.05
    private var phase: Float = 0

    override func didMove(to view: SKView) {
        let node = SKShapeNode(rectOf: CGSize(width: 256, height: 256), cornerRadius: 16)
        node.fillColor = .clear
        node.strokeColor = .white
        node.strokeShader = Me.marquee
        node.lineWidth = 8
        node.glowWidth = 4
        node.run(
            SKAction.repeatForever(
                SKAction.sequence(
                    [
                        SKAction.wait(forDuration: beat),
                        SKAction.run() { [weak self] in
                            if let weak = self {
                                weak.phase = fmodf(weak.phase + Float(weak.beat), 1)
                                let phase = weak.phase / Me.phaseMultiplier
                                node.strokeColor = SKColor.init(red: phase, green: 0, blue: 0, alpha: 1)
                            }
                        }
                    ]
                )
            )
        )
        addChild(node)
    }
}

注意: :我没有编译上面发布的代码。我应该只是一个草图。


  

发布后的第二天,我发现属性参数不应该在片段着色器中使用,而只能在顶点着色器中使用。

看看StackOverflow问题: In WebGL what are the differences between an attribute, a uniform, and a varying variable?

  

SKAttributeSKAttributeValue的SDK文档似乎暗示“ SKAttributes”和实际着色器 attribute 参数之间可能没有直接关系。它们似乎只是被命名为相同的名称。


更简单的解决方案:

但是,最后,没有必要像我在此处所示的那样利用任何黑客手段。

  

所有要做的就是通过SKShader.uniformNamed("u_phase")?.floatValue = phase设置 uniform 的值,而不是像 jmdecombe 那样使用.setValue(forAttribute:),正确,稍后指出。

我将在这里保留我的答案以供参考,因为它提供了解决问题的非常规方法。