Tam Hartman
Tam Hartman

Reputation: 185

Solving odd behavior re: glsl/metal shader. (unintentional coordinate flipping)

I've got this shader that I ported from a shadertoy into a metal shader for iOS. The original one works fine, but I'm getting some strange behavior now that I've moved it to iOS. Basically, for the first few seconds that the shader is running, everything is misaligned. I think that this is because there is mirroring on the X axis, which is correct, but the vertical coordinates have also been somehow flipped on one side. Can anyone tell me how I should go about fixing this?

Original Shadertoy: https://www.shadertoy.com/view/ltl3Dj

My version, converted into metal shading language:

#include <metal_stdlib>
using namespace metal;

////////////////

///CSB CONSTANTS (not required, just make sure it's handled properly at the bottom)
constant float2 resolution = (1, 1);
constant float contrast = 1.0;
constant float saturation = 1.02;
constant float brightness = 1.5;

struct FloweringQuadVertexToFragmentVariables
{
    //basic Active Shader Variables
    float4 position [[ position ]];
    float2 textureCoordinates;
    float time;

    //Shader specific variables go here (not required)

};

vertex FloweringQuadVertexToFragmentVariables FloweringQuadVertexShader (constant float4 *positions [[ buffer(0) ]],
                                                               constant float2 *textureCoordinates [[ buffer(1) ]],
                                                               constant float *shaderFloatZero [[buffer(2)]],
                                                               uint vertexID [[ vertex_id ]])
{
    FloweringQuadVertexToFragmentVariables output;

    //basic variables output here
    output.position = positions[vertexID];
    output.textureCoordinates = textureCoordinates[vertexID];
    output.time = *shaderFloatZero;

    //additional variables here

    //output

    return output;
}
// Remember, can do [color(0)] etc. for rendering to attachments other than just [0]

float3 FloweringContrastSaturationBrightness(float3 color, float brt, float sat, float con)
{
    // Increase or decrease theese values to adjust r, g and b color channels seperately
    const float AvgLumR = 0.4;
    const float AvgLumG = 0.4;
    const float AvgLumB = 0.4;

    const float3 LumCoeff = float3(0.2125, 0.7154, 0.0721); //luminosity coefficient

    float3 AvgLumin  = float3(AvgLumR, AvgLumG, AvgLumB);
    float3 brtColor  = color * brt;
    float3 intensity = float3(dot(brtColor, LumCoeff));
    float3 satColor  = mix(intensity, brtColor, sat);
    float3 conColor  = mix(AvgLumin, satColor, con);

    return conColor;
}

float4 hue(float4 color, float shift) {

    const float4  kRGBToYPrime = float4 (0.299, 0.587, 0.114, 0.0);
    const float4  kRGBToI     = float4 (0.596, -0.275, -0.321, 0.0);
    const float4  kRGBToQ     = float4 (0.212, -0.523, 0.311, 0.0);

    const float4  kYIQToR   = float4 (1.0, 0.956, 0.621, 0.0);
    const float4  kYIQToG   = float4 (1.0, -0.272, -0.647, 0.0);
    const float4  kYIQToB   = float4 (1.0, -1.107, 1.704, 0.0);

    // Convert to YIQ
    float   YPrime  = dot (color, kRGBToYPrime);
    float   I      = dot (color, kRGBToI);
    float   Q      = dot (color, kRGBToQ);

    // Calculate the hue and chroma
    float   hue     = atan (Q/ I);
    float   chroma  = sqrt (I * I + Q * Q);

    // Make the user's adjustments
    hue += shift;

    // Convert back to YIQ
    Q = chroma * sin (hue);
    I = chroma * cos (hue);

    // Convert back to RGB
    float4    yIQ   = float4 (YPrime, I, Q, 0.0);
    color.r = dot (yIQ, kYIQToR);
    color.g = dot (yIQ, kYIQToG);
    color.b = dot (yIQ, kYIQToB);

    return color;
}

float2 kale(float2 uv, float angle, float base, float spin) {
    float a = atan(uv.y/uv.x)+spin;
    float d = length(uv);
    a = fmod(a,angle*2.0);
    a = abs(a-angle);
    uv.x = sin(a+base)*d;
    uv.y = cos(a+base)*d;
    return uv;
}

float2 rotate(float px, float py, float angle){
    float2 r = float2(0);
    r.x = cos(angle)*px - sin(angle)*py;
    r.y = sin(angle)*px + cos(angle)*py;
    return r;
}

float floweringlum(float3 c) {
    return dot(c, float3(0.3, 0.59, 0.11));
}

float3 floweringclipcolor(float3 c) {
    float l = floweringlum(c);
    float n = min(min(c.r, c.g), c.b);
    float x = max(max(c.r, c.g), c.b);

    if (n < 0.0) {
        c.r = l + ((c.r - l) * l) / (l - n);
        c.g = l + ((c.g - l) * l) / (l - n);
        c.b = l + ((c.b - l) * l) / (l - n);
    }
    if (x > 1.0) {
        c.r = l + ((c.r - l) * (1.0 - l)) / (x - l);
        c.g = l + ((c.g - l) * (1.0 - l)) / (x - l);
        c.b = l + ((c.b - l) * (1.0 - l)) / (x - l);
    }

    return c;
}

float3 setfloweringlum(float3 c, float l) {
    float d = l - floweringlum(c);
    c = c + float3(d);
    return floweringclipcolor(c);
}

fragment float4 FloweringQuadFragmentShader(FloweringQuadVertexToFragmentVariables input [[ stage_in ]],
                                       texture2d<float> fragmentTexture [[ texture(0) ]],
                                       sampler samplr [[sampler(0) ]])
{   float timeElapsed = input.time;

    float4 textureColor = fragmentTexture.sample(samplr, input.textureCoordinates);


    ///////
    float2 iResolution = (1, 1);
    float2 texCoords = input.textureCoordinates;
    //float2 p = texCoords.xy / iResolution.xy;

    ////////
    float p = 3.14159265359;
    float i = timeElapsed*.5;
    float2 uv = texCoords.xy / iResolution.xy*5.0-2.5;


    uv = kale(uv, p/6.0,i,i*0.2);
    float4 c = float4(1.0);
    const float2x2 m = float2x2(float2(sin(uv.y*cos(uv.x+i)+i*0.1)*20.0, -6.0),
                       float2(sin(uv.x+i*1.5)*3.0,-cos(uv.y-i)*2.0));


    uv = rotate(uv.x,uv.y,length(uv)+i*.4);
    c.rg = cos(sin(uv.xx+uv.yy)*m-i);
    c.b = sin(rotate(uv.x,uv.x,length(uv.xx)*3.0+i).x-uv.y+i);
    float4 color = float4(1.0-hue(c,i).rgb,1.0);
    ////////
    float4 finalColor;
    float4 FloweringColor;
    /*FloweringColor.r = (color.r+(textureColor.r*1.3))/2;
    FloweringColor.g = (color.g + (textureColor.g*1.3))/2;
    FloweringColor.b = (color.b + (textureColor.b*1.3))/2;
    FloweringColor.a = 1.0;*/

    float4 cam = textureColor;
    float4 overlay = color;

    FloweringColor = float4(cam.rgb * (1.0 - overlay.a) + setfloweringlum(overlay.rgb, floweringlum(cam.rgb)) * overlay.a, cam.a);

    float3 csbcolor = FloweringContrastSaturationBrightness(FloweringColor.rgb, contrast, saturation, brightness);
    float alpha = 1.0;
    finalColor = float4(csbcolor.r, csbcolor.g, csbcolor.b, alpha);

    return finalColor;//float4(textureColor.a, textureColor.a, textureColor.a, 1.0);
}

Upvotes: 3

Views: 1809

Answers (1)

warrenm
warrenm

Reputation: 31782

This is due to a difference in behavior between GLSL's mod function and Metal's fmod function. In Metal, GLSL's mod function would look like this:

float mod(float x, float y) {
    return x - y * floor(x / y);
}

while Metal's own fmod is equivalent to

float fmod(float x, float y) {
    return x - y * trunc(x / y);
}

The intermediate operations respectively floor (toward negative infinity) or truncate (toward zero). If you replace your calls to fmod with calls to the version of mod above that emulates GLSL, you should observe identical behavior between the two.

You can flip the coordinate system to match GL's by replacing any occurrences of the texture coordinates (u, v) with (u, 1-v). That will make the lobes revolve clockwise rather than counterclockwise as they currently do in your Metal implementation. It's easiest to just do this transformation once in the vertex function.

Upvotes: 13

Related Questions