Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.

ShaderToy (GLSL) porting to HLSL

Discussion in 'Shaders' started by angelomoro, Oct 24, 2019.

  1. angelomoro

    angelomoro

    Joined:
    Sep 4, 2018
    Posts:
    13
    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:

    Code (CSharp):
    1. // 2D vector field visualization by Morgan McGuire, @morgan3d, http://casual-effects.com
    2. // Choose your arrow head style
    3. const float ARROW_TILE_SIZE = 32.0;
    4. // Used for ARROW_LINE_STYLE
    5. const float ARROW_HEAD_LENGTH = ARROW_TILE_SIZE / 6.0;
    6. // Computes the center pixel of the tile containing pixel pos
    7. vec2 arrowTileCenterCoord(vec2 pos)
    8. {
    9.     return (floor(pos / ARROW_TILE_SIZE) + 0.5) * ARROW_TILE_SIZE;
    10. }
    11. // v = field sampled at tileCenterCoord(p), scaled by the length
    12. // desired in pixels for arrows
    13. // Returns 1.0 where there is an arrow pixel.
    14. float arrow(vec2 p, vec2 v)
    15. {
    16.     // Make everything relative to the center, which may be fractional
    17.     p -= arrowTileCenterCoord(p);
    18.        
    19.     float mag_v = length(v), mag_p = length(p);
    20.    
    21.     if (mag_v > 0.0)
    22.     {
    23.         // Non-zero velocity case
    24.         vec2 dir_p = p / mag_p, dir_v = v / mag_v;
    25.        
    26.         // We can't draw arrows larger than the tile radius, so clamp magnitude.
    27.         // Enforce a minimum length to help see direction
    28.         mag_v = clamp(mag_v, 5.0, ARROW_TILE_SIZE / 2.0);
    29.         // Arrow tip location
    30.         v = dir_v * mag_v;
    31.        
    32.         // Define a 2D implicit surface so that the arrow is antialiased.
    33.         // In each line, the left expression defines a shape and the right controls
    34.         // how quickly it fades in or out.
    35.         float dist = 2.0 / 4.0 -
    36.             max(abs(dot(p, vec2(dir_v.y, -dir_v.x))), // Width
    37.                 abs(dot(p, dir_v)) - mag_v + ARROW_HEAD_LENGTH / 2.0); // Length
    38.        
    39.         return clamp(1.0 + dist, 0.0, 1.0);
    40.     }
    41.     else
    42.     {
    43.         // Center of the pixel is always on the arrow
    44.         return max(0.0, 1.2 - mag_p);
    45.     }
    46. }
    47. /////////////////////////////////////////////////////////////////////
    48. // The vector field; use your own function or texture
    49. vec2 field(vec2 pos) {
    50.     return vec2(cos(pos.x * 0.01 + pos.y * 0.01) + cos(pos.y * 0.005 + iTime),
    51.                 2.0 * cos(pos.y * 0.01  + iTime * 0.3)) * 0.5;
    52. }
    53. void mainImage(out vec4 fragColor, in vec2 fragCoord)
    54. {
    55.     vec2 mainColor = field(arrowTileCenterCoord(fragCoord.xy));
    56.     float arrowColor = 1.0 - arrow(fragCoord.xy, mainColor * ARROW_TILE_SIZE * 0.4);
    57.     vec4 fieldColor = vec4(field(fragCoord.xy) * 0.5 + 0.5, 0.5, 1.0);
    58.    
    59.     fragColor = arrowColor * fieldColor;
    60. }
    Into the HLSL as follows:

    Code (CSharp):
    1.     struct vsin
    2.     {
    3.         float4 vertex : POSITION;
    4.         float2 uv : TEXCOORD0;
    5.     };
    6.  
    7.     vsin vert (appdata v)
    8.     {
    9.         vsin o;
    10.         o.vertex = UnityObjectToClipPos(v.vertex);
    11.         o.uv = v.uv;
    12.         return o;
    13.     }
    14.  
    15.     const float ARROW_TILE_SIZE = 32.0;
    16.  
    17.     // Computes the center pixel of the tile containing pixel pos
    18.     float2 arrowTileCenterCoord(float2 pos)
    19.     {
    20.         float x = floor(pos.x / ARROW_TILE_SIZE) + 0.5;
    21.         float y = floor(pos.y / ARROW_TILE_SIZE) + 0.5;
    22.         return float2(x, y) * ARROW_TILE_SIZE;
    23.     }
    24.  
    25.     // v = field sampled at tileCenterCoord(p), scaled by the length
    26.     // desired in pixels for arrows
    27.     // Returns 1.0 where there is an arrow pixel.
    28.     float arrowMorgan(float2 p, float2 v)
    29.     {
    30.         // Make everything relative to the center, which may be fractional
    31.         p -= arrowTileCenterCoord(p);
    32.  
    33.         float arrow_head_length = ARROW_TILE_SIZE / 6.0;
    34.  
    35.         float mag_v = length(v), mag_p = length(p);
    36.  
    37.         if (mag_v > 0.0) {
    38.             // Non-zero velocity case
    39.             float2 dir_p = p / mag_p, dir_v = v / mag_v;
    40.  
    41.             // We can't draw arrows larger than the tile radius, so clamp magnitude.
    42.             // Enforce a minimum length to help see direction
    43.             mag_v = clamp(mag_v, 5.0, ARROW_TILE_SIZE / 2.0);
    44.  
    45.             // Arrow tip location
    46.             v = dir_v * mag_v;
    47.  
    48.             // Define a 2D implicit surface so that the arrow is antialiased.
    49.             // In each line, the left expression defines a shape and the right controls
    50.             // how quickly it fades in or out.
    51.  
    52.             float dist = 10.0 / 4.0 -
    53.                 max(abs(dot(p, float2(dir_v.y, -dir_v.x))), // Width
    54.                     abs(dot(p, dir_v)) - mag_v + arrow_head_length / 2.0); // Length
    55.  
    56.             return clamp(1.0 + dist, 0.0, 1.0);
    57.         }
    58.         else {
    59.             // Center of the pixel is always on the arrow
    60.             return max(0.0, 1.2 - mag_p);
    61.         }
    62.     }
    63.  
    64.     // The vector field; use your own function or texture
    65.     float2 field(float2 pos)
    66.     {
    67.         float x = cos(pos.x * 0.01 + pos.y * 0.01) + cos(pos.y * 0.005 + _Time.x);
    68.         float y = 2.0 * cos(pos.y * 0.01 + _Time.y * 0.3) * 0.5;
    69.         return float2(x, y);
    70.     }
    71.  
    72. SubShader
    73.     {
    74.         Cull Off ZWrite Off ZTest Always
    75.  
    76.         // 1 : Flow Vector
    77.         Pass
    78.         {
    79.             CGPROGRAM
    80.             #pragma vertex vert
    81.             #pragma fragment fragVisualize
    82.  
    83.             float4 fragVisualize (vsin i) : SV_Target
    84.             {
    85.                 float2 fieldDir = field(arrowTileCenterCoord(i.vertex));
    86.                 float4 arrowColor = 1.0 - arrowMorgan(i.vertex, fieldDir * ARROW_TILE_SIZE * 0.4);
    87.                 float4 fieldColor = float4(field(i.vertex) * 0.5 + 0.5, 0.5, 1.0);
    88.                 return arrowColor * fieldColor;
    89.             }
    90.             ENDCG
    91.         }
    92.     }
    But for some reason, the shader in unity does not create the lines as seen in the Shader Toy example:

    shadertoy.png

    unity_shader.png

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

    - Unity Shadertoys (a.k.a Converting GLSL shaders to Cg/HLSL)
    - https://docs.unity3d.com/Manual/SL-VertexProgramInputs.html
    - https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-pguide

    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 :)
     
  2. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    317
    Code (CSharp):
    1. Shader "VectorFieldFlow"
    2. {
    3.     SubShader
    4.     {
    5.         Pass
    6.         {
    7.             CGPROGRAM
    8.             #pragma vertex VSMain
    9.             #pragma fragment PSMain
    10.  
    11.             static const float PI = 3.1415927;
    12.             static const int   ARROW_V_STYLE = 1;
    13.             static const int   ARROW_LINE_STYLE = 2;
    14.             static const int   ARROW_STYLE = ARROW_LINE_STYLE;
    15.             static const float ARROW_TILE_SIZE = 64.0;
    16.             static const float ARROW_HEAD_ANGLE = 45.0 * PI / 180.0;
    17.             static const float ARROW_HEAD_LENGTH = ARROW_TILE_SIZE / 6.0;
    18.             static const float ARROW_SHAFT_THICKNESS = 3.0;
    19.  
    20.             float2 arrowTileCenterCoord(float2 pos)
    21.             {
    22.                 return (floor(pos / ARROW_TILE_SIZE) + 0.5) * ARROW_TILE_SIZE;
    23.             }
    24.  
    25.             float arrow(float2 p, float2 v)
    26.             {
    27.                 p -= arrowTileCenterCoord(p);
    28.                 float mag_v = length(v), mag_p = length(p);  
    29.                 if (mag_v > 0.0)
    30.                 {
    31.                     float2 dir_p = p / mag_p, dir_v = v / mag_v;
    32.                     mag_v = clamp(mag_v, 5.0, ARROW_TILE_SIZE / 2.0);
    33.                     v = dir_v * mag_v;
    34.                     float dist;
    35.                     if (ARROW_STYLE == ARROW_LINE_STYLE)
    36.                     {
    37.                         dist = max(ARROW_SHAFT_THICKNESS / 4.0 - max(abs(dot(p, float2(dir_v.y, -dir_v.x))),
    38.                             abs(dot(p, dir_v)) - mag_v + ARROW_HEAD_LENGTH / 2.0),
    39.                             min(0.0, dot(v - p, dir_v) - cos(ARROW_HEAD_ANGLE / 2.0) * length(v - p)) * 2.0 +
    40.                             min(0.0, dot(p, dir_v) + ARROW_HEAD_LENGTH - mag_v));
    41.                     }
    42.                     else
    43.                     {
    44.                         dist = min(0.0, mag_v - mag_p) * 2.0 +
    45.                                 min(0.0, dot(normalize(v - p), dir_v) - cos(ARROW_HEAD_ANGLE / 2.0)) * 2.0 * length(v - p) +
    46.                                 min(0.0, dot(p, dir_v) + 1.0) +
    47.                                 min(0.0, cos(ARROW_HEAD_ANGLE / 2.0) - dot(normalize(v * 0.33 - p), dir_v)) * mag_v * 0.8;
    48.                     }
    49.                     return clamp(1.0 + dist, 0.0, 1.0);
    50.                 }
    51.                 else
    52.                 {
    53.                     return max(0.0, 1.2 - mag_p);
    54.                 }
    55.             }
    56.  
    57.             float2 field(float2 pos)
    58.             {
    59.                 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;
    60.             }          
    61.            
    62.             void VSMain (inout float4 vertex:POSITION, inout float2 uv:TEXCOORD0)
    63.             {
    64.                 vertex = UnityObjectToClipPos(vertex);
    65.             }
    66.  
    67.             float4 PSMain (float4 vertex:POSITION, float2 uv:TEXCOORD0) : SV_Target
    68.             {
    69.                 float2 fragCoord = uv * float2(1024,1024);
    70.                 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);
    71.             }
    72.            
    73.             ENDCG
    74.         }
    75.     }
    76. }
    77.  
    upload_2019-10-24_13-44-12.png
     
    angelomoro and Olmi like this.
  3. Olmi

    Olmi

    Joined:
    Nov 29, 2012
    Posts:
    1,553
    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:

    Code (CSharp):
    1. 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.
     
    angelomoro likes this.
  4. angelomoro

    angelomoro

    Joined:
    Sep 4, 2018
    Posts:
    13
    @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.