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

sar*_*tak 6 shader opengl-es glsl sprite-kit

着色器中的变量u_time告诉您​​当前时间。但是,如果您重复使用着色器(根据Apple 的最佳实践:“如果多个精灵需要相同的行为,请创建一个着色器对象并将其与每个精灵 [\xe2\x80\xa6] 关联”),然后u_time继续从第一次将其添加到场景中时。这使得使用着色器产生某些类型的效果变得困难,例如从黑色到白色的简单淡入淡出,如下所示:

\n\n
void main() {\n    float duration = 3.0; // seconds\n    float progress = u_time / duration;\n\n    gl_FragColor = vec4(progress, progress, progress, 1.0);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n

sar*_*tak 4

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

总体思路是为着色器提供特定于精灵的属性,用于跟踪着色器何时添加到该精灵。要计算该值,请跟踪着色器第一次添加到场景中的时间(即我们知道u_time应该为零的时间)。然后,每次我们将着色器添加到精灵时,都会计算第一个时间戳和当前时间之间的差异。提供该差异作为属性a_startTime(对应于我们希望u_time或其他东西为零的情况)。然后在着色器中,a_startTime - u_time是自添加着色器以来的当前时间戳,从零开始。

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

static dispatch_once_t firstStartToken;
static float firstStart;

dispatch_once(&firstStartToken, ^{
    firstStart = (float)CACurrentMediaTime();
});
Run Code Online (Sandbox Code Playgroud)

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

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

[shader setAttributes:@[
    [SKAttribute attributeWithName:@"a_startTime" type:SKAttributeTypeFloat]]];
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

    gl_FragColor = vec4(progress, progress, progress, 1.0);
}
Run Code Online (Sandbox Code Playgroud)

这种方法似乎有效。每次我将此着色器添加到精灵中时,它都会根据需要从黑色淡入白色。

然而,我担心它依赖于SKShader. 第一个是它u_time总是会继续上升:如果在 iOS 的未来版本中存在暂停或重置为零的情况,那么此代码将通过为当前时间提供负值或巨大值而中断。另一个潜在的破坏是我假设u_time使用 进行计数CACurrentMediaTime(),它似乎只与 中的内容直接对应u_time;在某些情况下,例如夏令时转换,这些计时器可能会出现偏差。