sobel operator - height to normal map on GPU

For some reason (mostly lazy), I don’t want to make the normal map on the editor. so that, I made it on runtime :slight_smile: That is based on the Sobel operator

sobel operation…

float3 height2normal_sobel(float3x3 c)
{		
	float3x3 x = float3x3(	 1.0, 0.0, -1.0,
							        2.0, 0.0, -2.0,
							        1.0, 0.0, -1.0  );

	float3x3 y = float3x3(	 1.0,  2.0,  1.0,
							        0.0,  0.0,  0.0,
							       -1.0, -2.0, -1.0 );
	
	x = x * c;
	y = y * c;

	float cx =  x[0][0] +x[0][2]
				   +x[1][0] +x[1][2]
				   +x[2][0] +x[2][2];
	
	float cy =  y[0][0] +y[0][1] +y[0][2]
				  +y[2][0] +y[2][1] +y[2][2];
				
	float cz =  sqrt(1-(cx*cx+cy*cy));
	
	return float3(cx, cy, cz);
}

( Edit: almost forget the float3x3 could do multiply directly :p)

texture read in…

float3x3 img3x3(sampler2D color_map, float2 tc, float ts, int ch)
{
	float 	d = 1.0/ts; // ts, texture sampling size
	float3x3 c;		
	c[0][0] = tex2D(color_map,tc + float2(-d,-d))[ch];
	c[0][1] = tex2D(color_map,tc + float2( 0,-d))[ch];
	c[0][2] = tex2D(color_map,tc + float2( d,-d))[ch];	
		
	c[1][0] = tex2D(color_map,tc + float2(-d, 0))[ch];
	c[1][1] = tex2D(color_map,tc                )[ch];
	c[1][2] = tex2D(color_map,tc + float2( d, 0))[ch];
		
	c[2][0] = tex2D(color_map,tc + float2(-d, d))[ch];
	c[2][1] = tex2D(color_map,tc + float2( 0, d))[ch];
	c[2][2] = tex2D(color_map,tc + float2( d, d))[ch];
	
	return c;
}

how to use …

float3x3 c = img3x3(color_map, IN.texcoord, texture_size, 0 );// red only
float3 normal = height2normal_sobel(c);
normal = normalize(float3(normal.xy, normal.z *bump_level));

Hope that is useful for someone like me (lazy :p)

2 Likes

good one - this sort of thing could be very useful for web deployment, if all you wanted was some noisy normalmaps (well, you could make it generate stuff like crazybump does) then this would save having to upload (and having to consider compressing) normalmaps!
good work :slight_smile:

Thank you som much for this :smile:
I know this is kind of an old thread but i extended the default surface shader with this if anyone intrested.

Shader "Custom/H2nTest"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _heightNrm ("Height2nrm (R)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
        _BumpLvl ("bumpscale", Range(-1,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex, _heightNrm;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        float4 _heightNrm_TexelSize;
        float _BumpLvl;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)




float3 height2normal_sobel(float3x3 c)
{     
    float3x3 x = float3x3(   1.0, 0.0, -1.0,
                                    2.0, 0.0, -2.0,
                                    1.0, 0.0, -1.0  );
    float3x3 y = float3x3(   1.0,  2.0,  1.0,
                                    0.0,  0.0,  0.0,
                                   -1.0, -2.0, -1.0 );
  
    x = x * c;
    y = y * c;
    float cx =  x[0][0] +x[0][2]
                   +x[1][0] +x[1][2]
                   +x[2][0] +x[2][2];
  
    float cy =  y[0][0] +y[0][1] +y[0][2]
                  +y[2][0] +y[2][1] +y[2][2];
              
    float cz =  sqrt(1-(cx*cx+cy*cy));
  
    return float3(cx, cy, cz);
}


        float3x3 img3x3(sampler2D color_map, float2 tc, float ts, int ch)
{
    float   d = 1.0/ts; // ts, texture sampling size
    float3x3 c;   
    c[0][0] = tex2D(color_map,tc + float2(-d,-d))[ch];
    c[0][1] = tex2D(color_map,tc + float2( 0,-d))[ch];
    c[0][2] = tex2D(color_map,tc + float2( d,-d))[ch];
      
    c[1][0] = tex2D(color_map,tc + float2(-d, 0))[ch];
    c[1][1] = tex2D(color_map,tc                )[ch];
    c[1][2] = tex2D(color_map,tc + float2( d, 0))[ch];
      
    c[2][0] = tex2D(color_map,tc + float2(-d, d))[ch];
    c[2][1] = tex2D(color_map,tc + float2( 0, d))[ch];
    c[2][2] = tex2D(color_map,tc + float2( d, d))[ch];
  
    return c;
}


        void surf (Input IN, inout SurfaceOutputStandard o)
        {

            float3x3 ce = img3x3(_heightNrm, IN.uv_MainTex, _heightNrm_TexelSize.w, 0 );// red only
            float3 normal = height2normal_sobel(ce);
            normal = normalize(float3(normal.xy, normal.z *_BumpLvl));


            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Normal = normal;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
           
        }
        ENDCG
    }
    FallBack "Diffuse"
}
1 Like

Why not just regular bump?

On top “sobel”, on bottom regular bump like here, but with texel size multiplied with an value. For 2 cases from 3 regular one seems look better for me.

1 Like