SpriteKit问题与Swift中的SKShader动画

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpriteKit问题与Swift中的SKShader动画相关的知识,希望对你有一定的参考价值。

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

注意: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)
    }
}
答案

我也遇到了.setValue(forAttribute:)的问题。它似乎没有用。经过调查,我的结论是......

我相信SKShapeNode.setValue(forAttribute:)不起作用。

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

事实上,无论我做什么,我可能在SKAttributeSKShapeNode.fillShader设置的任何SKShapeNode.strokeShaders都将它们的值设置为0.0

然而,SKSpriteNode.shader没有发生同样的情况。

所以我必须找到一种方法来规避这个bug。某种破解。幸运的是,就我而言,我确实找到了一种方法。

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


在这种情况下,您可以做的是将相位的值作为strokeColor的一个组件传递,例如红色组件。

要做到这一点,你必须将相位标准化为0.01.0之间的值。您可以使用shader.uniform传递相位乘法器以帮助非规范化它。

然后从SKDefaultShading().x(红色组件)中读取着色器内的相位值。

确保将strokeColor的alpha分量设置为1.0,因为SKDefaultShading()的颜色分量值已经被alpha预乘。

现在,因为你的strokeColor已经被用来传递相位,你可以将描边线的实际颜色作为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”和实际着色器属性参数之间可能没有直接关系。它们似乎被命名为相同。


更简单的解决方案

然而,最终,从来没有任何需要使用像我在这里展示的那样的黑客。

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

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

以上是关于SpriteKit问题与Swift中的SKShader动画的主要内容,如果未能解决你的问题,请参考以下文章

SKPhysics 与 Swift 和 SpriteKit

如何使用 Swift 正确切换 SpriteKit 中的 SKScene?

在使用 Swift 的 SpriteKit 中的 didBeginContact(日志)中未检测到 contactTestBitMask

Swift:Spritekit 中的多个场景,从第二个和第一个场景的不同 Sprite 节点类型调用 touchesBegan

如何从 Swift SpriteKit 中的另一个 SKScene 访问变量

如何使用 swift 使用带有 spriteKit 的情节提要