Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

bumpmap (grayscale) to normalmap (rgb)

Discussion in 'Scripting' started by metervara, Jul 23, 2007.

  1. metervara

    metervara

    Joined:
    Jun 15, 2006
    Posts:
    203
    I'm generating some grayscale textures with code and need to convert them to normal maps (in a script) for use with bumped shaders, has anyone tried that? I'd like to do it in the Update function so it needs to be pretty fast. The grayscale step does not need to be stored as a texture if that's slower than built in arrays i guess.

    /P
     
  2. Omar Rojo

    Omar Rojo

    Joined:
    Jan 4, 2007
    Posts:
    494
    MMmmm depending on the texture resolution, i dont know, i want some thoughts about this too..

    .ORG
     
  3. Aras

    Aras

    Unity Technologies

    Joined:
    Nov 7, 2005
    Posts:
    4,770
    A good normal map filter is called "Sobel filter", it basically takes derivatives in both directions with a 3x3 filter and you get the normal out of that.

    A simple way is: for each pixel, take difference in heights horizontally and vertically. Imagine them as vectors, i.e. horizontal to next pixel is (1,0,deltaHeightHorizontal) and vertical to next pixel is (0,1,deltaHeightVertical). These vectors lie on the "plane of the surface" at that pixel. Now doing a cross product of those will give the normal.

    Finally, encode the normal so that each component -1.0 = 0, 0.0 = 128, +1.0 = 255.

    All this can be done on the GPU of course, by sampling the heightmap and outputting the normal map into the render texture.
     
    Deleted User likes this.
  4. metervara

    metervara

    Joined:
    Jun 15, 2006
    Posts:
    203
    Thanks.

    I'll see what i can dig up on "Sobel Filter".

    My first attemt was kind of successful. Just checking grayscale difference of neighboring pixels and generating red+green values for the color from that. Problem is the 'bumpiness' isn't very high so the effect is not very visible. (Left: generated, Right: Converted by Unity)



    Code (csharp):
    1.  
    2. for(int y=1;y<_source.width-1;y++){
    3.    for(int x=1;x<_source.height-1;x++){
    4.       float xLeft = _source.GetPixel(x-1,y).grayscale;
    5.       float xRight = _source.GetPixel(x+1,y).grayscale;
    6.       float yUp = _source.GetPixel(x,y-1).grayscale;
    7.       float yDown = _source.GetPixel(x,y+1).grayscale;
    8.       float xDelta = ((xLeft-xRight)+1)*.5f;
    9.       float yDelta = ((yUp-yDown)+1)*.5f;
    10.       _destination.SetPixel(x,y,new Color(xDelta,yDelta,1f,1f));
    11.    }
    12. }
    13.  
    I'll treat them as vectors instead like you propose and see if I can't do it on the GPU instead :)

    /P
     
  5. metervara

    metervara

    Joined:
    Jun 15, 2006
    Posts:
    203
    Trying to get this to work in a fragment program.

    At the moment I just want to output the normal color to see how it works, but all I get is the "normal map blue" (0.5,0.5,1). I have a alpha heightmap in _MainTex, 128x128px, and the fragment program looks like this:

    Code (csharp):
    1.  
    2. half4 frag (v2f i) : COLOR {
    3.  
    4.    float val = tex2D(_MainTex, i.uv).a;
    5.    float valX = tex2D(_MainTex, i.uv + float2(1 / 128, 0)).a;
    6.    float valY = tex2D(_MainTex, i.uv + float2(0, 1 / 128)).a;
    7.    float3 h = float3(1,0,val - valX);
    8.    float3 v = float3(0,1,val - valY);
    9.    float3 norm = cross(h,v);
    10.            
    11.    return half4(norm.x*0.5+0.5,norm.y*0.5+0.5,norm.z*0.5+0.5,1);
    12. }
    I'm not sure what goes on with the cross product so maybe that's where it goes wrong?

    /P
     
  6. Aras

    Aras

    Unity Technologies

    Joined:
    Nov 7, 2005
    Posts:
    4,770
    First guess: 1/128 is integer division, so the result is zero. Try 1.0/128.0 perhaps?
     
  7. AaronC

    AaronC

    Joined:
    Mar 6, 2006
    Posts:
    3,552
    Hi, //digs up old thread

    Metervara did you figure out why the normal map generated in your first example was so subtle? I've experimented with a javascript version here and is working well, but as you posted with your example above its a very subtle effect.

    Did you find a way to amplify the effect at all? I hope you dont mind me sampling a bit of your code.

    Cheers
    AaronC

    Code (csharp):
    1. var bumpTexture : Texture2D;
    2. var bumpSource : Texture2D;
    3. var x: float;
    4. var y: float;
    5. var xLeft: float;
    6. var xRight: float;
    7. var yUp: float;
    8. var yDown: float;
    9. var yDelta: float;
    10. var xDelta: float;
    11. function Start () {
    12.    
    13.     bumpSource=gameObject.renderer.material.GetTexture("_BumpMap");
    14.     bumpTexture = new Texture2D (bumpSource.width, bumpSource.height, TextureFormat.ARGB32, false);
    15.    
    16.    
    17.     for (y=0; y < bumpTexture.height; y++) {
    18.     for (x=0; x < bumpTexture.width; x++) {
    19.    
    20.              xLeft = bumpSource.GetPixel(x-1,y).grayscale;
    21.              xRight = bumpSource.GetPixel(x+1,y).grayscale;
    22.              yUp = bumpSource.GetPixel(x,y-1).grayscale;
    23.              yDown = bumpSource.GetPixel(x,y+1).grayscale;
    24.              xDelta = ((xLeft-xRight)+1)*0.5;
    25.              yDelta = ((yUp-yDown)+1)*0.5;
    26.              
    27.         bumpTexture.SetPixel(x,y,new Color(xDelta,yDelta,1.0,1.0));
    28.          
    29.     }
    30.    
    31.    
    32.    }
    33.    
    34.   bumpTexture.Apply();
    35.   gameObject.renderer.material.SetTexture("_BumpMap",bumpTexture);
    36.  
    37. }
     

    Attached Files:

  8. b4cksp4ce

    b4cksp4ce

    Joined:
    Apr 13, 2011
    Posts:
    114
    Sorry to bump old post but I found a solution to enhance this effect and get the result of the "create normal from grayscale" in UNITY importer.

    First I added an S-curve on the detals to control more the sharpness of the normal map created in the function. I find it good when the distortion varies between 10 and 20 !

    Code (CSharp):
    1. n.SetPixel(x,y,new Color( Mathf.Clamp01(sCurve(xDelta, distortion)) , Mathf.Clamp01(sCurve(yDelta, distortion)),1f,1.0f));
    Code (csharp):
    1.  
    2. public static float sCurve(float x, float distortion){
    3.         return 1f / ( 1f + Mathf.Exp(-Mathf.Lerp(5f,15f,distortion)*(x-0.5f)));
    4.     }
    5.  
    And then I pass the resulting texture in this function :

    Code (csharp):
    1.  
    2.  
    3. public static Texture2D toNormalMap(Texture2D t){
    4.  
    5.         Texture2D n = new Texture2D(t.width,t.height,TextureFormat.ARGB32,true);
    6.         Color oldColor = new Color();
    7.         Color newColor = new Color();
    8.      
    9.         for (int x=0; x<t.width; x++){
    10.             for (int y=0; y<t.height; y++){
    11.              
    12.                 oldColor = t.GetPixel(x,y);
    13.                 newColor.r = oldColor.g;
    14.                 newColor.b = oldColor.g;
    15.                 newColor.g = oldColor.g;
    16.                 newColor.a = oldColor.r;
    17.                 n.SetPixel(x,y, newColor);
    18.             }
    19.         }
    20.      
    21.         n.Apply();
    22.      
    23.         return n;
    24.     }
    25.  
    26.  
     
  9. sumpfkraut

    sumpfkraut

    Joined:
    Jan 18, 2013
    Posts:
    242
    GetPixel is very slow if you use it for every pixel, and for every pixel multiple times!
    Here is a similar function with less GetPixel calls (only once/column):

    Code (CSharp):
    1. public static Texture2D CalculateNormal(Texture2D sourceImage, float strength = 1f)
    2. {
    3.     Mathf.Clamp(strength, 0.0f, 10.0f);
    4.     int w = sourceImage.width;
    5.     int h = sourceImage.height;
    6.     Texture2D res = new Texture2D(w, h);
    7.     Color[] current_col = null;
    8.     Color[] l_col = null;
    9.     Color[] r_col = null;
    10.     float sample_l, sample_r, sample_u, sample_d;
    11.     float x_vector, y_vector;
    12.  
    13.     for (int x = 0; x < w; x++)
    14.     {
    15.         //Left column = previous column
    16.         if (current_col != null) l_col = current_col;
    17.  
    18.         //Current column = right column
    19.         if (r_col != null) current_col = r_col;
    20.         else current_col = sourceImage.GetPixels(x, 0, 1, h);
    21.  
    22.         //If left column null, take current column (if x=0)
    23.         if (l_col == null) l_col = current_col;
    24.  
    25.         //Right column = next column (everytime except the last run)
    26.         if (x < w - 1) r_col = sourceImage.GetPixels(x + 1, 0, 1, h);
    27.         else r_col = current_col;
    28.  
    29.         for (int y = 0; y < current_col.Length; y++)
    30.         {
    31.             //Read pixel (left,right,up,down) from current
    32.             sample_l = l_col[y].grayscale * strength;
    33.             sample_r = r_col[y].grayscale * strength;
    34.             if (y < h - 1) sample_u = current_col[y + 1].grayscale * strength;
    35.             else sample_u = current_col[y].grayscale * strength;
    36.             if (y > 0) sample_d = current_col[y - 1].grayscale * strength;
    37.             else sample_d = current_col[y].grayscale * strength;
    38.  
    39.             //WebGL integer overflow on new Color() without Mathf.Clamp01()
    40.             x_vector = Mathf.Clamp01((((sample_l - sample_r) + 1) * 0.5f);
    41.             y_vector = Mathf.Clamp01((((sample_d - sample_u) + 1) * 0.5f);
    42.             Color col = new Color(x_vector, y_vector, 1f, 1f);
    43.             res.SetPixel(x, y, col);
    44.         }
    45.     }
    46.     res.Apply();
    47.     return res;
    48. }
    Code (CSharp):
    1. public static void SetMaterialNormal(Material mat, Texture2D tex, float value)
    2. {
    3.     //URP
    4.     mat.EnableKeyword("_NORMALMAP");
    5.     mat.SetTexture("_BumpMap", tex);
    6.     mat.SetFloat("_BumpScale", value);
    7. }
     
    Last edited: Oct 23, 2020
    horttanainen likes this.
  10. theforgot3n1

    theforgot3n1

    Joined:
    Sep 26, 2018
    Posts:
    205
    Because Unity's grayscale normal map algorithm does not work with tilemaps (visible edges between tiles because of its algorithm), I had to do it manually. sumpfkraut's algorithm allows for seamless tiling, but its edges are not as sharp against transparent areas. So I modified it slightly.


    Code (CSharp):
    1. public Texture2D CalculateNormal(Texture2D sourceImage, float strength)
    2.     {
    3.         Mathf.Clamp(strength, 0.0f, 10.0f);
    4.         int w = sourceImage.width;
    5.         int h = sourceImage.height;
    6.         Texture2D res = new Texture2D(w, h);
    7.         Color[] current_col = null;
    8.         Color[] l_col = null;
    9.         Color[] r_col = null;
    10.         float sample_l, sample_r, sample_u, sample_d;
    11.         float x_vector, y_vector;
    12.  
    13.         for (int x = 0; x < w; x++)
    14.         {
    15.             //Left column = previous column
    16.             if (current_col != null) l_col = current_col;
    17.  
    18.             //Current column = right column
    19.             if (r_col != null) current_col = r_col;
    20.             else current_col = sourceImage.GetPixels(x, 0, 1, h);
    21.  
    22.             //If left column null, take current column (if x=0)
    23.             if (l_col == null) l_col = current_col;
    24.  
    25.             //Right column = next column (everytime except the last run)
    26.             if (x < w - 1) r_col = sourceImage.GetPixels(x + 1, 0, 1, h);
    27.             else r_col = current_col;
    28.  
    29.             for (int y = 0; y < current_col.Length; y++)
    30.             {
    31.                 if (current_col[y].a < 0.05f)
    32.                 {
    33.                     res.SetPixel(x, y, new Color(0.5f,0.5f,1f,1f));
    34.                     continue;
    35.                 }
    36.                 //Read pixel (left,right,up,down) from current
    37.                 sample_l = l_col[y].grayscale * strength;
    38.                 if (l_col[y].a < 0.05f)
    39.                 {
    40.                     //sample_l = current_col[y].grayscale;
    41.                     sample_l = 0f;
    42.                 }
    43.                 sample_r = r_col[y].grayscale * strength;
    44.                 if (r_col[y].a < 0.05f)
    45.                 {
    46.                     sample_r = 0f;
    47.                 }
    48.                 if (y < h - 1)
    49.                 {
    50.                     sample_u = current_col[y + 1].grayscale * strength;
    51.                 }
    52.                 else
    53.                 {
    54.                     sample_u = current_col[y].grayscale * strength;
    55.                 }
    56.                 if (y > 0)
    57.                 {
    58.                     sample_d = current_col[y - 1].grayscale * strength;
    59.                 }
    60.                 else
    61.                 {
    62.                     sample_d = current_col[y].grayscale * strength;
    63.                 }
    64.  
    65.                 //WebGL integer overflow on new Color() without Mathf.Clamp01()
    66.                 x_vector = Mathf.Clamp01((((sample_l - sample_r) + 1) * 0.5f));
    67.                 y_vector = Mathf.Clamp01((((sample_d - sample_u) + 1) * 0.5f));
    68.                 Color col = new Color(x_vector, y_vector, 1f, 1f);
    69.                 res.SetPixel(x, y, col);
    70.             }
    71.         }
    72.         res.Apply();
    73.         return res;
    74.     }
    Hope this helps.
     
  11. horttanainen

    horttanainen

    Joined:
    Dec 3, 2020
    Posts:
    1
    I want to congratulate all of you. Very nice work!
     
  12. NanushTol

    NanushTol

    Joined:
    Jan 9, 2018
    Posts:
    127
    Bumping this because someone here might be able to help me, also trying in Unity Answers.

    I'm calculating curvature map from a height map in a shader, for small gradients it works nicely but for larger gradients it creates jittering artifacts.



    the first step in this shader is using a 3x3 Soble filter to create the normal from the height map, at this point you can see that with large gradients it starts showing the jittering artifacts, which leads to the artifacts in the curvature map as well.

    is there a way of getting a smoother normal maps? without passing the normal map through another blur filter? am I missing something in my implementation?

    I've also tried with different sample distanced for the Soble filter, some help a little bit, but no specific distance solves this issue

    The filtered texture is a 1024x1024 render texture - ARGB64 (16 bit per channel)

    this is the shader code:
    Code (CSharp):
    1. fixed4 frag(v2f i) : SV_Target
    2.              {
    3.                  float3 normal = sobleFilter3x3(_MainTex, _MainTex_TexelSize.x, i.uv, _Scale, _SobleSampleDist);
    4.                  // calculate curvature
    5.                  float3 derX = ddx(normal);
    6.                  float3 derY = ddy(normal);
    7.                  float3 xn = normal - derX;
    8.                  float3 xp = normal + derX;
    9.                  float3 yn = normal - derY;
    10.                  float3 yp = normal + derY;
    11.                  float s = tex2D(_MainTex, i.uv);
    12.                  float3 pos = float3(i.uv.x, i.uv.y, s);
    13.                  float mag = length(pos);
    14.                  float curvature = (cross(xn, xp).y - cross(yn, yp).x) * _Scale / mag;
    15.                  return fixed4((curvature + _Concave).xxx, 1);
    16.              }
    17.  
    18.              float3 sobleFilter3x3(sampler2D tex, float texalSize, float2 uv, float scale, float sampleDist)
    19.              {
    20.                  /*
    21.                  [6][7][8]
    22.                  [3][4][5]
    23.                  [0][1][2]
    24.                  */
    25.                  float ts = texalSize;
    26.                  float s[9];
    27.                  float2 off = {ts * sampleDist, -ts * sampleDist };// offsets
    28.                  s[0] = tex2D(tex, uv + float2(off.y, off.y));
    29.                  s[1] = tex2D(tex, uv + float2(0    , off.y));
    30.                  s[2] = tex2D(tex, uv + float2(off.x, off.y));
    31.                  s[3] = tex2D(tex, uv + float2(off.y, 0));
    32.                  s[5] = tex2D(tex, uv + float2(off.x, 0));
    33.                  s[6] = tex2D(tex, uv + float2(off.y, off.x));
    34.                  s[7] = tex2D(tex, uv + float2(0    , off.x));
    35.                  s[8] = tex2D(tex, uv + float2(off.x, off.x));
    36.                  float3 normal;
    37.                  normal.x = scale * -(s[2] - s[0] + 2 * (s[5] - s[3]) + s[8] - s[6]);
    38.                  normal.y = scale * -(s[6] - s[0] + 2 * (s[7] - s[1]) + s[8] - s[2]);
    39.                  normal.z = 1.0;
    40.                  return normalize(normal) * 0.5 + 0.5;
    41.              }