Search Unity

Road Marking Shader

Discussion in 'Shaders' started by rcFMS, Mar 20, 2019.

  1. rcFMS

    rcFMS

    Joined:
    Apr 17, 2018
    Posts:
    9
    Hello,

    for my current project I'm trying to write a shader to display some road markings.

    The blue lines are the outlines of a road (vertices defined by List<Vector3>).
    From these I've already created a mesh (green lines).
    Then I draw the red Quad (made from 2 triangles 0,2,3, 3,1,0) for each road segment via Graphics.DrawMesh.
    Next I want to draw the road markings (orange area) with an absolute (not relative) distance to the right border (1 to 3) and with an absolute width. (Since different segments can have different widths I'll later pass these values to the shader via a MaterialPropertyBlock.)

    I've implemented a function to project the position of a fragment onto the vector between vertices 1 and 3.
    This projected position is then used to get the distance to the original position and if it's between a min and a max distance the texture should be drawn.

    Code (CSharp):
    1. Shader "Custom/Quad"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex("Texture", 2D) = "white" {}
    6.         _Vertex0("Vertex0", Vector) = (0, 0, 0, 0)
    7.         _Vertex1("Vertex1", Vector) = (1, 0, 0, 0)
    8.         _Vertex2("Vertex2", Vector) = (0, 0, 1, 0)
    9.         _Vertex3("Vertex3", Vector) = (1, 0, 1, 0)
    10.         _UV0("UV0", Vector) = (0, 0, 0, 0)
    11.         _UV1("UV1", Vector) = (1, 0, 0, 0)
    12.         _UV2("UV2", Vector) = (0, 1, 0, 0)
    13.         _UV3("UV3", Vector) = (0, 1, 0, 0)
    14.  
    15.         _Offset("Offset", Vector) = (1, 1, 1, 1)
    16.     }
    17.     SubShader
    18.     {
    19.         Tags
    20.         {
    21.             "Queue" = "Transparent"
    22.             "RenderType" = "Transparent"
    23.         }
    24.  
    25.         Blend SrcAlpha OneMinusSrcAlpha
    26.  
    27.         Pass
    28.         {
    29.             CGPROGRAM
    30.             #pragma vertex vert
    31.             #pragma fragment frag
    32.             #pragma multi_compile_instancing
    33.             #include "UnityCG.cginc"
    34.  
    35.             struct appdata
    36.             {
    37.                 float4 vertex : POSITION;
    38.                 float2 uv : TEXCOORD0;
    39.                 uint id : SV_VertexID;
    40.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    41.             };
    42.  
    43.             struct v2f
    44.             {
    45.                 float4 vertex : SV_POSITION;
    46.                 float4 uv : TEXCOORD0;
    47.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    48.             };
    49.  
    50.             sampler2D _MainTex;
    51.             float4 _MainTex_ST;
    52.  
    53.             UNITY_INSTANCING_BUFFER_START(Props)
    54.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex0)
    55.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex1)
    56.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex2)
    57.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex3)
    58.             UNITY_DEFINE_INSTANCED_PROP(float4, _UV0)
    59.             UNITY_DEFINE_INSTANCED_PROP(float4, _UV1)
    60.             UNITY_DEFINE_INSTANCED_PROP(float4, _UV2)
    61.             UNITY_DEFINE_INSTANCED_PROP(float4, _UV3)
    62.             UNITY_DEFINE_INSTANCED_PROP(float4, _Offset)
    63.             UNITY_INSTANCING_BUFFER_END(Props)
    64.  
    65.             float inverseLerp(float a, float b, float value)
    66.             {
    67.                 return (value - a) / (b - a);
    68.             }
    69.  
    70.             float projectClamped(float position, float origin, float destination)
    71.             {
    72.                 float direction = destination - origin;
    73.                 float magnitude = length(direction);
    74.                 direction = normalize(direction);
    75.  
    76.                 float lhs = position - origin;
    77.                 float dotP = dot(lhs, direction);
    78.                 dotP = clamp(dotP, 0, magnitude);
    79.                 return origin + direction * dotP;
    80.             }
    81.  
    82.             float project(float position, float origin, float destination)
    83.             {
    84.                 float direction = destination - origin;
    85.                 float lhs = position - origin;
    86.  
    87.                 float dotP = dot(lhs, direction);
    88.                 return origin + direction * dotP;
    89.             }
    90.  
    91.             v2f vert(appdata v)
    92.             {
    93.                 UNITY_SETUP_INSTANCE_ID(v);
    94.                 v2f o;
    95.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    96.  
    97.                 // Use a single quad mesh and set the vertex positions here?
    98.                 // Problem: Mesh does not get drawn when original vertex positions are not visible by camera!
    99.  
    100.                 //fixed2 coordinate = fixed2(v.id % 2, v.id / 2 % 2);
    101.                 //float4 newVertex = lerp(
    102.                 //    lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1), coordinate.x),
    103.                 //    lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3), coordinate.x),
    104.                 //    coordinate.y);
    105.                 //
    106.                 //o.vertex = UnityObjectToClipPos(newVertex);
    107.                 o.vertex = UnityObjectToClipPos(v.vertex);
    108.                 o.uv.xy = v.uv.xy;
    109.                 o.uv.zw = v.vertex.xy;
    110.                 return o;
    111.             }
    112.  
    113.             fixed4 frag(v2f i) : SV_Target
    114.             {
    115.                 UNITY_SETUP_INSTANCE_ID(i);
    116.  
    117.                 //return tex2D(_MainTex, i.uv.xy);
    118.  
    119.                 float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0);
    120.                 float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1);
    121.                 float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2);
    122.                 float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3);
    123.  
    124.                 float2 uv0 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV0);
    125.                 float2 uv1 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV1);
    126.                 float2 uv2 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV2);
    127.                 float2 uv3 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV3);
    128.  
    129.                 float4 _offset = UNITY_ACCESS_INSTANCED_PROP(Props, _Offset);
    130.  
    131.                 float4 _projectedPoint = project(i.uv.xy, v1, v3);
    132.                 float _distance = distance(i.uv.zw, _projectedPoint.xz);
    133.                 if(_distance > _offset.x && _distance < _offset.y)
    134.                 {
    135.                     return tex2D(_MainTex, i.uv.xy);
    136.                     return fixed4(0, 1, 0, 1);
    137.                 }
    138.                 return fixed4(1, 0, 0, 1);
    139.             }
    140.             ENDCG
    141.         }
    142.     }
    143.     FallBack "VertexLit"
    144. }
    However, I'm getting some weird results instead:

    Settings:


    Also note the texture stretching along the diagonal of the quad. When applied to a larger section of a road with a curve this causes these weird waves:


    Any help with this would be hugely appreciated!

    Cheers
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
  3. rcFMS

    rcFMS

    Joined:
    Apr 17, 2018
    Posts:
    9
    Thanks for the link. Unfortunately the shader posted there (even with the fixes) does not seem to work for me.

    I've done some more searching and found this blog post which seems to solve this by using bilinear interpolation (which I tried initially but failed because it turns out that trying to inverseLerp a fragment position between the vertex positions (to get the percent values to lerp with afterwards) divides a vector by another vector and thus returns a result with imaginary numbers...):
    http://www.reedbeta.com/blog/quadrilateral-interpolation-part-2/

    I've tried implementing the pseudo code posted there (which seems to use a work-around to the imaginary number problem) but the result is the same as the default shader:
    Code (CSharp):
    1. Shader "Custom/Quad"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex("Texture", 2D) = "white" {}
    6.         _Vertex0("Vertex0", Vector) = (0, 0, 0, 0)
    7.         _Vertex1("Vertex1", Vector) = (1, 0, 0, 0)
    8.         _Vertex2("Vertex2", Vector) = (0, 0, 1, 0)
    9.         _Vertex3("Vertex3", Vector) = (1, 0, 1, 0)
    10.         _UV0("UV0", Vector) = (0, 0, 0, 0)
    11.         _UV1("UV1", Vector) = (1, 0, 0, 0)
    12.         _UV2("UV2", Vector) = (0, 1, 0, 0)
    13.         _UV3("UV3", Vector) = (0, 1, 0, 0)
    14.     }
    15.     SubShader
    16.     {
    17.         Tags
    18.         {
    19.             "Queue" = "Transparent"
    20.             "RenderType" = "Transparent"
    21.         }
    22.  
    23.         Blend SrcAlpha OneMinusSrcAlpha
    24.  
    25.         Pass
    26.         {
    27.             CGPROGRAM
    28.             #pragma vertex vert
    29.             #pragma fragment frag
    30.             #pragma multi_compile_instancing
    31.             #include "UnityCG.cginc"
    32.  
    33.             struct appdata
    34.             {
    35.                 float4 vertex : POSITION;
    36.                 float2 uv : TEXCOORD0;
    37.                 uint id : SV_VertexID;
    38.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    39.             };
    40.  
    41.             struct v2f
    42.             {
    43.                 float4 vertex : SV_POSITION;
    44.                 float4 uv : TEXCOORD0;
    45.                 float4 q : TEXCOORD1;
    46.                 float4 b1 : TEXCOORD2;
    47.                 float4 b2 : TEXCOORD3;
    48.                 float4 b3 : TEXCOORD4;
    49.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    50.             };
    51.  
    52.             sampler2D _MainTex;
    53.             float4 _MainTex_ST;
    54.  
    55.             UNITY_INSTANCING_BUFFER_START(Props)
    56.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex0)
    57.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex1)
    58.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex2)
    59.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex3)
    60.             UNITY_DEFINE_INSTANCED_PROP(float4, _UV0)
    61.             UNITY_DEFINE_INSTANCED_PROP(float4, _UV1)
    62.             UNITY_DEFINE_INSTANCED_PROP(float4, _UV2)
    63.             UNITY_DEFINE_INSTANCED_PROP(float4, _UV3)
    64.             UNITY_DEFINE_INSTANCED_PROP(float4, _Widths)
    65.             UNITY_INSTANCING_BUFFER_END(Props)
    66.  
    67.             float2 cross2d(float4 a, float4 b)
    68.             {
    69.                 return a.x * b.z - a.z * b.x;
    70.             }
    71.  
    72.             v2f vert(appdata v)
    73.             {
    74.                 UNITY_SETUP_INSTANCE_ID(v);
    75.                 v2f o;
    76.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    77.  
    78.                 fixed2 coordinate = fixed2(v.id % 2, v.id / 2 % 2);
    79.                 float4 newVertex = lerp(
    80.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1), coordinate.x),
    81.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3), coordinate.x),
    82.                     coordinate.y);
    83.                
    84.                 //o.vertex = UnityObjectToClipPos(newVertex);
    85.                 o.vertex = UnityObjectToClipPos(v.vertex);
    86.                 o.uv.xy = v.uv.xy;
    87.                 //o.uv.zw = TRANSFORM_TEX(v.uv, _MainTex);
    88.                 o.uv.zw = v.vertex.xy;
    89.  
    90.  
    91.                 float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0);
    92.                 float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1);
    93.                 float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2);
    94.                 float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3);
    95.  
    96.  
    97.                 o.q = newVertex;
    98.                 o.b1 = v1 - v0;
    99.                 o.b2 = v2 - v0;
    100.                 o.b3 = v0 - v1 - v2 + v3;
    101.  
    102.                 return o;
    103.             }
    104.  
    105.             fixed4 frag(v2f i) : SV_Target
    106.             {
    107.                 UNITY_SETUP_INSTANCE_ID(i);
    108.  
    109.                 //return tex2D(_MainTex, i.uv.xy);
    110.  
    111.                 float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0);
    112.                 float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1);
    113.                 float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2);
    114.                 float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3);
    115.  
    116.                 float2 uv0 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV0);
    117.                 float2 uv1 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV1);
    118.                 float2 uv2 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV2);
    119.                 float2 uv3 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV3);
    120.  
    121.                 float4 p = i.uv;
    122.  
    123.  
    124.  
    125.                 float A = cross2d(i.b2, i.b3);
    126.                 float B = cross2d(i.b3, i.q) - cross2d(i.b1, i.b2);
    127.                 float C = cross2d(i.b1, i.q);
    128.  
    129.                 float2 uv;
    130.                 if (abs(A) < 0.001)
    131.                 {
    132.                     // Linear form
    133.                     uv.y = -C/B;
    134.                 }
    135.                 else
    136.                 {
    137.                     // Quadratic form. Take positive root for CCW winding with V-up
    138.                     float discrim = B*B - 4*A*C;
    139.                     uv.y = 0.5 * (-B + sqrt(discrim)) / A;
    140.                 }
    141.  
    142.                 // Solve for u, using largest-magnitude component
    143.                 float2 denom = i.b1 + uv.y * i.b3;
    144.                 if (abs(denom.x) > abs(denom.y))
    145.                     uv.x = (i.q.x - i.b2.x * uv.y) / denom.x;
    146.                 else
    147.                     uv.x = (i.q.y - i.b2.y * uv.y) / denom.y;
    148.  
    149.                 return tex2D(_MainTex, uv);
    150.             }
    151.             ENDCG
    152.         }
    153.     }
    154.     FallBack "VertexLit"
    155. }


    Is there really no easy way of doing this?
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    The GPU is always interpolating the UVs over single triangles. You want to do quad interpolation, which means having to reconstruct and interpolate the quad within the triangle in some way. That's not easy. I still think my approach is the simplest solution even if it requires custom mesh data. Any of the "shader only" based approaches (really, shader and custom per quad material properties), like the one in Nathan Reed's blog only works on a single quad at a time and doesn't generalize well to the usual case of "I have a long strip of geometry I want to not look like sh*t when it curves".
     
    Last edited: Mar 21, 2019
  5. rcFMS

    rcFMS

    Joined:
    Apr 17, 2018
    Posts:
    9
    I looked at my original code and noticed that the "project" function had an error (from the forum where I looked it up) as it was missing the division by the squared length of the direction.

    After fixing this I am now able to compare the distance of a fragment to the right border correctly and across both triangles:


    The uv-test texture being projected top-down works fine (at least for the moment); since this is a shader for road markings I'll later replace the texture by a tileable white texture with some noise.

    The only problem left now seems to be properly receiving shadows.
    (Also, since these road markings will lie just a tiny distance above road meshes they do not need to cast shadows themselves, especially if they are wrong anyway since they get calculated before the vertex displacement.)
    [EDIT: I managed to disable shadow casting by setting the "castShadows" parameter in the Graphics.DrawMesh method to false. However, if it's possible and better for performance to disable this in the shader as well, I'd like to do that.]

    I tried inserting the relevant lines of code from the official manual (under "Receiving Shadows", towards the bottom):
    https://docs.unity3d.com/Manual/SL-VertexFragmentShaderExamples.html

    But it does not work, as can be seen in the above.
    In fact, replacing my entire shader by the shader posted in the manual did not result in a Material which could receive shadows. So is the example code bugged? Or is it my Unity version (2018.3.7)?

    I also checked in some other forums which had examples on how to let a shader receive shadows and they were all more or less the same.

    Here's my current code:
    Code (CSharp):
    1. Shader "Custom/Quad"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex("Texture", 2D) = "white" {}
    6.         _Vertex0("Vertex0", Vector) = (0, 0, 0, 0)
    7.         _Vertex1("Vertex1", Vector) = (1, 0, 0, 0)
    8.         _Vertex2("Vertex2", Vector) = (0, 0, 1, 0)
    9.         _Vertex3("Vertex3", Vector) = (1, 0, 1, 0)
    10.         _Offset("Offset", Vector) = (1, 0, 1, 0)
    11.     }
    12.     SubShader
    13.     {
    14.         Tags
    15.         {
    16.             "Queue" = "Transparent"
    17.             "RenderType" = "Transparent"
    18.             "LightMode" = "ForwardBase"
    19.         }
    20.  
    21.         Blend SrcAlpha OneMinusSrcAlpha
    22.  
    23.         Pass
    24.         {
    25.             CGPROGRAM
    26.             #pragma vertex vert
    27.             #pragma fragment frag
    28.             #pragma multi_compile_instancing
    29.             #include "UnityCG.cginc"
    30.  
    31.             #pragma multi_compile_fwdbase
    32.             #include "AutoLight.cginc"
    33.  
    34.             struct appdata
    35.             {
    36.                 float4 vertex : POSITION;
    37.                 float2 uv : TEXCOORD0;
    38.                 uint id : SV_VertexID;
    39.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    40.             };
    41.  
    42.             struct v2f
    43.             {
    44.                 float4 vertex : SV_POSITION;
    45.                 float4 worldPos : TEXCOORD0;
    46.                 SHADOW_COORDS(1)
    47.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    48.             };
    49.  
    50.             sampler2D _MainTex;
    51.             float4 _MainTex_ST;
    52.  
    53.             UNITY_INSTANCING_BUFFER_START(Props)
    54.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex0)
    55.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex1)
    56.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex2)
    57.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex3)
    58.             UNITY_DEFINE_INSTANCED_PROP(float4, _Offset)
    59.             UNITY_INSTANCING_BUFFER_END(Props)
    60.  
    61.             float4 project(float4 _position, float4 _origin, float4 _destination)
    62.             {
    63.                 float4 _direction = _destination - _origin;
    64.                 float _distance = dot(_position - _origin, _direction) / (length(_direction) * length(_direction));
    65.                 return _origin + _direction * _distance;
    66.             }
    67.  
    68.             v2f vert(appdata v)
    69.             {
    70.                 UNITY_SETUP_INSTANCE_ID(v);
    71.                 v2f o;
    72.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    73.  
    74.                 float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0);
    75.                 float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1);
    76.                 float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2);
    77.                 float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3);
    78.  
    79.                 fixed2 coordinate = fixed2(v.id % 2, v.id / 2 % 2);
    80.                 float4 newVertex = lerp(
    81.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1), coordinate.x),
    82.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3), coordinate.x),
    83.                     coordinate.y);
    84.                 o.vertex = UnityObjectToClipPos(newVertex);
    85.  
    86.                 //o.vertex = UnityObjectToClipPos(v.vertex);
    87.  
    88.                 o.worldPos = mul(unity_ObjectToWorld, newVertex);
    89.  
    90.                 TRANSFER_SHADOW(o)
    91.  
    92.                 return o;
    93.             }
    94.  
    95.             fixed4 frag(v2f i) : SV_Target
    96.             {
    97.                 UNITY_SETUP_INSTANCE_ID(i);
    98.  
    99.                 //return tex2D(_MainTex, i.worldPos.xz);
    100.              
    101.                 float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0);
    102.                 float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1);
    103.                 float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2);
    104.                 float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3);
    105.  
    106.                 float2 _offset = UNITY_ACCESS_INSTANCED_PROP(Props, _Offset);
    107.              
    108.                 float4 _projectedPosition = project(i.worldPos, v1, v3);
    109.                 float _distance = distance(i.worldPos, _projectedPosition);
    110.  
    111.                 fixed shadow = SHADOW_ATTENUATION(i);
    112.                 fixed4 _color = tex2D(_MainTex, i.worldPos.xz) * shadow;
    113.                 _color.a = _offset.x < _distance && _distance < _offset.y;
    114.  
    115.                 return _color;
    116.             }
    117.             ENDCG
    118.         }
    119.         UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
    120.     }
    121. }
     
    Last edited: Mar 26, 2019
    bgolus likes this.
  6. rcFMS

    rcFMS

    Joined:
    Apr 17, 2018
    Posts:
    9
    Setting the "Render Queue" value on the Material to "AlphaTest" shows that the shadow gets received by the original mesh from before the vertex displacement:

     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    Transparent queue materials don't receive shadows for the built in rendering paths. There are decade old threads on the forum with "surely this works by now?!" posts followed by "nope". The new HDRP finally adds this, but that's not really useful to you if you're using the built in rendering path.

    Luckily you don't need to be using the transparent queue. As you found, swapping to AlphaTest works fine, and you can keep the alpha blending. I would however suggest using a queue like "Geometry+1" instead of "AlphaTest" as it'll prevent possible sorting issues later. Basically you want to make sure you're drawing after the road surface, but before anything else. You probably also want to use ZWrite Off.

    A quirk of Unity's main directional shadows is the shadow caster pass is what receives shadows. Unity generates a full screen camera depth texture using the shadow caster pass of each opaque object and then casts shadows onto that texture creating a new full screen texture, the screen space shadow texture. The shadow related macros in the ForwardBase shader pass you've written is just passing along the screen space position, and sample that screen space shadow texture. Since you're just reusing an existing shadow casting pass from another shader, it knows nothing of the modifications you're doing to the vertex positions.

    But you don't want this object shadow casting to begin with, and you really don't even care if it receives shadows, just that it shows the shadows being received by the ground below it, so just remove your UsePass line. With that gone it'll still read the screen space shadow texture, but it'll just be for what's below the object.
     
    Last edited: Mar 26, 2019
  8. rcFMS

    rcFMS

    Joined:
    Apr 17, 2018
    Posts:
    9
    Okay, I got that part to work now.
    Although in between I got this weird error a few times when setting the Render Queue value to anything below 2501 (including 2450 for "AlphaTest"):
    I'm not sure what caused it since I didn't really change anything in that regard. After restarting Unity a few times it went away though...

    Commenting out the UsePass line removes the wrong shadow from where the original quad used to be.
    But it does not seem to add shadows on the non-transparent part after the vertex displacement.
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    Your v2f has the SV_Position named vertex, but any macros related to lighting and shadows assume it's named pos.
     
  10. rcFMS

    rcFMS

    Joined:
    Apr 17, 2018
    Posts:
    9
    Thank you so much!

    I finally got this to work (where I also added a Range slider for shadow intensity):


    Is this documented anywhere by chance? Especially since when creating a new Unlit Shader or Image Effect Shader in Unity the default name for this actually is "vertex"...?

    I also added a factor for the light direction, the current code looks like this:
    Code (CSharp):
    1. Shader "Custom/Quad"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex("Texture", 2D) = "white" {}
    6.         _Vertex0("Vertex0", Vector) = (0, 0, 0, 0)
    7.         _Vertex1("Vertex1", Vector) = (1, 0, 0, 0)
    8.         _Vertex2("Vertex2", Vector) = (0, 0, 1, 0)
    9.         _Vertex3("Vertex3", Vector) = (1, 0, 1, 0)
    10.         _Offset("Offset", Vector) = (1, 0, 1, 0)
    11.         _ShadowIntensity("Shadow Intensity", Range(0, 1)) = 0.5
    12.     }
    13.     SubShader
    14.     {
    15.         Tags
    16.         {
    17.             "Queue" = "Geometry+1"
    18.             "RenderType" = "Transparent"
    19.             "LightMode" = "ForwardBase"
    20.         }
    21.  
    22.         Zwrite Off
    23.         Blend SrcAlpha OneMinusSrcAlpha
    24.  
    25.         Pass
    26.         {
    27.             CGPROGRAM
    28.             #pragma vertex vert
    29.             #pragma fragment frag
    30.             #pragma multi_compile_instancing
    31.             #include "UnityCG.cginc"
    32.  
    33.             #pragma multi_compile_fwdbase
    34.             #include "AutoLight.cginc"
    35.             #include "UnityLightingCommon.cginc"
    36.  
    37.             struct appdata
    38.             {
    39.                 float4 vertex : POSITION;
    40.                 float3 normal : NORMAL;
    41.                 float2 uv : TEXCOORD0;
    42.                 uint id : SV_VertexID;
    43.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    44.             };
    45.  
    46.             struct v2f
    47.             {
    48.                 float4 pos : SV_POSITION;
    49.                 fixed4 diff : COLOR0;
    50.                 float4 worldPos : TEXCOORD0;
    51.                 SHADOW_COORDS(1)
    52.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    53.             };
    54.  
    55.             sampler2D _MainTex;
    56.             float4 _MainTex_ST;
    57.             float _ShadowIntensity;
    58.  
    59.             UNITY_INSTANCING_BUFFER_START(Props)
    60.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex0)
    61.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex1)
    62.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex2)
    63.             UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex3)
    64.             UNITY_DEFINE_INSTANCED_PROP(float4, _Offset)
    65.             UNITY_INSTANCING_BUFFER_END(Props)
    66.  
    67.             float4 project(float4 _position, float4 _origin, float4 _destination)
    68.             {
    69.                 float4 _direction = _destination - _origin;
    70.                 float _distance = dot(_position - _origin, _direction) / (length(_direction) * length(_direction));
    71.                 return _origin + _direction * _distance;
    72.             }
    73.  
    74.             v2f vert(appdata v)
    75.             {
    76.                 UNITY_SETUP_INSTANCE_ID(v);
    77.                 v2f o;
    78.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    79.  
    80.                 float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0);
    81.                 float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1);
    82.                 float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2);
    83.                 float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3);
    84.  
    85.                 fixed2 coordinate = fixed2(v.id % 2, v.id / 2 % 2);
    86.                 float4 newVertex = lerp(
    87.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1), coordinate.x),
    88.                     lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3), coordinate.x),
    89.                     coordinate.y);
    90.                 o.pos = UnityObjectToClipPos(newVertex);
    91.  
    92.                 //o.pos = UnityObjectToClipPos(v.vertex);
    93.  
    94.                 o.worldPos = mul(unity_ObjectToWorld, newVertex);
    95.  
    96.                 half3 worldNormal = UnityObjectToWorldNormal(v.normal);
    97.                 half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
    98.                 o.diff = nl * _LightColor0;
    99.                 o.diff.rgb += ShadeSH9(half4(worldNormal, 1));
    100.  
    101.                 TRANSFER_SHADOW(o)
    102.  
    103.                 return o;
    104.             }
    105.  
    106.             fixed4 frag(v2f i) : SV_Target
    107.             {
    108.                 UNITY_SETUP_INSTANCE_ID(i);
    109.  
    110.                 //return tex2D(_MainTex, i.worldPos.xz);
    111.              
    112.                 float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0);
    113.                 float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1);
    114.                 float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2);
    115.                 float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3);
    116.  
    117.                 float2 _offset = UNITY_ACCESS_INSTANCED_PROP(Props, _Offset);
    118.              
    119.                 float4 _projectedPosition = project(i.worldPos, v1, v3);
    120.                 float _distance = distance(i.worldPos, _projectedPosition);
    121.  
    122.                 fixed _brightness = 1 - ((1 - SHADOW_ATTENUATION(i)) * _ShadowIntensity);
    123.                 fixed4 _color = tex2D(_MainTex, i.worldPos.xz) * _brightness * i.diff;
    124.                 _color.a = _offset.x < _distance && _distance < _offset.y;
    125.  
    126.                 return _color;
    127.             }
    128.             ENDCG
    129.         }
    130.     }
    131. }
    I think I'll later need to recalculate the normals of the vertices after the displacement in order for this to be correct when the road is not perfectly flat. But this should be doable for me and I'll first implement some more on the C# side to use Graphics.DrawMesh for every quad segment of each road to draw this for an entire road (since I'd like to get a look at the big picture now ;)).
     
    bgolus likes this.
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    Some macros use .vertex and some use .pos and some require you to tell the macro which one to use. Generally unlit & sprite macros are vertex, lit shaders are pos, with more recent or shared macros going for the last approach. And no, it is not documented. It's just a 'fun' anachronism of Unity's code base.