Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using shaders from Shadertoy in Interface Builder (Xcode)

I'm attempting to see what shaders look like in Interface Builder using sprite kit, and would like to use some of the shaders at ShaderToy. To do it, I created a "shader.fsh" file, a scene file, and added a color sprite to the scene, giving it a custom shader (shader.fsh)

While very basic shaders seem to work:

void main() {
    gl_FragColor = vec4(0.0,1.0,0.0,1.0);
}

Any attempt I make to convert shaders from ShaderToy cause Xcode to freeze up (spinning color ball) as soon as the attempt is made to render them.

The shader I am working with for example, is this one:

#define M_PI 3.1415926535897932384626433832795

float rand(vec2 co)
{
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    float size = 30.0;
    float prob = 0.95;

    vec2 pos = floor(1.0 / size * fragCoord.xy);

    float color = 0.0;
    float starValue = rand(pos);

    if (starValue > prob)
    {
        vec2 center = size * pos + vec2(size, size) * 0.5;

        float t = 0.9 + 0.2 * sin(iGlobalTime + (starValue - prob) / (1.0 - prob) * 45.0);

        color = 1.0 - distance(fragCoord.xy, center) / (0.5 * size);
        color = color * t / (abs(fragCoord.y - center.y)) * t / (abs(fragCoord.x - center.x));
    }
    else if (rand(fragCoord.xy / iResolution.xy) > 0.996)
    {
        float r = rand(fragCoord.xy);
        color = r * (0.25 * sin(iGlobalTime * (r * 5.0) + 720.0 * r) + 0.75);
    }

    fragColor = vec4(vec3(color), 1.0);
}

I've tried:

  • Replacing mainImage() with main(void) (so that it will be called)
  • Replacing the iXxxxx variables (iGlobalTime, iResolution) and fragCoord variables with their related variables (based on the suggestions here)
  • Replacing some of the variables (iGlobalTime)...

While changing mainImage to main() and swapping out the variables got it to work without error in TinyShading realtime tester app - the outcome is always the same in Xcode (spinning ball, freeze). Any advice here would be helpful as there is a surprisingly small amount of information currently available on the topic.

like image 703
BadPirate Avatar asked Mar 14 '23 10:03

BadPirate


1 Answers

I managed to get this working in SpriteKit using SKShader. I've been able to render every shader from ShaderToy that I've attempted so far. The only exception is that you must remove any code using iMouse, since there is no mouse in iOS. I did the following...

1) Change the mainImage function declaration in the ShaderToy to...

void main(void) {
    ...
}

The ShaderToy mainImage function has an input named fragCoord. In iOS, this is globally available as gl_FragCoord, so your main function no longer needs any inputs.

2) Do a replace all to change the following from their ShaderToy names to their iOS names...

  • fragCoord becomes gl_FragCoord
  • fragColor becomes gl_FragColor
  • iGlobalTime becomes u_time

Note: There are more that I haven't encountered yet. I'll update as I do

3) Providing iResolution is slightly more involved...

iResolution is the viewport size (in pixels), which translates to the sprite size in SpriteKit. This used to be available as u_sprite_size in iOS, but has been removed. Luckily, Apple provides a nice example of how to inject it into your shader using uniforms in their SKShader documentation.

However, as stated in Shader Inputs section of ShaderToy, the type of iResolution is vec3 (x, y and z) as opposed to u_sprite_size, which is vec2 (x and y). I am yet to see a single ShaderToy that uses the z value of iResolution. So, we can simply use a z value of zero. I modified the example in the Apple documentation to provide my shader an iResolution of type vec3 like so...

let uniformBasedShader = SKShader(fileNamed: "YourShader.fsh")

let sprite = SKSpriteNode()
sprite.shader = uniformBasedShader       

let spriteSize = vector_float3(
    Float(sprite.frame.size.width),  // x                 
    Float(sprite.frame.size.height), // y
    Float(0.0)                       // z - never used
)

uniformBasedShader.uniforms = [    
    SKUniform(name: "iResolution", vectorFloat3: spriteSize)
]

That's it :)

like image 99
Charlie Martin Avatar answered Mar 31 '23 21:03

Charlie Martin