如何计算自SKShader添加到精灵后的当前时间?

时间:2018-02-05 20:41:48

标签: opengl-es sprite-kit glsl shader

着色器中的u_time变量会告诉您当前时间。但是,如果您重复使用着色器(就像best practice according to Apple一样:“如果多个精灵需要相同的行为,创建一个着色器对象并将其与每个精灵[...]相关联”),则u_time继续计数将第一次时间添加到场景中。这使得很难使用着色器来产生某些类型的效果,例如从黑色到白色的简单淡入淡出,如下所示:

void main() {
    float duration = 3.0; // seconds
    float progress = u_time / duration;

    gl_FragColor = vec4(progress, progress, progress, 1.0);
}

此着色器在第一次添加到精灵时按预期工作,因为u_time从零开始。但是,相同着色器代码的后续重用以非零u_time开头。因此,后续着色器将开始全白并且不会呈现预期的黑白淡化。这似乎极大地限制了SKShader的可能效用。

3 个答案:

答案 0 :(得分:1)

我找到了一种有效的方法,但我对它不满意。可能甚至不满意我每次将它添加到场景时都要重新编译着色器。

一般的想法是为着色器提供一个特定于精灵的属性,该属性跟踪着色器何时被添加到该精灵中。要计算它,跟踪着色器第一次被添加到场景中(当我们知道u_time应为零时)。然后每次我们将着色器添加到精灵时,取第一个时间戳和当前时间之间的差值。将差异作为属性a_startTime提供(对应于我们希望u_time或其他地方的时间为零)。然后在着色器中,a_startTime - u_time是当前时间戳,从零开始,因为添加了着色器。

这是整个实现的样子。跟踪第一次将着色器附加到全局变量中的精灵:

static dispatch_once_t firstStartToken;
static float firstStart;

dispatch_once(&firstStartToken, ^{
    firstStart = (float)CACurrentMediaTime();
});

firstStart变量标记着色器的u_time制服为零时的时间戳。

我们还必须为a_startTime实例声明一个属性SKShader,以便有一个地方填写当前精灵的开始时间:

[shader setAttributes:@[
    [SKAttribute attributeWithName:@"a_startTime" type:SKAttributeTypeFloat]]];

然后,每次将着色器添加到SKSpriteNode时,将精灵的属性a_startTime设置为相对于初始u_time <的当前时间 / em>的

float startTime = CACurrentMediaTime() - firstStart;
[sprite setValue:[SKAttributeValue valueWithFloat:startTime]
    forAttributeNamed:@"a_startTime"];

最后,让着色器实现使用a_startTime

void main() {
    float duration = 3.0; // seconds
    float progress = (u_time - a_startTime) / duration;

    gl_FragColor = vec4(progress, progress, progress, 1.0);
}

这种方法似乎有效。每次我将这个着色器添加到精灵时,它会根据需要从黑色渐变为白色。

但是,我关注其依赖SKShader的一些实施细节的脆弱性。第一个是u_time将始终继续滴答:如果在iOS的未来版本中有暂停或重置为零的情况,那么此代码将通过为当前时间提供负值或巨大值来中断。另一个潜在的破坏是我假设u_time使用CACurrentMediaTime()计算,只有出现才能直接对应u_time中的内容。可能会出现一些情况,例如夏令时切换,这些计时器会发生分歧。

答案 1 :(得分:0)

我不是一位经验丰富的程序员,但我认为我找到了解决此问题的更好解决方案: 只需将自应用程序启动以来经过的时间作为制服传递给着色器,然后从u_time中减去它。

为此,我在应用启动时保存indices.foldLeft(template)((b, a) => b.updated(a, 1))

CACurrentMediaTime()

这样我可以告诉我的着色器已经过了多长时间:

let startTime = CACurrentMediaTime()

然后我可以在我的着色器代码中执行此操作:

let shader = SKShader(fileNamed: "shader.fsh")
let currentTime = CACurrentMediaTime()
let timeSinceAppStart = Float(currentTime - startTime)
shader.uniforms = [SKUniform(name: "time", float: timeSinceAppStart)]

现在 appTime 正在计算 u_time ,但是从0开始。

答案 2 :(得分:0)

首先将一个属性添加到着色器,然后使用自定义操作在内部传递该时间。每次运行时它将从零重置。

let a = SKAttribute(name: "TimeElsp", type: .float)
skshader.attributes = [a]

let shaderAction = SKAction.customAction(withDuration: 0.3) { (node, time) in
        let s = node as? SKSpriteNode
        print("time = \(time)")
        s?.setValue(SKAttributeValue(float: Float(time)), forAttribute: "TimeElsp")

    }