Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Shader shadow offset too far from edge of objects

Discussion in 'Shaders' started by NetherGear, Feb 10, 2019.

  1. NetherGear

    NetherGear

    Joined:
    Sep 21, 2018
    Posts:
    15
    I'm looking for some help with a simple texture and shadow only shader. I have a scene with modular assets and the default Unity smooth shading makes the seams visible so I downloaded a texture and shadow only shader.

    The shader works but the shadows are rendering far from the edges of the objects. Here is a sample with one point light hitting a group of boxes. I marked he edges with yellow to show how far off he shadows are. It's the same way for every object in my scene. The shadows don't line up to the edges.

    shadowedge.png

    Here is the shader code. I copied this from offline. I know only the bare basics of how shader code works and not enough to know what the issue is.

    Code (CSharp):
    1. Shader "Custom/ShadowsTexturesOnly" {
    2. Properties{
    3. _MainTex("Texture", 2D) = "white" {}
    4. }
    5. SubShader{
    6. Tags{ "RenderType" = "Opaque" }
    7. CGPROGRAM
    8. #pragma surface surf SimpleLambert fullforwardshadows
    9.  
    10. half4 LightingSimpleLambert(SurfaceOutput s, half3 lightDir, half atten) {
    11. half NdotL = dot(s.Normal, lightDir);
    12. half4 c;
    13. c.rgb = s.Albedo * _LightColor0.rgb * (atten);
    14. c.a = s.Alpha;
    15. return c;
    16. }
    17.  
    18. struct Input {
    19. float2 uv_MainTex;
    20. };
    21.  
    22. sampler2D _MainTex;
    23.  
    24. void surf(Input IN, inout SurfaceOutput o) {
    25. o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
    26. }
    27. ENDCG
    28. }
    29. Fallback "Diffuse"
    30. }
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    This is the nature of real time shadows. For almost all modern 3D games with real time shadows, we use a technique called shadow mapping. Apart from obvious aliasing from this being a texture, as can be seen by the stair stepping on the shadow on the wall, it has another common problem called shadow acne. This is caused by the on screen pixels and the shadow map pixels not perfectly lining up (and they can't) causing the positions stored in the shadow maps to not quite lining up with the positions being rendered. This causes darkening, stripes, or moire interference patterns to show up on the surface of objects. The simplest solution is biasing, which is pushing the shadows slightly back and away from surfaces either based on the light direction or surface normal.

    If you look at your light, it'll have two settings, bias and normal bias, which controls those two things mentioned above (note: not all light types use both). You can try setting those values to 0.0 and you'll see the shadow get much closer to the edge, but you'll also see the front of the box get those shadow acne problems mentioned above, and the shadow on the box's edge will be jagged just like the shadow on the wall. Thus why the bias is there to begin with.

    Normally a surface's shading hides that shadow gap, making any surface not facing the light not be affected by that light's color hides the fact it's not actually self shadowing. One of most basic forms of that is Lambert shading, which is just a dot product between the surface normal and light direction. That's what's in your shader above, though not used.

    The usual solution to all of this is some form of cell shading.

    c.rgb = s.Albedo * _LightColor0.rgb * atten * step(0.0, NdotL);
     
    Beennn, Westland and NetherGear like this.
  3. NetherGear

    NetherGear

    Joined:
    Sep 21, 2018
    Posts:
    15
    Thanks, I played around with the settings some but it still doesn't get the light close to the edge.

    I'm not using smooth shading since my scene is composed of modular pieces. The smooth shading stops at the seams of each piece and makes the seams visible.

    Shadows only



    Smoothing plus shadows



    The last one is the default standard shader. As you can see it's looks horrible.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Ah. You just have bad normals on your objects. They're using smooth normals when they should be using hard edge normals. This is making your boxy walls try to shade as if they're very low poly spheres or cylinders.

    You can either fix this in the content by re-exporting the meshes with proper normals, or if you're not planning on shipping on mobile (explicitly, not on OpenGL ES 2.0 devices), then you can try generating the flat normals in the shader.

    See this example for how to do this with a surface shader:
    https://forum.unity.com/threads/flat-lighting-without-separate-smoothing-groups.280183/#post-3696988

    However you might be better off going with a vertex fragment shader as a surface shader still requires your meshes have normals, where a vertex fragment shader would not. There's an example of that in the thread above that linked post.
     
    NetherGear likes this.
  5. NetherGear

    NetherGear

    Joined:
    Sep 21, 2018
    Posts:
    15
    Thanks a million mate. That flat shader was exactly what I was looking for. It gives that hard edge low poly retro look I'm going for.

    I also had to go back and redo the normals in blender to hard edge which was extremely simply. You can select all edges with Control + A then Control + E -> Mark Sharp and the normals are set to hard edge.

    I still have some work to do with the lighting but overall it looks good.



    The extra shading you see along the grey of the walls is applied via texture.

    Here's a repost of your shader in case anyone else needs it.

    Code (CSharp):
    1. Shader "Custom/FlatSurfaceShader" {
    2.     Properties {
    3.         _Color ("Color", Color) = (1,1,1,1)
    4.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    5.     }
    6.     SubShader {
    7.         Tags { "RenderType"="Opaque" }
    8.  
    9.         CGPROGRAM
    10.         #pragma surface surf Standard fullforwardshadows vertex:vert
    11.         #pragma target 3.0
    12.  
    13.         sampler2D _MainTex;
    14.  
    15.         struct Input {
    16.             float2 uv_MainTex;
    17.             float3 cameraRelativeWorldPos;
    18.             float3 worldNormal;
    19.             INTERNAL_DATA
    20.         };
    21.  
    22.         half _Glossiness;
    23.         half _Metallic;
    24.         fixed4 _Color;
    25.  
    26.         // pass camera relative world position from vertex to fragment
    27.         void vert(inout appdata_full v, out Input o)
    28.         {
    29.             UNITY_INITIALIZE_OUTPUT(Input,o);
    30.             o.cameraRelativeWorldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)) - _WorldSpaceCameraPos.xyz;
    31.         }
    32.  
    33.         void surf (Input IN, inout SurfaceOutputStandard o) {
    34.  
    35.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
    36.             o.Albedo = c.rgb * _Color.rgb;
    37.  
    38.             // flat world normal from position derivatives
    39.             half3 flatWorldNormal = normalize(cross(ddy(IN.cameraRelativeWorldPos.xyz), ddx(IN.cameraRelativeWorldPos.xyz)));
    40.  
    41.             // construct world to tangent matrix
    42.             half3 worldT =  WorldNormalVector(IN, half3(1,0,0));
    43.             half3 worldB =  WorldNormalVector(IN, half3(0,1,0));
    44.             half3 worldN =  WorldNormalVector(IN, half3(0,0,1));
    45.             half3x3 tbn = half3x3(worldT, worldB, worldN);
    46.  
    47.             // apply world to tangent transform to flat world normal
    48.             o.Normal = mul(tbn, flatWorldNormal);
    49.         }
    50.         ENDCG
    51.     }
    52.     FallBack "Diffuse"
    53. }
     
    Last edited: Feb 14, 2019