Search Unity

Translating a GLSL shader (noise algorithm) to HLSL/CG

Discussion in 'Shaders' started by AddySm, Jul 29, 2017.

  1. AddySm

    AddySm

    Joined:
    Jul 29, 2017
    Posts:
    2
    I am in the process of translating a few shader functions (for periodic simplex noise) written for WebGL to HLSL.
    After taking care of the usual syntax differences the HLSL version compiles, but parts of the result are flipped around - as seen here, left is the original:



    The relevant portions of the code (original and translation) are below, for the output above I just used a dummy shader program with the equivalent of `fragmentw = psnoise(float2(input.uv.x * 6, input.uv.y * 6), float2(6, 6)) * 0.5 + 0.5;` in the fragment shader part.

    Code (GLSL):
    1.  
    2. //
    3. // 2-D tiling simplex noise with fixed gradients,
    4. // without the analytical derivative.
    5. // This function is implemented as a wrapper to "psrnoise",
    6. // at the minimal cost of three extra additions.
    7. //
    8. float psnoise(vec2 pos, vec2 per) {
    9.   return psrnoise(pos, per, 0.0);
    10. }
    11.  
    12.  
    13. // Modulo 289, optimizes to code without divisions
    14. vec3 mod289(vec3 x) {
    15.   return x - floor(x * (1.0 / 289.0)) * 289.0;
    16. }
    17.  
    18. float mod289(float x) {
    19.   return x - floor(x * (1.0 / 289.0)) * 289.0;
    20. }
    21.  
    22. // Permutation polynomial (ring size 289 = 17*17)
    23. vec3 permute(vec3 x) {
    24.   return mod289(((x*34.0)+1.0)*x);
    25. }
    26.  
    27. float permute(float x) {
    28.   return mod289(((x*34.0)+1.0)*x);
    29. }
    30.  
    31. // Hashed 2-D gradients with an extra rotation.
    32. // (The constant 0.0243902439 is 1/41)
    33. vec2 rgrad2(vec2 p, float rot) {
    34. #if 0
    35. // Map from a line to a diamond such that a shift maps to a rotation.
    36.   float u = permute(permute(p.x) + p.y) * 0.0243902439 + rot; // Rotate by shift
    37.   u = 4.0 * fract(u) - 2.0;
    38.   // (This vector could be normalized, exactly or approximately.)
    39.   return vec2(abs(u)-1.0, abs(abs(u+1.0)-2.0)-1.0);
    40. #else
    41. // For more isotropic gradients, sin/cos can be used instead.
    42.   float u = permute(permute(p.x) + p.y) * 0.0243902439 + rot; // Rotate by shift
    43.   u = fract(u) * 6.28318530718; // 2*pi
    44.   return vec2(cos(u), sin(u));
    45. #endif
    46. }
    47.  
    48.  
    49. //
    50. // 2-D tiling simplex noise with rotating gradients,
    51. // but without the analytical derivative.
    52. //
    53. float psrnoise(vec2 pos, vec2 per, float rot) {
    54.   // Offset y slightly to hide some rare artifacts
    55.   pos.y += 0.001;
    56.   // Skew to hexagonal grid
    57.   vec2 uv = vec2(pos.x + pos.y*0.5, pos.y);
    58.  
    59.   vec2 i0 = floor(uv);
    60.   vec2 f0 = fract(uv);
    61.   // Traversal order
    62.   vec2 i1 = (f0.x > f0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
    63.  
    64.   // Unskewed grid points in (x,y) space
    65.   vec2 p0 = vec2(i0.x - i0.y * 0.5, i0.y);
    66.   vec2 p1 = vec2(p0.x + i1.x - i1.y * 0.5, p0.y + i1.y);
    67.   vec2 p2 = vec2(p0.x + 0.5, p0.y + 1.0);
    68.  
    69.   // Integer grid point indices in (u,v) space
    70.   i1 = i0 + i1;
    71.   vec2 i2 = i0 + vec2(1.0, 1.0);
    72.  
    73.   // Vectors in unskewed (x,y) coordinates from
    74.   // each of the simplex corners to the evaluation point
    75.   vec2 d0 = pos - p0;
    76.   vec2 d1 = pos - p1;
    77.   vec2 d2 = pos - p2;
    78.  
    79.   // Wrap i0, i1 and i2 to the desired period before gradient hashing:
    80.   // wrap points in (x,y), map to (u,v)
    81.   vec3 xw = mod(vec3(p0.x, p1.x, p2.x), per.x);
    82.   vec3 yw = mod(vec3(p0.y, p1.y, p2.y), per.y);
    83.   vec3 iuw = xw + 0.5 * yw;
    84.   vec3 ivw = yw;
    85.  
    86.   // Create gradients from indices
    87.   vec2 g0 = rgrad2(vec2(iuw.x, ivw.x), rot);
    88.   vec2 g1 = rgrad2(vec2(iuw.y, ivw.y), rot);
    89.   vec2 g2 = rgrad2(vec2(iuw.z, ivw.z), rot);
    90.  
    91.   // Gradients dot vectors to corresponding corners
    92.   // (The derivatives of this are simply the gradients)
    93.   vec3 w = vec3(dot(g0, d0), dot(g1, d1), dot(g2, d2));
    94.  
    95.   // Radial weights from corners
    96.   // 0.8 is the square of 2/sqrt(5), the distance from
    97.   // a grid point to the nearest simplex boundary
    98.   vec3 t = 0.8 - vec3(dot(d0, d0), dot(d1, d1), dot(d2, d2));
    99.  
    100.   // Set influence of each surflet to zero outside radius sqrt(0.8)
    101.   t = max(t, 0.0);
    102.  
    103.   // Fourth power of t
    104.   vec3 t2 = t * t;
    105.   vec3 t4 = t2 * t2;
    106.  
    107.   // Final noise value is:
    108.   // sum of ((radial weights) times (gradient dot vector from corner))
    109.   float n = dot(t4, w);
    110.  
    111.   // Rescale to cover the range [-1,1] reasonably well
    112.   return 11.0*n;
    113. }
    114.  
    Code (HLSL):
    1.  
    2. //
    3. // 2-D tiling simplex noise with fixed gradients,
    4. // without the analytical derivative.
    5. // This function is implemented as a wrapper to "psrnoise",
    6. // at the minimal cost of three extra additions.
    7. //
    8. float psnoise(float2 pos, float2 per) {
    9.   return psrnoise(pos, per, 0.0);
    10. }
    11.  
    12.  
    13. // GLSL mod
    14. float modx(float3 x, float y) {
    15.   return x - floor(x * (1.0 / y)) * y;
    16. }
    17. float modx(float x, float y) {
    18.   return x - floor(x * (1.0 / y)) * y;
    19. }
    20.  
    21. // Modulo 289, optimizes to code without divisions
    22. float3 mod289(float3 x) {
    23.   return x - floor(x * (1.0 / 289.0)) * 289.0;
    24. }
    25. float mod289(float x) {
    26.   return x - floor(x * (1.0 / 289.0)) * 289.0;
    27. }
    28.  
    29. // Permutation polynomial (ring size 289 = 17*17)
    30. float3 permute(float3 x) {
    31.   return mod289(((x*34.0)+1.0)*x);
    32. }
    33. float permute(float x) {
    34.   return mod289(((x*34.0)+1.0)*x);
    35. }
    36.  
    37. // Hashed 2-D gradients with an extra rotation.
    38. // (The constant 0.0243902439 is 1/41)
    39. float2 rgrad2(float2 p, float rot) {
    40. #if 0
    41. // Map from a line to a diamond such that a shift maps to a rotation.
    42.   float u = permute(permute(p.x) + p.y) * 0.0243902439 + rot; // Rotate by shift
    43.   u = 4.0 * frac(u) - 2.0;
    44.   // (This vector could be normalized, exactly or approximately.)
    45.   return float2(abs(u)-1.0, abs(abs(u+1.0)-2.0)-1.0);
    46. #else
    47. // For more isotropic gradients, sin/cos can be used instead.
    48.   float u = permute(permute(p.x) + p.y) * 0.0243902439 + rot; // Rotate by shift
    49.   u = frac(u) * 6.28318530718; // 2*pi
    50.   return float2(cos(u), sin(u));
    51. #endif
    52. }
    53.  
    54.  
    55. //
    56. // 2-D tiling simplex noise with rotating gradients,
    57. // but without the analytical derivative.
    58. //
    59. float psrnoise(float2 pos, float2 per, float rot) {
    60.   // Offset y slightly to hide some rare artifacts
    61.   pos.y += 0.001;
    62.   // Skew to hexagonal grid
    63.   float2 uv = float2(pos.x + pos.y*0.5, pos.y);
    64.  
    65.   float2 i0 = floor(uv);
    66.   float2 f0 = frac(uv);
    67.   // Traversal order
    68.   float2 i1 = (f0.x > f0.y) ? float2(1.0, 0.0) : float2(0.0, 1.0);
    69.  
    70.   // Unskewed grid points in (x,y) space
    71.   float2 p0 = float2(i0.x - i0.y * 0.5, i0.y);
    72.   float2 p1 = float2(p0.x + i1.x - i1.y * 0.5, p0.y + i1.y);
    73.   float2 p2 = float2(p0.x + 0.5, p0.y + 1.0);
    74.  
    75.   // Integer grid point indices in (u,v) space
    76.   i1 = i0 + i1;
    77.   float2 i2 = i0 + float2(1.0, 1.0);
    78.  
    79.   // Vectors in unskewed (x,y) coordinates from
    80.   // each of the simplex corners to the evaluation point
    81.   float2 d0 = pos - p0;
    82.   float2 d1 = pos - p1;
    83.   float2 d2 = pos - p2;
    84.  
    85.   // Wrap i0, i1 and i2 to the desired period before gradient hashing:
    86.   // wrap points in (x,y), map to (u,v)
    87.   float3 xw = modx(float3(p0.x, p1.x, p2.x), per.x);
    88.   float3 yw = modx(float3(p0.y, p1.y, p2.y), per.y);
    89.   float3 iuw = xw + 0.5 * yw;
    90.   float3 ivw = yw;
    91.  
    92.   // Create gradients from indices
    93.   float2 g0 = rgrad2(float2(iuw.x, ivw.x), rot);
    94.   float2 g1 = rgrad2(float2(iuw.y, ivw.y), rot);
    95.   float2 g2 = rgrad2(float2(iuw.z, ivw.z), rot);
    96.  
    97.   // Gradients dot vectors to corresponding corners
    98.   // (The derivatives of this are simply the gradients)
    99.   float3 w = float3(dot(g0, d0), dot(g1, d1), dot(g2, d2));
    100.  
    101.   // Radial weights from corners
    102.   // 0.8 is the square of 2/sqrt(5), the distance from
    103.   // a grid point to the nearest simplex boundary
    104.   float3 t = 0.8 - float3(dot(d0, d0), dot(d1, d1), dot(d2, d2));
    105.  
    106.   // Set influence of each surflet to zero outside radius sqrt(0.8)
    107.   t = max(t, 0.0);
    108.  
    109.   // Fourth power of t
    110.   float3 t2 = t * t;
    111.   float3 t4 = t2 * t2;
    112.  
    113.   // Final noise value is:
    114.   // sum of ((radial weights) times (gradient dot vector from corner))
    115.   float n = dot(t4, w);
    116.  
    117.   // Rescale to cover the range [-1,1] reasonably well
    118.   return 11.0*n;
    119. }
    120.  
    121.  

    Can anyone see what's causing the patterned rotation in the result of the shader?
     
  2. AddySm

    AddySm

    Joined:
    Jul 29, 2017
    Posts:
    2
    I solved it: The "mod" replacement (modx) I wrote up simply didn't have the correct return type - one of the variants has to return "float3" instead of "float". HLSL seems to just accept this and does not error or warn - Some automatic type conversion seems to be happening behind the scenes.

    But I also found out that simply using the "%" operator is a simple replacement for GLSL's "mod" function, which is an even simpler solution, (but with slightly different results that make it non-applicable here).
     
    Last edited: Jul 29, 2017
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    The hlsl "equivalent" of glsl mod is fmod, which x % y is often equivalent to fmod(x, y) in hlsl (though not always!), unlike % in glsl which is specifically for integers.

    However mod and fmod aren't actually equivalent (nor are mod and hlsl %).

    glsl mod(x, y) is:
    x - y * floor(x/y)

    hlsl fmod(x, y) is:
    x - y * trunc(x/y)

    The most obvious difference from this is in glsl the output sign depends on the sign of y, in hlsl the sign depends on x:

    Code (csharp):
    1.  mod(1.5, 1) == 0.5    //  glsl mod +, + = +
    2. fmod(1.5, 1) == 0.5    // hlsl fmod +, + = +
    3.  
    4.  mod(-1.5, -1) == -0.5 //  glsl mod -, - = -
    5. fmod(-1.5, -1) == -0.5 // hlsl fmod -, - = -
    6.  
    7.  mod(-1.5, 1) == 0.5   //  glsl mod -, + = +
    8. fmod(-1.5, 1) == -0.5  // hlsl fmod -, + = -
    9.  
    10.  mod(1.5, -1) == -0.5  //  glsl mod +, - = -
    11. fmod(1.5, -1) == 0.5   // hlsl fmod +, - = +
    12.  
    13. // and the real kicker
    14.  mod(-1.25, 1) == 0.75
    15. fmod(-1.25, 1) == -0.25

    I personally like implementing functions like this using a define so I can let the compiler deal with the "return" type. This relies on the compiler not being too strict though, which might cause it to complain on mobile devices.

    // glsl style mod
    #define mod(x, y) (x - y * floor(x / y))
     
  4. Pacosmico

    Pacosmico

    Joined:
    Jan 16, 2014
    Posts:
    14
    @bgolus and @Bunny83 , you both people keep saving my life, you should get a medal. I just keep running into your answers over and over, time after time, and they are always spot on. Unity should pay you a salary for your impact in the community. You are polite, wise, clear. Literally can't ask for a better person to exist in a gamedev community... In any community really!
     
    domportera likes this.
  5. domportera

    domportera

    Joined:
    Sep 12, 2013
    Posts:
    23
    hard agree. I been running into @bgolus left and right and he has made the world a better place for me to live in. unity, pay this man