Grayscale to Normal?

I am in need of a shader that will convert a grayscale image (bumpmap) into a normal map. It seems that this would be theoretically possible… at least… I can’t think of any reasons with my limited shader knowledge why this wouldn’t be possible in unity.

Has anyone here done any work to this affect or have any ideas for accomplishing it? I tried a conversion method listed here:

http://forum.unity3d.com/threads/5714-bumpmap-%28grayscale%29-to-normalmap-%28rgb%29?p=43006#post43006

… using javascript but it was slow enough to be impractical for what I’m looking for. the shader method listed at the same url is incomplete… unfortunately.

It’s been some time since I’ve tested this, but I seem to remember that it used to work. Feel free to clean it up :slight_smile:

Shader "Custom/BumpMap" {
	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_HeightMap ("HeightMap (RGB)", 2D) = "black" {}
		//_BumpStrength ("HeightMap strength", Range (0.03, 10)) = 1.0
	}
	SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf BlinnPhong
      
      struct Input {
          float2 uv_MainTex;
      };
      sampler2D _MainTex;
      
      sampler2D _HeightMap;
      uniform float4 _HeightMap_TexelSize;
      
      //uniform float _BumpStrength;
      
      void surf (Input IN, inout SurfaceOutput o) 
      {
      		float3 normal = float3(0, 0, 1);
      	
      		float heightSampleCenter = tex2D (_HeightMap, IN.uv_MainTex).r;
      		float heightSampleRight = tex2D (_HeightMap, IN.uv_MainTex + float2(_HeightMap_TexelSize.x, 0)).r;
      		float heightSampleUp = tex2D (_HeightMap, IN.uv_MainTex + float2(0, _HeightMap_TexelSize.y)).r;
      
      		float sampleDeltaRight = heightSampleRight - heightSampleCenter;
      		float sampleDeltaUp = heightSampleUp - heightSampleCenter;
      
      		//TODO: Expose?
      		float _BumpStrength = 3.0f;
      
      		normal = cross(
      		float3(1, 0, sampleDeltaRight * _BumpStrength), 
      		float3(0, 1, sampleDeltaUp * _BumpStrength));
      
      
      		normal = normalize(normal);
      
          	o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;//*0.001 + normal*0.5 + 0.5;
          	o.Gloss = 0.9;
          	o.Specular = 1;
          	o.Normal = normal;
          
      }
      ENDCG
    }
    
}
1 Like

Weeeellll… that is certainly a start! :smile: I’ll have to go into the shader and play around a bit until it looks right, but it is certainly starting to approximate a normal map.

Thanks for the help ToreTank!

Just make sure your source image isn’t DXT compressed, that will completely destroy the normal effect :slight_smile: Also, you should get a better result if you get sample more points around the center instead of only right and up.

ToreTank, all I can say is that you are my hero of the month!

Was able to incorporate your code into my existing shaders and get the exact functionality I was looking for. I actually didn’t really notice much difference between compressed and uncompressed textures, which is a big plus. :smile:

Well now this shader seems to be broken in Unity 3.2 :frowning: I’m glad I saved my old project before upgrading, but If I can’t get this to work properly I may have to figure out a different method. ugh. 2 steps forward, one step back.

OK, scratch that. I got it to work again by adding “#pragma target 3.0” at the start of the CG program… as detailed in this thread:

http://forum.unity3d.com/threads/42387-Maximum-texture-indirection-of-4-exceeded

Thanks Unity Forums!

I know this is an old thread but I wanna ask just in case. Do you by any chance still have access to the shader you made with this @chingwa ? Would you mind sharing it?
Thanks!

An other answer for a zombie thread :slight_smile: : If you need a normal map but only have a greyscale bump map, instead of creating a custom shader you can set your bump map to import as a normal map, and enable “Create from Grayscale”, and tweek the bumpiness and filtering setting. You’ll then have a normal map that suits to a lot of the shaders provided by Unity.

@Remy_Unity In most cases this is what you would want to do , but the original question was asking for a way to do this on-the-fly, rather than as an editor setting. I was using a generated grayscale texture and wanting to extract the normal info from grayscale directly in the shader.

@opponent019_unity The solution from @ToreTank above still works. I’ve put it into a modern Unity PBR shader below. Do note that you do lose some quality in the normal rendering by doing it this way. If you need the best quality then you should do it in the editor if possible.

Shader "Custom/CustomGrayscaleNormal" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _GrayTex ("GrayScale Normal", 2D) = "white" {}
        _NormStrength ("Normal Strength", Range(0,3)) = 3.0
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
      
        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        struct Input {
            float2 uv_MainTex;
            float2 uv_GrayTex;
        };

        sampler2D _MainTex;
        sampler2D _GrayTex;
        uniform float4 _GrayTex_TexelSize;
        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        half _NormStrength;

        void surf (Input IN, inout SurfaceOutputStandard o) {
            //Albedo
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;

            //Calculate Normal from Grayscale
            float3 graynorm = float3(0, 0, 1);
            float heightSampleCenter = tex2D (_GrayTex, IN.uv_GrayTex).r;
            float heightSampleRight = tex2D (_GrayTex, IN.uv_GrayTex + float2(_GrayTex_TexelSize.x, 0)).r;
            float heightSampleUp = tex2D (_GrayTex, IN.uv_GrayTex + float2(0, _GrayTex_TexelSize.y)).r;
            float sampleDeltaRight = heightSampleRight - heightSampleCenter;
            float sampleDeltaUp = heightSampleUp - heightSampleCenter;
            graynorm = cross(
            float3(1, 0, sampleDeltaRight * _NormStrength),
            float3(0, 1, sampleDeltaUp * _NormStrength));

            o.Normal = normalize(graynorm);
    
            //PBR Settings
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
1 Like

Is there any way to set this from code? I cant find it!..

Here : https://docs.unity3d.com/ScriptReference/TextureImporter-convertToNormalmap.html

1 Like