Typical UV mapping is what's called an affine transformation. That means the mapping of each triangle between 3D space and texture space can include rotation, translation, scaling/squash, and skew (ie. anything we can do with a homogeneous matrix multiplication)
The thing about affine transformations is that they're uniform across their whole domain - the rotation, translation, scale, and skew we apply to the texture near vertex A is the same as what we apply near vertex B, within any one triangle. Lines that are parallel in one space will be mapped to parallel lines in the other, never converging/diverging.
But the gradual tapering you're trying to apply is not uniform - it's mapping parallel lines in the texture to converging lines on the mesh. That means the scale of the texture measured across the band is changing continuously as we move down the strip. That's more than the affine transformations of 2D UV mapping can accurately represent: interpolating 2D uv coordinates between adjacent vertices will get one consistent scale along the whole edge, even the diagonal edge which should be shrinking in scale as it moves down the strip. That mismatch is what creates this warbling zigzag.
This issue crops up anytime we want to map a rectangle to a trapezoid - parallel sides to converging sides: there's just no affine transformation that does this, so we have to approximate it piecewise, leading to visible seams.
For most purposes you can minimize the effect by adding more geometry. Increasing the number of subdivisions along the length of the strip, and splitting the strip into two or more segments along its breadth, with the diagonals of the triangles arranged in a herringbone pattern, can make the effect much less noticeable. It will always be present to some extent as long as we're using affine transformations though.
But there is a way around it. We can use the same trick we use for 3D rendering to draw trapezoids in perspective given rectangular walls & floors: we use projective coordinates!
Affine texturing:

Projective texturing:

To do this, we need to add a third uv coordinate (uvw) and modify our shaders.
Given a scale factor at each point (say, equal to the breadth of your strip at that spot), you can construct the 3D projective uvw coordinate from your regular 2D uv coordinate this way:
Vector3 uv3 = ((Vector3)uv2) * scale;
uv3.z = scale;
To apply these 3D uvw coordinates to your mesh, you'll need to use the Vector3 overload Mesh.SetUVs(int channel, List uvs)
And be sure to change your shader's input struct to expect a 3D texture coordinate (shown here using the default unlit shader):
struct appdata
{
float4 vertex : POSITION;
float3 uv : TEXCOORD0; // Change float2 to float 3.
};
// Also do this for the uv sent from the vertex shader to the fragment shader.
You'll also need to cut out the TRANSFORM_TEX macro in the vertex shader, since it expects a 2D uv:
// o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.uv = v.uv;
// If you define a float4 with the special name _MainTex_ST,
// you can get the same effect the macro had by doing this:
o.uv.xy = o.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;
Finally, to turn back to a 2D texture coordinate for texture sampling, you just divide by the third coordinate in your fragment shader:
float2 uv = i.uv.xy / i.uv.z;
Since we made this 3D uvw coordinate from our desired 2D coordinate by multiplying by the same number, the two operations cancel out and we get back to our original desired 2D coordinate, but now with non-linear interpolation between the vertices. :D
It's important to do this division per fragment and not in the vertex shader. If it's done per vertex, then we're back to interpolating the resulting coordinates linearly along each edge, and we've lost the nonlinearity we were trying to introduce with the projective coordinate!
id
andcount
? What doesUVs.ToArray()
do? How are you uploading the vertices and texture coordinates to the card? \$\endgroup\$id
is the index of the current segment out of all crossbars along the strip, of where there arecount
in total.List<T>.ToArray()
returns a typed array of all entries in the list (so here, aVector2[]
). These data buffers are made available to the rendering system by assigning them to theMesh
instancemesh
in the last few lines. But none of this is the particularly interesting part of the code: how the vertex points are chosen. Those details would be more useful to see. ;) \$\endgroup\$count
actually meant vertices instead of polygons, or vice-versa. Just making sure everything we think is going on is really going on. \$\endgroup\$