Search Unity

Question Geometry grass shader with uniform light and shadow

Discussion in 'Shaders' started by spiritworld, Jan 12, 2021.

  1. spiritworld

    spiritworld

    Joined:
    Nov 26, 2014
    Posts:
    29
    I picked up this great tutorial https://roystan.net/articles/grass-shader.html and tried to add some customizations. I've been struggling to get lighting and shadows right and still don't fully grasp the math.

    What I want: uniform light intensity compared to ground and NOT pitch black shadows (see terrain shader part in the end). Here's the grass fragment shader so far looks
    Code (CSharp):
    1.            
    2.             sampler2D _MainTex;
    3.             float _Cutoff;
    4.             float4 _TranslucentColor, _BottomColor;
    5.             float _ShadowIntensity;
    6.  
    7.             float4 frag (geometryOutput i) : SV_Target
    8.             {          
    9.                 float4 col = tex2D(_MainTex, i.uv);
    10.                 clip(col.a - _Cutoff);
    11.                 float3 normal = float3(0, 1, 0); //trying to achieve uniform light
    12.                 float shadowIntensity = (1 - _ShadowIntensity); // to soften the shadow
    13.                 float shadowAttenuation = SHADOW_ATTENUATION(i);
    14.                 float NdotL = saturate(dot(normal, _WorldSpaceLightPos0)) + shadowIntensity;
    15.                 float4 ambient = float4(ShadeSH9(float4(normal, 1)), 1);
    16.                 float4 lightIntensity = NdotL * shadowAttenuation + ambient;
    17.              
    18.                 col *= lerp(_BottomColor, _TranslucentColor, i.uv.y) * _LightColor0;
    19.                 col *= lightIntensity;
    20.                 return col;
    21.             }
    With correct _ShadowIntensity and Directional light angle it looks just fine but the results vary ridiculously between different light settings. Examples:

    15dg.png X-axis 15d and _ShadowIntensity 0.2

    30dg.png X-axis 30d and _ShadowIntensity 0.6


    Desired result would be this.
    0.png

    I'm starting to feel like running in circles: some _ShadowIntensity value works in daylight, some in night but never both. Can anyone point out what calculation would keep lighting same as ground?

    If it's any help here's the terrain shader's lighting function, it's basically from Unity docs ramp lighting without ramp texture (yes, i tried to imitate these calculations in grass fragment but still no avail)
    Code (CSharp):
    1.        
    2.         inline half4 LightingRamp (SurfaceOutput s, half3 lightDir, half shadowAttenuation) {
    3.             float NdotL = dot(s.Normal, lightDir);
    4.             float lightIntensity = smoothstep(_LightThreshold - _LightSmoothness * 0.25, _LightThreshold + _LightSmoothness * 0.25, NdotL);
    5.             lightIntensity *= shadowAttenuation;
    6.  
    7.             //calculate shadow color and mix light and shadow based on the light. Then tint it based on the light color, shadow intensity is 0.6
    8.             float3 shadowColor = lerp(s.Albedo, fixed3(0, 0, 0), _ShadowIntensity);
    9.             float4 color;
    10.             color.rgb = lerp(shadowColor, s.Albedo, lightIntensity) * _LightColor0.rgb;
    11.             color.a = s.Alpha;
    12.             return color;
    13.         }
     
    Last edited: Jan 12, 2021
  2. UnityMaru

    UnityMaru

    Community Engagement Manager PSM

    Joined:
    Mar 16, 2016
    Posts:
    1,227
    Are you using the version of the editor that is mentioned in that tutorial? You'll usually run into problems if you don't use a version it's intended for.
     
  3. spiritworld

    spiritworld

    Joined:
    Nov 26, 2014
    Posts:
    29
    I don't think editor has much to do with shader math, but I'm using Unity 2020.2. Forgot to mention: my terrain is separate plane on which the geometry grass plane is placed (= two different materials), just like in Roystan tutorial.

    Anyway, I figured to modify terrain shader lighting too, applying the shadowIntensity same way in both and now I got 90% of what I intended.

    Looks fine day and night, except at some threshold angles it still brings out the "grass edges" from ground.

    Grass light
    Code (CSharp):
    1.            
    2.         float4 frag (geometryOutput i) : SV_Target
    3.             {          
    4.                 float4 col = tex2D(_MainTex, i.uv);
    5.                 clip(col.a - _Cutoff);
    6.                 float3 normal = float3(0, 1, 0);
    7.                 float shadowAttenuation = SHADOW_ATTENUATION(i);
    8.              
    9.                 float NdotL = saturate(dot(normal, _WorldSpaceLightPos0));
    10.                 float4 ambient = float4(ShadeSH9(float4(normal, 1)), 1);
    11.                 float lightIntensity = NdotL * shadowAttenuation + ambient;
    12.                 float shadowIntensity = (1 - _ShadowIntensity);
    13.                 lightIntensity += shadowIntensity; // add
    14.  
    15.                 col.rgb *= lerp(_BottomColor, _TranslucentColor, i.uv.y) * _LightColor0;
    16.                 col.rgb *= lightIntensity;
    17.                 return col;
    18.             }
    Terrain light
    Code (CSharp):
    1.        
    2.     inline half4 LightingRamp (SurfaceOutput s, half3 lightDir, half shadowAttenuation) {
    3.             float NdotL = saturate(dot(s.Normal, lightDir));
    4.             float lightIntensity = smoothstep(_LightThreshold - _LightSmoothness * 0.25, _LightThreshold + _LightSmoothness * 0.25, NdotL) ;
    5.             lightIntensity *= shadowAttenuation;
    6.             lightIntensity += (1 - _ShadowIntensity);
    7.             //calculate shadow color and mix light and shadow based on the light. Then tint it based on the light color
    8.             float4 color;
    9.             color.rgb = s.Albedo * lightIntensity * _LightColor0.rgb;
    10.             color.a = s.Alpha;
    11.             return color;
    12.         }