Home Unity

creating normals from alpha/heightmap inside a shader?

AlecMoody
ngon master
Offline / Send Message
AlecMoody ngon master
I am making some custom terrain shaders with strumpy's editor and I want to be able to create normals based on my blend mask. Does anyone know how I can turn grayscale data into basic normal mapping info? I have seen someone do this in unreal but I can't remember where.

Replies

  • Farfarer
    Offline / Send Message
    Farfarer polycounter lvl 17
    You probably want to get the difference of the values pixel-by-pixel using ddx, I think.

    Off the top of my head... for CG/HLSL;

    float heightmap = your height map value;
    float3 normal;
    normal.x = ddx(heightmap);
    normal.y = ddy(heightmap);
    normal.z = sqrt(1 - normal.x*normal.x - normal.y * normal.y); // Reconstruct z component to get a unit normal.

    For OpenGL, I think the functions are dFdx and dFdy.

    DirectX11 has ddx_fine and ddy_fine which I think give more accurate results.


    That'll give you the normal in, ehr... screen space I think?

    Otherwise you can sample the heightmap multiple times, offsetting it by a pixel and working out the difference from those values. That should get you it in whatever space the base normal is in.

    Nicked frm here; http://www.gamedev.net/topic/594781-hlsl-height-map-to-normal-algorithm/
    1. float me = tex2D(heightMapSampler,IN.tex).x;
    2. float n = tex2D(heightMapSampler,float2(IN.tex.x,IN.tex.y+1.0/heightMapSizeY)).x;
    3. float s = tex2D(heightMapSampler,float2(IN.tex.x,IN.tex.y-1.0/heightMapSizeY)).x;
    4. float e = tex2D(heightMapSampler,float2(IN.tex.x+1.0/heightMapSizeX,IN.tex.y)).x;
    5. float w = tex2D(heightMapSampler,float2(IN.tex.x-1.0/heightMapSizeX,IN.tex.y)).x;
    6.  
    7. //find perpendicular vector to norm:
    8. float3 temp = norm; //a temporary vector that is not parallel to norm
    9. if(norm.x==1)
    10. temp.y+=0.5;
    11. else
    12. temp.x+=0.5;
    13.  
    14. //form a basis with norm being one of the axes:
    15. float3 perp1 = normalize(cross(norm,temp));
    16. float3 perp2 = normalize(cross(norm,perp1));
    17.  
    18. //use the basis to move the normal in its own space by the offset
    19. float3 normalOffset = -bumpHeightScale*(((n-me)-(s-me))*perp1 + ((e-me)-(w-me))*perp2);
    20. norm += normalOffset;
    21. norm = normalize(norm);
  • Farfarer
    Offline / Send Message
    Farfarer polycounter lvl 17
    Gave it a try out of curiosity.

    Here's the multi-sample method in a stripped-down surface shader.

    I had to change the handedness from the code above to match Unity (changing the sign of the operation when sampling the e and w values from + to - and vice versa).
    1. Shader "Debug/Normal Map From Height" {
    2. Properties {
    3. _Color ("Main Color", Color) = (1,1,1,1)
    4. _MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "white" {}
    5. _BumpMap ("Normal (Normal)", 2D) = "bump" {}
    6. _HeightMap ("Heightmap (R)", 2D) = "grey" {}
    7. _HeightmapStrength ("Heightmap Strength", Float) = 1.0
    8. _HeightmapDimX ("Heightmap Width", Float) = 2048
    9. _HeightmapDimY ("Heightmap Height", Float) = 2048
    10. }
    11.  
    12. SubShader{
    13. Tags { "RenderType" = "Opaque" }
    14.  
    15. CGPROGRAM
    16.  
    17. #pragma surface surf NormalsHeight
    18. #pragma target 3.0
    19.  
    20. struct Input
    21. {
    22. float2 uv_MainTex;
    23. };
    24.  
    25. sampler2D _MainTex, _BumpMap, _HeightMap;
    26. float _HeightmapStrength, _HeightmapDimX, _HeightmapDimY;
    27.  
    28. void surf (Input IN, inout SurfaceOutput o)
    29. {
    30. o.Albedo = fixed3(0.5);
    31.  
    32. float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
    33.  
    34. float me = tex2D(_HeightMap,IN.uv_MainTex).x;
    35. float n = tex2D(_HeightMap,float2(IN.uv_MainTex.x,IN.uv_MainTex.y+1.0/_HeightmapDimY)).x;
    36. float s = tex2D(_HeightMap,float2(IN.uv_MainTex.x,IN.uv_MainTex.y-1.0/_HeightmapDimY)).x;
    37. float e = tex2D(_HeightMap,float2(IN.uv_MainTex.x-1.0/_HeightmapDimX,IN.uv_MainTex.y)).x;
    38. float w = tex2D(_HeightMap,float2(IN.uv_MainTex.x+1.0/_HeightmapDimX,IN.uv_MainTex.y)).x;
    39.  
    40. float3 norm = normal;
    41. float3 temp = norm; //a temporary vector that is not parallel to norm
    42. if(norm.x==1)
    43. temp.y+=0.5;
    44. else
    45. temp.x+=0.5;
    46.  
    47. //form a basis with norm being one of the axes:
    48. float3 perp1 = normalize(cross(norm,temp));
    49. float3 perp2 = normalize(cross(norm,perp1));
    50.  
    51. //use the basis to move the normal in its own space by the offset
    52. float3 normalOffset = -_HeightmapStrength * ( ( (n-me) - (s-me) ) * perp1 + ( ( e - me ) - ( w - me ) ) * perp2 );
    53. norm += normalOffset;
    54. norm = normalize(norm);
    55.  
    56. o.Normal = norm;
    57. }
    58.  
    59. inline fixed4 LightingNormalsHeight (SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten)
    60. {
    61. viewDir = normalize(viewDir);
    62. lightDir = normalize(lightDir);
    63. s.Normal = normalize(s.Normal);
    64. float NdotL = dot(s.Normal, lightDir);
    65. _LightColor0.rgb = _LightColor0.rgb;
    66.  
    67. fixed4 c;
    68. c.rgb = float3(0.5) * saturate ( NdotL ) * _LightColor0.rgb * atten;
    69. c.a = 1.0;
    70. return c;
    71. }
    72.  
    73. ENDCG
    74. }
    75. FallBack "VertexLit"
    76. }

    Using the base normal map and the height map I had used to generate a detail normal from.

    heightmapShader.png
  • Farfarer
    Offline / Send Message
    Farfarer polycounter lvl 17
    Derivative method works, but it's fucking ugly 'cause it's in screen space - really noisy.

    ddx_fine might give better results in DX11, but it looks like crap in DX9.

    heightmapShaderDeriv.png
Sign In or Register to comment.