着色器中的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
的可能效用。
答案 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")
}