ShaderToy (GLSL) porting to HLSL

Hello, I'm trying to port the 2D Vector Field Flow shader from: https://www.shadertoy.com/view/4s23DG

That I break as simple as this:

// 2D vector field visualization by Morgan McGuire, @morgan3d, http://casual-effects.com
// Choose your arrow head style
const float ARROW_TILE_SIZE = 32.0;
// Used for ARROW_LINE_STYLE
const float ARROW_HEAD_LENGTH = ARROW_TILE_SIZE / 6.0;
// Computes the center pixel of the tile containing pixel pos
vec2 arrowTileCenterCoord(vec2 pos)
{
    return (floor(pos / ARROW_TILE_SIZE) + 0.5) * ARROW_TILE_SIZE;
}
// v = field sampled at tileCenterCoord(p), scaled by the length
// desired in pixels for arrows
// Returns 1.0 where there is an arrow pixel.
float arrow(vec2 p, vec2 v)
{
    // Make everything relative to the center, which may be fractional
    p -= arrowTileCenterCoord(p);

    float mag_v = length(v), mag_p = length(p);

    if (mag_v > 0.0)
    {
        // Non-zero velocity case
        vec2 dir_p = p / mag_p, dir_v = v / mag_v;

        // We can't draw arrows larger than the tile radius, so clamp magnitude.
        // Enforce a minimum length to help see direction
        mag_v = clamp(mag_v, 5.0, ARROW_TILE_SIZE / 2.0);
        // Arrow tip location
        v = dir_v * mag_v;

        // Define a 2D implicit surface so that the arrow is antialiased.
        // In each line, the left expression defines a shape and the right controls
        // how quickly it fades in or out.
        float dist = 2.0 / 4.0 - 
            max(abs(dot(p, vec2(dir_v.y, -dir_v.x))), // Width
                abs(dot(p, dir_v)) - mag_v + ARROW_HEAD_LENGTH / 2.0); // Length

        return clamp(1.0 + dist, 0.0, 1.0);
    }
    else 
    {
        // Center of the pixel is always on the arrow
        return max(0.0, 1.2 - mag_p);
    }
}
/////////////////////////////////////////////////////////////////////
// The vector field; use your own function or texture
vec2 field(vec2 pos) {
    return vec2(cos(pos.x * 0.01 + pos.y * 0.01) + cos(pos.y * 0.005 + iTime),
                2.0 * cos(pos.y * 0.01  + iTime * 0.3)) * 0.5;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    vec2 mainColor = field(arrowTileCenterCoord(fragCoord.xy));
    float arrowColor = 1.0 - arrow(fragCoord.xy, mainColor * ARROW_TILE_SIZE * 0.4);
    vec4 fieldColor = vec4(field(fragCoord.xy) * 0.5 + 0.5, 0.5, 1.0);

    fragColor = arrowColor * fieldColor;
}

Into the HLSL as follows:

    struct vsin
    {
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
    };

    vsin vert (appdata v)
    {
        vsin o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = v.uv;
        return o;
    }

    const float ARROW_TILE_SIZE = 32.0;

    // Computes the center pixel of the tile containing pixel pos
    float2 arrowTileCenterCoord(float2 pos)
    {
        float x = floor(pos.x / ARROW_TILE_SIZE) + 0.5;
        float y = floor(pos.y / ARROW_TILE_SIZE) + 0.5;
        return float2(x, y) * ARROW_TILE_SIZE;
    }

    // v = field sampled at tileCenterCoord(p), scaled by the length
    // desired in pixels for arrows
    // Returns 1.0 where there is an arrow pixel.
    float arrowMorgan(float2 p, float2 v)
    {
        // Make everything relative to the center, which may be fractional
        p -= arrowTileCenterCoord(p);

        float arrow_head_length = ARROW_TILE_SIZE / 6.0;

        float mag_v = length(v), mag_p = length(p);

        if (mag_v > 0.0) {
            // Non-zero velocity case
            float2 dir_p = p / mag_p, dir_v = v / mag_v;

            // We can't draw arrows larger than the tile radius, so clamp magnitude.
            // Enforce a minimum length to help see direction
            mag_v = clamp(mag_v, 5.0, ARROW_TILE_SIZE / 2.0);

            // Arrow tip location
            v = dir_v * mag_v;

            // Define a 2D implicit surface so that the arrow is antialiased.
            // In each line, the left expression defines a shape and the right controls
            // how quickly it fades in or out.

            float dist = 10.0 / 4.0 -
                max(abs(dot(p, float2(dir_v.y, -dir_v.x))), // Width
                    abs(dot(p, dir_v)) - mag_v + arrow_head_length / 2.0); // Length

            return clamp(1.0 + dist, 0.0, 1.0);
        }
        else {
            // Center of the pixel is always on the arrow
            return max(0.0, 1.2 - mag_p);
        }
    }

    // The vector field; use your own function or texture
    float2 field(float2 pos)
    {
        float x = cos(pos.x * 0.01 + pos.y * 0.01) + cos(pos.y * 0.005 + _Time.x);
        float y = 2.0 * cos(pos.y * 0.01 + _Time.y * 0.3) * 0.5;
        return float2(x, y);
    }

SubShader
    {
        Cull Off ZWrite Off ZTest Always

        // 1 : Flow Vector
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment fragVisualize

            float4 fragVisualize (vsin i) : SV_Target
            {
                float2 fieldDir = field(arrowTileCenterCoord(i.vertex));
                float4 arrowColor = 1.0 - arrowMorgan(i.vertex, fieldDir * ARROW_TILE_SIZE * 0.4);
                float4 fieldColor = float4(field(i.vertex) * 0.5 + 0.5, 0.5, 1.0);
                return arrowColor * fieldColor;
            }
            ENDCG
        }
    }

But for some reason, the shader in unity does not create the lines as seen in the Shader Toy example:

5101610--503099--shadertoy.png

5101610--503102--unity_shader.png

I'm using the following documentation in order to create the shader:

Know issues:

  • it can the calculation of the center pixel in the shader unity using the arrowTileCenterCoord method?
  • does it have to be the fact that "UV coordinates in GLSL have 0 at the top and increase downwards, in HLSL 0 is at the bottom and increases upwards, so you may need to use uv.y = 1 – uv.y at some point"?

Any help is really appreciated :)

Shader "VectorFieldFlow"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex VSMain
            #pragma fragment PSMain

            static const float PI = 3.1415927;
            static const int   ARROW_V_STYLE = 1;
            static const int   ARROW_LINE_STYLE = 2;
            static const int   ARROW_STYLE = ARROW_LINE_STYLE;
            static const float ARROW_TILE_SIZE = 64.0;
            static const float ARROW_HEAD_ANGLE = 45.0 * PI / 180.0;
            static const float ARROW_HEAD_LENGTH = ARROW_TILE_SIZE / 6.0;
            static const float ARROW_SHAFT_THICKNESS = 3.0;

            float2 arrowTileCenterCoord(float2 pos)
            {
                return (floor(pos / ARROW_TILE_SIZE) + 0.5) * ARROW_TILE_SIZE;
            }

            float arrow(float2 p, float2 v)
            {
                p -= arrowTileCenterCoord(p);
                float mag_v = length(v), mag_p = length(p);   
                if (mag_v > 0.0)
                {
                    float2 dir_p = p / mag_p, dir_v = v / mag_v;
                    mag_v = clamp(mag_v, 5.0, ARROW_TILE_SIZE / 2.0);
                    v = dir_v * mag_v;
                    float dist;
                    if (ARROW_STYLE == ARROW_LINE_STYLE)
                    {
                        dist = max(ARROW_SHAFT_THICKNESS / 4.0 - max(abs(dot(p, float2(dir_v.y, -dir_v.x))),
                            abs(dot(p, dir_v)) - mag_v + ARROW_HEAD_LENGTH / 2.0),
                            min(0.0, dot(v - p, dir_v) - cos(ARROW_HEAD_ANGLE / 2.0) * length(v - p)) * 2.0 +
                            min(0.0, dot(p, dir_v) + ARROW_HEAD_LENGTH - mag_v));
                    }
                    else
                    {
                        dist = min(0.0, mag_v - mag_p) * 2.0 +
                                min(0.0, dot(normalize(v - p), dir_v) - cos(ARROW_HEAD_ANGLE / 2.0)) * 2.0 * length(v - p) +
                                min(0.0, dot(p, dir_v) + 1.0) +
                                min(0.0, cos(ARROW_HEAD_ANGLE / 2.0) - dot(normalize(v * 0.33 - p), dir_v)) * mag_v * 0.8;
                    }
                    return clamp(1.0 + dist, 0.0, 1.0);
                }
                else
                {
                    return max(0.0, 1.2 - mag_p);
                }
            }

            float2 field(float2 pos)
            {
                return float2(cos(pos.x*0.01+pos.y*0.01)+cos(pos.y*0.005+_Time.g), 2.0*cos(pos.y*0.01+_Time.g*0.3)) * 0.5;
            }           

            void VSMain (inout float4 vertex:POSITION, inout float2 uv:TEXCOORD0)
            {
                vertex = UnityObjectToClipPos(vertex);
            }

            float4 PSMain (float4 vertex:POSITION, float2 uv:TEXCOORD0) : SV_Target
            {
                float2 fragCoord = uv * float2(1024,1024);
                return (1.0 - arrow(fragCoord.xy, field(arrowTileCenterCoord(fragCoord.xy)) * ARROW_TILE_SIZE * 0.4)) * float4(field(fragCoord.xy) * 0.5 + 0.5, 0.5, 1.0);
            }

            ENDCG
        }
    }
}

5101964--503171--upload_2019-10-24_13-44-12.jpg

2 Likes

It might be beneficial to point out what the mistakes were, instead of providing functioning code, so that others can learn too.

Unless you use the static word in front of the constants you are defining, the values will be zero inside the shader. You would assign them from C# side. You can try for example to define a preset value to test this:

static const float4 TestColor = { 0,0,1,1 };

Now try to return that in the fragment program and see the difference when the variable is defined static. With static you get the blue color, without black (all zero).

That's why your arrows got messed up and that zero ARROW_TILE_SIZE shrunk your arrows to nothingness...

And another issue is the UV scale that often causes some issues with ShaderToy porting attempts; If you take typical Unity input UV, it's between 0-1 while those coordinates are the image pixel dimensions. That's why @Przemyslaw_Zaworski multiplied those UVs with 1024.

1 Like

@Przemyslaw_Zaworski that is amazing, I was cracking down this code for days. Thanks for the quick reply.

@Olmi thank you very much for explaining the issues with the porting. It's very helpful to know that while reading the new code. As you say to understand the mistakes.