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): Shader "Custom/Quad" { Properties { _MainTex("Texture", 2D) = "white" {} _Vertex0("Vertex0", Vector) = (0, 0, 0, 0) _Vertex1("Vertex1", Vector) = (1, 0, 0, 0) _Vertex2("Vertex2", Vector) = (0, 0, 1, 0) _Vertex3("Vertex3", Vector) = (1, 0, 1, 0) _UV0("UV0", Vector) = (0, 0, 0, 0) _UV1("UV1", Vector) = (1, 0, 0, 0) _UV2("UV2", Vector) = (0, 1, 0, 0) _UV3("UV3", Vector) = (0, 1, 0, 0) _Offset("Offset", Vector) = (1, 1, 1, 1) } SubShader { Tags { "Queue" = "Transparent" "RenderType" = "Transparent" } Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; uint id : SV_VertexID; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; float4 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; sampler2D _MainTex; float4 _MainTex_ST; UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex0) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex1) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex2) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex3) UNITY_DEFINE_INSTANCED_PROP(float4, _UV0) UNITY_DEFINE_INSTANCED_PROP(float4, _UV1) UNITY_DEFINE_INSTANCED_PROP(float4, _UV2) UNITY_DEFINE_INSTANCED_PROP(float4, _UV3) UNITY_DEFINE_INSTANCED_PROP(float4, _Offset) UNITY_INSTANCING_BUFFER_END(Props) float inverseLerp(float a, float b, float value) { return (value - a) / (b - a); } float projectClamped(float position, float origin, float destination) { float direction = destination - origin; float magnitude = length(direction); direction = normalize(direction); float lhs = position - origin; float dotP = dot(lhs, direction); dotP = clamp(dotP, 0, magnitude); return origin + direction * dotP; } float project(float position, float origin, float destination) { float direction = destination - origin; float lhs = position - origin; float dotP = dot(lhs, direction); return origin + direction * dotP; } v2f vert(appdata v) { UNITY_SETUP_INSTANCE_ID(v); v2f o; UNITY_TRANSFER_INSTANCE_ID(v, o); // Use a single quad mesh and set the vertex positions here? // Problem: Mesh does not get drawn when original vertex positions are not visible by camera! //fixed2 coordinate = fixed2(v.id % 2, v.id / 2 % 2); //float4 newVertex = lerp( // lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1), coordinate.x), // lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3), coordinate.x), // coordinate.y); // //o.vertex = UnityObjectToClipPos(newVertex); o.vertex = UnityObjectToClipPos(v.vertex); o.uv.xy = v.uv.xy; o.uv.zw = v.vertex.xy; return o; } fixed4 frag(v2f i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); //return tex2D(_MainTex, i.uv.xy); float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0); float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1); float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2); float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3); float2 uv0 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV0); float2 uv1 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV1); float2 uv2 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV2); float2 uv3 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV3); float4 _offset = UNITY_ACCESS_INSTANCED_PROP(Props, _Offset); float4 _projectedPoint = project(i.uv.xy, v1, v3); float _distance = distance(i.uv.zw, _projectedPoint.xz); if(_distance > _offset.x && _distance < _offset.y) { return tex2D(_MainTex, i.uv.xy); return fixed4(0, 1, 0, 1); } return fixed4(1, 0, 0, 1); } ENDCG } } FallBack "VertexLit" } 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
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): Shader "Custom/Quad" { Properties { _MainTex("Texture", 2D) = "white" {} _Vertex0("Vertex0", Vector) = (0, 0, 0, 0) _Vertex1("Vertex1", Vector) = (1, 0, 0, 0) _Vertex2("Vertex2", Vector) = (0, 0, 1, 0) _Vertex3("Vertex3", Vector) = (1, 0, 1, 0) _UV0("UV0", Vector) = (0, 0, 0, 0) _UV1("UV1", Vector) = (1, 0, 0, 0) _UV2("UV2", Vector) = (0, 1, 0, 0) _UV3("UV3", Vector) = (0, 1, 0, 0) } SubShader { Tags { "Queue" = "Transparent" "RenderType" = "Transparent" } Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; uint id : SV_VertexID; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; float4 uv : TEXCOORD0; float4 q : TEXCOORD1; float4 b1 : TEXCOORD2; float4 b2 : TEXCOORD3; float4 b3 : TEXCOORD4; UNITY_VERTEX_INPUT_INSTANCE_ID }; sampler2D _MainTex; float4 _MainTex_ST; UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex0) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex1) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex2) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex3) UNITY_DEFINE_INSTANCED_PROP(float4, _UV0) UNITY_DEFINE_INSTANCED_PROP(float4, _UV1) UNITY_DEFINE_INSTANCED_PROP(float4, _UV2) UNITY_DEFINE_INSTANCED_PROP(float4, _UV3) UNITY_DEFINE_INSTANCED_PROP(float4, _Widths) UNITY_INSTANCING_BUFFER_END(Props) float2 cross2d(float4 a, float4 b) { return a.x * b.z - a.z * b.x; } v2f vert(appdata v) { UNITY_SETUP_INSTANCE_ID(v); v2f o; UNITY_TRANSFER_INSTANCE_ID(v, o); fixed2 coordinate = fixed2(v.id % 2, v.id / 2 % 2); float4 newVertex = lerp( lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1), coordinate.x), lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3), coordinate.x), coordinate.y); //o.vertex = UnityObjectToClipPos(newVertex); o.vertex = UnityObjectToClipPos(v.vertex); o.uv.xy = v.uv.xy; //o.uv.zw = TRANSFORM_TEX(v.uv, _MainTex); o.uv.zw = v.vertex.xy; float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0); float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1); float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2); float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3); o.q = newVertex; o.b1 = v1 - v0; o.b2 = v2 - v0; o.b3 = v0 - v1 - v2 + v3; return o; } fixed4 frag(v2f i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); //return tex2D(_MainTex, i.uv.xy); float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0); float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1); float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2); float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3); float2 uv0 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV0); float2 uv1 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV1); float2 uv2 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV2); float2 uv3 = UNITY_ACCESS_INSTANCED_PROP(Props, _UV3); float4 p = i.uv; float A = cross2d(i.b2, i.b3); float B = cross2d(i.b3, i.q) - cross2d(i.b1, i.b2); float C = cross2d(i.b1, i.q); float2 uv; if (abs(A) < 0.001) { // Linear form uv.y = -C/B; } else { // Quadratic form. Take positive root for CCW winding with V-up float discrim = B*B - 4*A*C; uv.y = 0.5 * (-B + sqrt(discrim)) / A; } // Solve for u, using largest-magnitude component float2 denom = i.b1 + uv.y * i.b3; if (abs(denom.x) > abs(denom.y)) uv.x = (i.q.x - i.b2.x * uv.y) / denom.x; else uv.x = (i.q.y - i.b2.y * uv.y) / denom.y; return tex2D(_MainTex, uv); } ENDCG } } FallBack "VertexLit" } Is there really no easy way of doing this?
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".
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): Shader "Custom/Quad" { Properties { _MainTex("Texture", 2D) = "white" {} _Vertex0("Vertex0", Vector) = (0, 0, 0, 0) _Vertex1("Vertex1", Vector) = (1, 0, 0, 0) _Vertex2("Vertex2", Vector) = (0, 0, 1, 0) _Vertex3("Vertex3", Vector) = (1, 0, 1, 0) _Offset("Offset", Vector) = (1, 0, 1, 0) } SubShader { Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "LightMode" = "ForwardBase" } Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc" #pragma multi_compile_fwdbase #include "AutoLight.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; uint id : SV_VertexID; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 vertex : SV_POSITION; float4 worldPos : TEXCOORD0; SHADOW_COORDS(1) UNITY_VERTEX_INPUT_INSTANCE_ID }; sampler2D _MainTex; float4 _MainTex_ST; UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex0) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex1) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex2) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex3) UNITY_DEFINE_INSTANCED_PROP(float4, _Offset) UNITY_INSTANCING_BUFFER_END(Props) float4 project(float4 _position, float4 _origin, float4 _destination) { float4 _direction = _destination - _origin; float _distance = dot(_position - _origin, _direction) / (length(_direction) * length(_direction)); return _origin + _direction * _distance; } v2f vert(appdata v) { UNITY_SETUP_INSTANCE_ID(v); v2f o; UNITY_TRANSFER_INSTANCE_ID(v, o); float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0); float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1); float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2); float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3); fixed2 coordinate = fixed2(v.id % 2, v.id / 2 % 2); float4 newVertex = lerp( lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1), coordinate.x), lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3), coordinate.x), coordinate.y); o.vertex = UnityObjectToClipPos(newVertex); //o.vertex = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, newVertex); TRANSFER_SHADOW(o) return o; } fixed4 frag(v2f i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); //return tex2D(_MainTex, i.worldPos.xz); float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0); float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1); float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2); float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3); float2 _offset = UNITY_ACCESS_INSTANCED_PROP(Props, _Offset); float4 _projectedPosition = project(i.worldPos, v1, v3); float _distance = distance(i.worldPos, _projectedPosition); fixed shadow = SHADOW_ATTENUATION(i); fixed4 _color = tex2D(_MainTex, i.worldPos.xz) * shadow; _color.a = _offset.x < _distance && _distance < _offset.y; return _color; } ENDCG } UsePass "Legacy Shaders/VertexLit/SHADOWCASTER" } }
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:
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.
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.
Your v2f has the SV_Position named vertex, but any macros related to lighting and shadows assume it's named pos.
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): Shader "Custom/Quad" { Properties { _MainTex("Texture", 2D) = "white" {} _Vertex0("Vertex0", Vector) = (0, 0, 0, 0) _Vertex1("Vertex1", Vector) = (1, 0, 0, 0) _Vertex2("Vertex2", Vector) = (0, 0, 1, 0) _Vertex3("Vertex3", Vector) = (1, 0, 1, 0) _Offset("Offset", Vector) = (1, 0, 1, 0) _ShadowIntensity("Shadow Intensity", Range(0, 1)) = 0.5 } SubShader { Tags { "Queue" = "Geometry+1" "RenderType" = "Transparent" "LightMode" = "ForwardBase" } Zwrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_instancing #include "UnityCG.cginc" #pragma multi_compile_fwdbase #include "AutoLight.cginc" #include "UnityLightingCommon.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; uint id : SV_VertexID; UNITY_VERTEX_INPUT_INSTANCE_ID }; struct v2f { float4 pos : SV_POSITION; fixed4 diff : COLOR0; float4 worldPos : TEXCOORD0; SHADOW_COORDS(1) UNITY_VERTEX_INPUT_INSTANCE_ID }; sampler2D _MainTex; float4 _MainTex_ST; float _ShadowIntensity; UNITY_INSTANCING_BUFFER_START(Props) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex0) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex1) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex2) UNITY_DEFINE_INSTANCED_PROP(float4, _Vertex3) UNITY_DEFINE_INSTANCED_PROP(float4, _Offset) UNITY_INSTANCING_BUFFER_END(Props) float4 project(float4 _position, float4 _origin, float4 _destination) { float4 _direction = _destination - _origin; float _distance = dot(_position - _origin, _direction) / (length(_direction) * length(_direction)); return _origin + _direction * _distance; } v2f vert(appdata v) { UNITY_SETUP_INSTANCE_ID(v); v2f o; UNITY_TRANSFER_INSTANCE_ID(v, o); float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0); float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1); float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2); float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3); fixed2 coordinate = fixed2(v.id % 2, v.id / 2 % 2); float4 newVertex = lerp( lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1), coordinate.x), lerp(UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2), UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3), coordinate.x), coordinate.y); o.pos = UnityObjectToClipPos(newVertex); //o.pos = UnityObjectToClipPos(v.vertex); o.worldPos = mul(unity_ObjectToWorld, newVertex); half3 worldNormal = UnityObjectToWorldNormal(v.normal); half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz)); o.diff = nl * _LightColor0; o.diff.rgb += ShadeSH9(half4(worldNormal, 1)); TRANSFER_SHADOW(o) return o; } fixed4 frag(v2f i) : SV_Target { UNITY_SETUP_INSTANCE_ID(i); //return tex2D(_MainTex, i.worldPos.xz); float4 v0 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex0); float4 v1 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex1); float4 v2 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex2); float4 v3 = UNITY_ACCESS_INSTANCED_PROP(Props, _Vertex3); float2 _offset = UNITY_ACCESS_INSTANCED_PROP(Props, _Offset); float4 _projectedPosition = project(i.worldPos, v1, v3); float _distance = distance(i.worldPos, _projectedPosition); fixed _brightness = 1 - ((1 - SHADOW_ATTENUATION(i)) * _ShadowIntensity); fixed4 _color = tex2D(_MainTex, i.worldPos.xz) * _brightness * i.diff; _color.a = _offset.x < _distance && _distance < _offset.y; return _color; } ENDCG } } } 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 ).
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.