SKShader 创建视差背景

SKShader to create parallax background

带有固定相机的视差背景很容易做到,但由于我正在制作自上而下的 2D 视图 space 探索游戏,我认为让单个 SKSpriteNode 填充屏幕并成为我的 SKCameraNode 和使用 SKShader 绘制视差星域会更容易。

我继续使用 shadertoy 并找到了这个看起来简单的着色器。我在 shadertoy 上成功地对其进行了调整,以接受我想作为 SKAttribute 传递的运动速度的 vec2(),以便它可以跟随我的船的运动。

原文如下: https://www.shadertoy.com/view/XtjSDh

我设法对原始代码进行了转换,因此编译没有任何错误,但屏幕上没有显示任何内容。我尝试了各个功能,它们确实可以生成固定图像。

有什么建议可以让它发挥作用吗?

谢谢!

这并不是一个真正的答案,但它提供的信息比评论多得多,并且强调了 SK 如何处理粒子的一些奇怪之处和适当性:

SceneKit 中的粒子有一些奇怪的地方,可能适用于 SpriteKit。

  1. 移动粒子系统时,可以让粒子一起移动。这是默认行为:

来自文档:

When the emitter creates particles, they are rendered as children of the emitter node. This means that they inherit the characteristics of the emitter node, just like nodes do. For example, if you rotate the emitter node, the positions of all of the spawned particles are rotated also. Depending on what effect you are simulating with the emitter, this may not be the correct behavior.

事实上,对于大多数应用程序来说,这是错误的行为。但对于您想做的事情,这是理想的。您可以将新的 SKNodeEmitters 放置在船驶向的屏幕外,并将它们固定到 "space" 以便它们随着玩家船的方向变化而旋转,并且粒子将完全按照您的方式 want/need 来创建整个过程中移动的感觉 space.

  1. SpriteKit 具有预构建或以推进模拟的形式填充的能力:https://developer.apple.com/reference/spritekit/skemitternode/1398027-advancesimulationtime

这意味着当 SKEmittor 出现在屏幕上时,您可以通过 space 准备好星星来显示船驶向的任何地方。构建星星不需要加载延迟,这会立即完成。


据我所知,您需要 3 个粒子发射器才能完成此操作,每个发射器的大小都与设备的屏幕相同。将粒子爆发出来,然后将你想要的每个图层释放到相机右侧 "depth" 的目标节点,然后根据屏幕移动移动这些目标。

有点混乱,但可能比创建您自己的系统更快、更容易,并且更强大,充满了有趣的效果潜力。

也许...我可能是错的。

编辑:代码很干净,现在可以工作了。我为此设置了一个 GitHub 存储库。

我想我没有正确解释我想要什么。我需要一个跟随相机的星空背景,就像您可以在子空间中找到的那样(回到过去)

结果很酷,很有说服力!稍后当节点数量成为瓶颈时,我可能会回到这个问题上。我仍然相信正确的方法是使用着色器!

这是我在 GitHub 上的代码 link。我希望它对某人有用。它仍在进行中,但效果很好。回购中包含来自 SKTUtils(Ray Wenderlich 的一个库,已经在 github 上免费提供)和我自己对 Ray 工具的扩展,我称之为 螺母-n-螺栓。这些只是对每个人都应该觉得有用的常见类型的扩展。当然,您拥有 StarfieldNodeInteractiveCameraNode 的源代码以及一个小型演示项目。


简短的回答是,在 SpriteKit 中你直接使用片段坐标而不需要根据视口分辨率缩放(iResoultion 在 shadertoy land 中),所以行:

vec2 samplePosition = (fragCoord.xy / maxResolution) + vec2(0.0, iTime * 0.01);

可以更改为忽略缩放:

vec2 samplePosition = fragCoord.xy + vec2(0.0, iTime * 0.01);

这可能是导致您只能从着色器中看到黑色的根本问题(如果没有渲染着色器代码则很难确定)。

要获得一个 SpriteKit 着色器制作星空的完整答案,让我们对原始着色器进行简化,这样只有一个星空,没有 "fog"(为了简单起见),并添加一个变量来控制星星运动的速度矢量:

(这仍在 shadertoy 代码中)

float Hash(in vec2 p)
{
    float h = dot(p, vec2(12.9898, 78.233));
    return -1.0 + 2.0 * fract(sin(h) * 43758.5453);
}

vec2 Hash2D(in vec2 p)
{
    float h = dot(p, vec2(12.9898, 78.233));
    float h2 = dot(p, vec2(37.271, 377.632));
    return -1.0 + 2.0 * vec2(fract(sin(h) * 43758.5453), fract(sin(h2) * 43758.5453));
}

float Noise(in vec2 p)
{
    vec2 n = floor(p);
    vec2 f = fract(p);
    vec2 u = f * f * (3.0 - 2.0 * f);

    return mix(mix(Hash(n), Hash(n + vec2(1.0, 0.0)), u.x),
               mix(Hash(n + vec2(0.0, 1.0)), Hash(n + vec2(1.0)), u.x), u.y);
}

vec3 Voronoi(in vec2 p)
{
    vec2 n = floor(p);
    vec2 f = fract(p);

    vec2 mg, mr;

    float md = 8.0;
    for(int j = -1; j <= 1; ++j)
    {
        for(int i = -1; i <= 1; ++i)
        {
            vec2 g = vec2(float(i), float(j));
            vec2 o = Hash2D(n + g);

            vec2 r = g + o - f;
            float d = dot(r, r);

            if(d < md)
            {
                md = d;
                mr = r;
                mg = g;
            }
        }
    }
    return vec3(md, mr);
}

vec3 AddStarField(vec2 samplePosition, float threshold)
{
    vec3 starValue = Voronoi(samplePosition);
    if(starValue.x < threshold)
    {
        float power = 1.0 - (starValue.x / threshold);
        return vec3(power * power * power);
    }
    return vec3(0.0);
}


void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    float maxResolution = max(iResolution.x, iResolution.y);

    vec2 velocity = vec2(0.01, 0.01);
    vec2 samplePosition = (fragCoord.xy / maxResolution) + vec2(iTime * velocity.x, iTime * velocity.y);
    vec3 finalColor = AddStarField(samplePosition * 16.0, 0.00125);

    fragColor = vec4(finalColor, 1.0);
}

如果将其粘贴到新的 shadertoy window 和 运行 中,您应该会看到一个单色的星空向左下方移动。

为 SpriteKit 调整它非常简单。我们需要从函数变量中删除 "in"s,更改一些常量的名称(有一个体面的 blog post 关于 shadertoy 到 SpriteKit 的变化是需要的),并为速度向量使用一个属性所以我们可以根据需要,随着时间的推移,为每个应用的 SKSpriteNode 改变星星的方向。

这是完整的 SpriteKit 着色器源代码,其中 a_velocity 作为定义星场运动的必要属性:

float Hash(vec2 p)
{
    float h = dot(p, vec2(12.9898, 78.233));
    return -1.0 + 2.0 * fract(sin(h) * 43758.5453);
}

vec2 Hash2D(vec2 p)
{
    float h = dot(p, vec2(12.9898, 78.233));
    float h2 = dot(p, vec2(37.271, 377.632));
    return -1.0 + 2.0 * vec2(fract(sin(h) * 43758.5453), fract(sin(h2) * 43758.5453));
}

float Noise(vec2 p)
{
    vec2 n = floor(p);
    vec2 f = fract(p);
    vec2 u = f * f * (3.0 - 2.0 * f);

    return mix(mix(Hash(n), Hash(n + vec2(1.0, 0.0)), u.x),
               mix(Hash(n + vec2(0.0, 1.0)), Hash(n + vec2(1.0)), u.x), u.y);
}

vec3 Voronoi(vec2 p)
{
    vec2 n = floor(p);
    vec2 f = fract(p);

    vec2 mg, mr;

    float md = 8.0;
    for(int j = -1; j <= 1; ++j)
    {
        for(int i = -1; i <= 1; ++i)
        {
            vec2 g = vec2(float(i), float(j));
            vec2 o = Hash2D(n + g);

            vec2 r = g + o - f;
            float d = dot(r, r);

            if(d < md)
            {
                md = d;
                mr = r;
                mg = g;
            }
        }
    }
    return vec3(md, mr);
}

vec3 AddStarField(vec2 samplePosition, float threshold)
{
    vec3 starValue = Voronoi(samplePosition);
    if (starValue.x < threshold)
    {
        float power = 1.0 - (starValue.x / threshold);
        return vec3(power * power * power);
    }
    return vec3(0.0);
}


void main()
{
    vec2 samplePosition = v_tex_coord.xy + vec2(u_time * a_velocity.x, u_time * a_velocity.y);
    vec3 finalColor = AddStarField(samplePosition * 20.0, 0.00125);

    gl_FragColor = vec4(finalColor, 1.0);
}

(值得注意的是,这只是 original 的修改版本)