Search Unity

[SOLVED] Recalculate Normals (Displacement)

Discussion in 'Shaders' started by flogelz, Oct 1, 2019.

  1. flogelz

    flogelz

    Joined:
    Aug 10, 2018
    Posts:
    142
    I'm using a greyscale image to displace a plane and want to recalculate the correct normals inside of my shader. There's a technique, which seem to work quite good, like for example in this video here, shifting additional points around:
    or http://diary.conewars.com/vertex-displacement-shader/

    For some reason, of all the examples i tried to implement, not even one worked- Can maybe someone see, where my error lies? In most examples, they did some crazy wooble or stuff like that, but I literally just want to displace on the y-axis- What I'm missing to make it work?

    Displacement_Normals_1.png
    Here's my shader:
    Code (CSharp):
    1. Shader "BellsArietta/Terrain" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.         _HeightMap ("Height Map", 2D) = "gray" {}
    6.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    7.         _Metallic ("Metallic", Range(0,1)) = 0.0
    8.         [Header(Tessellation)]
    9.         _TessDetail ("Tessellation", Range(1,15)) = 4
    10.         _TessMin ("Min", float) = 5
    11.         _TessMax ("Max", float) = 15
    12.  
    13.     }
    14.     SubShader {
    15.         Tags { "RenderType"="Opaque" }
    16.         LOD 300
    17.  
    18.         CGPROGRAM
    19.         #pragma surface surf Standard fullforwardshadows addshadow vertex:vert tessellate:tessDistance
    20.         #pragma target 4.6
    21.         #include "Tessellation.cginc"
    22.  
    23.         sampler2D _MainTex, _HeightMap;
    24.         fixed4 _Color;
    25.         half _Glossiness, _Metallic;
    26.         fixed _TessDetail, _TessMin, _TessMax;
    27.      
    28.  
    29.         float4 tessDistance (appdata_full v0, appdata_full v1, appdata_full v2) {
    30.             return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, _TessMin, _TessMax, _TessDetail);
    31.         }
    32.  
    33.  
    34.         void vert (inout appdata_full v)
    35.         {
    36.             float d = tex2Dlod(_HeightMap, float4(v.texcoord.xy,0,0)).r;
    37.          
    38.             float3 v0 = v.vertex.xyz;
    39.             float3 bitangent = cross(v.normal, v.tangent.xyz);
    40.             float3 v1 = v0 + (v.tangent.xyz * 0.01);
    41.             float3 v2 = v0 + (bitangent * 0.01);
    42.  
    43.             float ns0 = v0.y + d;
    44.             float ns1 = v1.y + d;
    45.             float ns2 = v2.y + d;
    46.  
    47.             v0.xyz += ns0 * v.normal;
    48.             v1.xyz += ns1 * v.normal;
    49.             v2.xyz += ns2 * v.normal;
    50.  
    51.             float3 modifiedNormal = cross(v2-v0, v1-v0);
    52.  
    53.             v.normal = normalize(-modifiedNormal);
    54.             v.vertex.xyz = v0;
    55.         }
    56.  
    57.  
    58.  
    59.         struct Input {
    60.             float2 uv_MainTex;
    61.         };
    62.      
    63.         void surf (Input IN, inout SurfaceOutputStandard o) {
    64.  
    65.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    66.             o.Albedo = c.rgb;
    67.             o.Metallic = _Metallic;
    68.             o.Smoothness = _Glossiness;
    69.             o.Alpha = c.a;
    70.         }
    71.         ENDCG
    72.     }
    73.     FallBack "Diffuse"
    74. }
    75.  
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    To extra a normal from a height map you need to calculate the slope. To calculate the slope you need to sample the height at multiple offset positions and calculate the difference. The height difference relative to the distance between samples determines the slope. Two slopes gives you two vectors that the cross product of gives you the new normal.

    You need to know the depth at each of the offset positions, and you're just using the depth at the current position. The idea would be to sample the heightmap texture multiple times with fixed offsets and remap that to the surface normal. You'd have to manually scale things to match the height offset and scale of the UVs relative to the mesh's size to get the correct normal, but that should be easy enough with a simple plane.
     
    flogelz likes this.
  3. flogelz

    flogelz

    Joined:
    Aug 10, 2018
    Posts:
    142
    Thanks for the advice! Also found this code snippet in another thread https://forum.unity.com/threads/calculate-vertex-normals-in-shader-from-heightmap.169871/, which does exactly that. I modified it a bit for it to work properly though.

    Code (CSharp):
    1.         float3 normalsFromHeight(sampler2D heigthTex, float4 uv, float texelSize)
    2.         {
    3.             float4 h;
    4.             h[0] = tex2Dlod(heigthTex, uv + float4(texelSize * float2( 0,-1 / _texSize),0,0)).r * _Height;
    5.             h[1] = tex2Dlod(heigthTex, uv + float4(texelSize * float2(-1 / _texSize, 0),0,0)).r * _Height;
    6.             h[2] = tex2Dlod(heigthTex, uv + float4(texelSize * float2( 1 / _texSize, 0),0,0)).r * _Height;
    7.             h[3] = tex2Dlod(heigthTex, uv + float4(texelSize * float2( 0, 1 / _texSize),0,0)).r * _Height;
    8.             float3 n;
    9.             n.z = h[3] - h[0];
    10.             n.x = h[2] - h[1];
    11.             n.y = 2;
    12.             return normalize(n);
    13.         }