I am creating a procedural mesh based on a curve and the size of the mesh gets smaller throughout the curve as you see below.

enter image description here

And the problem is UV gets zig-zagged as the size changes (it works perfectly when the size of the mesh is same throughout the curve).

enter image description here

 UVs.Add(new Vector2(0f, (float)id / count));
 UVs.Add(new Vector2(1f, (float)id / count));
 start = Vertices.Count - 4;
          Triangles.Add(start + 0);
                 Triangles.Add(start + 2);
                 Triangles.Add(start + 1);
                 Triangles.Add(start + 1);
                 Triangles.Add(start + 2);
                 Triangles.Add(start + 3);

 mesh.vertices = Vertices.ToArray();
         //mesh.normals = normales;
         mesh.uv = UVs.ToArray();
         mesh.triangles = Triangles.ToArray();

How can I fix this?

  • \$\begingroup\$ What are id and count? What does UVs.ToArray() do? How are you uploading the vertices and texture coordinates to the card? \$\endgroup\$ Sep 7, 2017 at 2:44
  • \$\begingroup\$ @user1118321 from context, id is the index of the current segment out of all crossbars along the strip, of where there are count in total. List<T>.ToArray() returns a typed array of all entries in the list (so here, a Vector2[]). These data buffers are made available to the rendering system by assigning them to the Mesh instance mesh 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\$
    – DMGregory
    Sep 7, 2017 at 12:31
  • \$\begingroup\$ That was my assumption, but I asked for clarification because sometimes assumptions are wrong. I've looked at something like that in my own code in the past only to realize that count actually meant vertices instead of polygons, or vice-versa. Just making sure everything we think is going on is really going on. \$\endgroup\$ Sep 7, 2017 at 16:36

1 Answer 1


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: Example of normal affine texturing with zig-zag artifacts

Projective texturing: Example of projective textring correcting the artifacts

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!

  • \$\begingroup\$ Hi, I've been struggling with the same problem for quite some time now, and this seems like precisely what's happening to me. The only difference is that I am working in threejs and not in unity. We managed to solve the problem by increasing the number of triangles, but would still like to try projective texturing. Do you have any idea how to even start to implement this in threejs? Thank you! \$\endgroup\$ Dec 12, 2018 at 19:35
  • 1
    \$\begingroup\$ Pretty much the same way. You need a third texture coordinate to store the divisor, then you perform the division in the fragment shader. If you've had trouble applying this to your case, asking a new question will let you include a code sample, showing how you're drawing your mesh so far, that answers can build upon. \$\endgroup\$
    – DMGregory
    Dec 12, 2018 at 19:42
  • \$\begingroup\$ Do I need to store the divisor as a third coordinate (and because of that change struct and cut out the TRANSFORM_TEX macro, which I don't know how to do in threejs) or can I just pass the divisor as an attribute to vertex shader and then from there as varyings to fragment shader where it could be used to to the division? \$\endgroup\$ Dec 12, 2018 at 23:51
  • \$\begingroup\$ TRANSFORM_TEX is a Unity macro. You don't have that in three.js so there's nothing to cut out. As long as the interpolated uv coordinates and divisors reach the fragment shader, it doesn't really matter how you got them there. Have you run into any trouble providing it as an attribute/varying so far? \$\endgroup\$
    – DMGregory
    Dec 12, 2018 at 23:54
  • \$\begingroup\$ I didn't try yet as I didn't want to waste time until I verify that it is possible. Just one last question, the divisor will also be interpolated once it reaches the fragment shader, correct? Meaning that on pixel between two vertices it will be some interpolated value and not original value? I have hard time wrapping my head around why multiplying the UV and then dividing it helps, but I'll take your word for it and try it. :slightly_smiling_face: Thank you very much your help! \$\endgroup\$ Dec 13, 2018 at 0:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .