Search Unity

Translate Mathf.PerlinNoise into shader

Discussion in 'Shaders' started by xHammy, Oct 21, 2019.

  1. xHammy

    xHammy

    Joined:
    Aug 11, 2017
    Posts:
    27
    I'm trying to add a noise function to my shader that aligns to an identical noise function in a C# script. Shadergraph has a "Gradient Noise" node that would do the trick, except I have the inverse problem of not being able to call that function in C#.

    I either need to translate Mathf.PerlinNoise into the shader, translate gadient noise into C#, or otherwise generate a noise function that works in both.
     
  2. Invertex

    Invertex

    Joined:
    Nov 7, 2013
    Posts:
    1,549
    You could look at the source files in the ShaderGraph plugin and adapt the GradientNoise function to C#, though results may not be the same due to differences in hardware generating the noise and the different manner the two are processed. You could maintain consistency by using Graphics.Blit() to copy the noise to a RenderTexture to use on the C# side instead though, simply feeding it an unlit material that's using a shader with the gradient noise node running on it.
     
  3. xHammy

    xHammy

    Joined:
    Aug 11, 2017
    Posts:
    27
    Adapting Gradient Noise to C# is about what I'd like to do, but I've barely dabbled in hlsl
     
  4. xHammy

    xHammy

    Joined:
    Aug 11, 2017
    Posts:
    27
    I converted the Gradient Noise node source from it's HLSL
    Code (HLSL):
    1. float2 unity_gradientNoise_dir(float2 p)
    2. {
    3.     p = p % 289;
    4.     float x = (34 * p.x + 1) * p.x % 289 + p.y;
    5.     x = (34 * x + 1) * x % 289;
    6.     x = frac(x / 41) * 2 - 1;
    7.     return normalize(float2(x - floor(x + 0.5), abs(x) - 0.5));
    8. }
    9.  
    10. float unity_gradientNoise(float2 p)
    11. {
    12.     float2 ip = floor(p);
    13.     float2 fp = frac(p);
    14.     float d00 = dot(unity_gradientNoise_dir(ip), fp);
    15.     float d01 = dot(unity_gradientNoise_dir(ip + float2(0, 1)), fp - float2(0, 1));
    16.     float d10 = dot(unity_gradientNoise_dir(ip + float2(1, 0)), fp - float2(1, 0));
    17.     float d11 = dot(unity_gradientNoise_dir(ip + float2(1, 1)), fp - float2(1, 1));
    18.     fp = fp * fp * fp * (fp * (fp * 6 - 15) + 10);
    19.     return lerp(lerp(d00, d01, fp.y), lerp(d10, d11, fp.y), fp.x);
    20. }
    21.  
    22. void Unity_GradientNoise_float(float2 UV, float Scale, out float Out)
    23. {
    24.     Out = unity_gradientNoise(UV * Scale) + 0.5;
    25. }
    to to C#
    Code (CSharp):
    1. Vector2 gradientNoisedir(Vector2 p)
    2. {
    3.     p = new Vector2(p.x % 289, p.y % 289);
    4.     float x = (34 * p.x + 1) * p.x % 289 + p.y;
    5.     x = (34 * x + 1) * x % 289;
    6.     x = ((x / 41)%1) * 2 - 1;
    7.     return (new  Vector2(x - Mathf.Floor(x + 0.5f), Mathf.Abs(x) - 0.5f)).normalized;
    8. }
    9.  
    10. float gradientNoise(Vector2 p)
    11. {
    12.     Vector2 ip = new Vector2(Mathf.Floor(p.x), Mathf.Floor(p.y));
    13.     Vector2 fp = new Vector2(p.x%1, p.y%1);
    14.     float d00 = Vector3.Dot(gradientNoisedir(ip), fp);
    15.     float d01 = Vector3.Dot(gradientNoisedir(ip + new Vector2(0, 1)), fp - new Vector2(0, 1));
    16.     float d10 = Vector3.Dot(gradientNoisedir(ip + new Vector2(1, 0)), fp - new Vector2(1, 0));
    17.     float d11 = Vector3.Dot(gradientNoisedir(ip + new Vector2(1, 1)), fp - new Vector2(1, 1));
    18.     fp = fp * fp * fp * (fp * (fp * 6 - new Vector2(15,15)) + new Vector2(10, 10));
    19.     return Mathf.Lerp(Mathf.Lerp(d00, d01, fp.y), Mathf.Lerp(d10, d11, fp.y), fp.x);
    20. }
    21.  
    22. void Update()
    23. {
    24.     UV = new Vector2(transform.position.x, transform.position.z); //Irrelevant to the transfer, but relevant to observing it
    25.     Height = gradientNoise(UV * Scale) + 0.5f; //relevant to the transfer
    26.     transform.position = new Vector3(transform.position.x, Height*Amplitude - ((Height*Amplitude)/2), transform.position.z); //Irrelevant to the transfer, but relevant to observing it
    27. }
    but I've definitely gone wrong somewhere, and I don't know where. Above a certain Z position I see it making huge skips and leaps, going into diagonal lines instead of organic curves. Below that position, it's making decent looking curves, but still not aligning with the expected ones. I don't know where I've gone wrong.
     
    mgear and morepixels like this.
  5. xHammy

    xHammy

    Joined:
    Aug 11, 2017
    Posts:
    27
    hmm, there was an issue, not with the translation but with the visualizer, "((Height*Amplitude)/2)" should be "amplitude/2"...
    Anyway, now I find that there are "sectors" where the function is perfectly accurate to what I need, and "sectors" where it either isn't or not only isn't but is excessively erratic. For example, between (0,0) and something like (80,140), note I might have those access flopped, it does exactly what it is supposed to, but in the negative values the output is erratic, and above that range the outputs are out of sync for a while before eventaully coming back into sync... I don't get it at all, but at least it seems I'm... close...
     
  6. RangerAT

    RangerAT

    Joined:
    Feb 27, 2020
    Posts:
    1
    Same problem here, still need an answer to translate PerlinNoise in shader script.
     
  7. markuscpersson

    markuscpersson

    Joined:
    Jul 25, 2020
    Posts:
    1
    Has anyone got a solution to this yet?
     
  8. MathMith

    MathMith

    Joined:
    Nov 15, 2019
    Posts:
    5
    still no solution??
     
  9. joshuacwilde

    joshuacwilde

    Joined:
    Feb 4, 2018
    Posts:
    727
    Often (but not always!) when people talk about using perlin noise in shaders, they just mean generating a perlin noise texture in something like Gimp or Photoshop then sampling that in the shader. It can potentially be expensive to calculate noise at runtime, if it is used a lot. Nevertheless, check this out : https://github.com/keijiro/NoiseShader
     
    Last edited: Aug 30, 2022
  10. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,408
  11. luxtrike

    luxtrike

    Joined:
    Jul 22, 2013
    Posts:
    1
    hello guys, im strugglin with this problem too, i wonder if this is a precision matter, the code in c# (cpu) is using floats and the noise in the shadergraph (gpu) is using another type that has more precision, that must be causing those differences, that could explain why they get out of sync then after some time they get in sync again.
     
  12. SuperSquid9000

    SuperSquid9000

    Joined:
    Apr 19, 2022
    Posts:
    1
    I had to face this problem myself. In my case I wanted to spawn structures on land parts of a 2d map generated with a shader for a top down 2d game. But because this was done as a material on a quad there is no way of checking which parts are land and which are not on the C# side. I couldn't fide a way of syncing two copies one for C# logic another for visuals so instead I found a way around it:

    You can use Graphics.Blit() to copy a material with a shader on it to a render texture.

    Then you can convert the render texture to a Texture2D Like this:

    Code (CSharp):
    1. public Texture2D RenderTextureToTexture2D(RenderTexture renderTex)
    2. {
    3.     Texture2D tex = new Texture2D(renderTex.width, renderTex.height, TextureFormat.RGB24, false);
    4.     RenderTexture.active = renderTex;
    5.     tex.ReadPixels(new Rect(0, 0, renderTex.width, renderTex.height), 0, 0);
    6.     tex.Apply();
    7.     return tex;
    8. }
    Final if you need to do this for game logic purposes like me, A simple double For loop for looping over each pixel will work. (you might want to have control for the loop resolution)