Unity Trail UV issue fix

Hello everyone. I would like to talk about Unity Trails UV issue. It exists both in Particle System trails and Trail Renderer component.

I’ve found it while making custom VFX shaders for particles and trails. So… Let’s assume, we would like to have interesting width curve for our trail:

image

Here we have a result:

image

On the first look nothing is wrong with it. But let’s apply some texture.
Just for demonstration, we’ll get this one:

image

Here is the result:

image

Weird, right? With more inconsistent speed we can have results much worse:

image

So what happen? As we know, we use UVs to map textures, right? Let’s look at it:

image

Well… It appeared that the problem hides in data interpolation inside triangles, which size is very inconsistent. The first idea of fixing the problem was to increase resolution of the trail. But here we have another issue - Unity do not add several quads to the trail mesh per frame. So regardless of how low we’ll set Min Vertex Distance, we are stuck with framerate.

Next idea was much more radical - write custom trail renderer. It’s serious decision and a lot of work to do to make user friendly trail system that will work with Unity particle system as deep as its own trails. Firstly I decided to test things on custom high poly model, and… I was not impressed:

image

At least I was pretty happy to know that from this point I do not have to right custom trail system. But we need to solve this issue somehow. I started looking for solution inside shader. And, actually, found one.

The idea in general is next:

  1. Leave the curve alone… just do not touch it at all. Let it be just a constant value of 1
  2. Inside shader get normalized position along trail. We can do it in two ways:
    1. Get UV.x if we have Stretch texture mode
    2. Use Color gradient if we have Tile texture mode. Just use linear gradient from completely White to completely Black color. At this point we have to lose color per trail, but we can replace it with gradient texture and use it inside shader (as we have normalized value along trail now). As we need just a single channel from color, we can still use other three for another purposes (for example, alpha).
  3. Use it to sample a texture with baked gradient that represents trail width
  4. Deform UVs using that value and cut values outside of [0, 1] bounds

The whole thing inside shader for tile mode might look like this:

image

Look at the result. Not a single issue by the cost of some flexibility:

I’m very pleased with such results. But at the end of the day, it’s up to you to choose if you want to lose color per trail for better quality or not.

Hope this information was helpful for you.

11 Likes

This is not really a Unity issue but a result of how texturing works in hardware. If you take a square, built up of two triangles, and you compress it to a Isosceles trapezoid, you get distortion. To show you why, take the square and draw a cross on it.

Distortion|690x206

So on the left you se a square. The red line touches the edge of the triangles, at the center of the edge. If we now distort the quad (center), you can see that the cross is no longer straight, but if you look more carefully you will see that the red lines still hit the center of the edges. The center is still correct (but may not be what you want)

For triangles that are drawn in perspective, this is adjusted for in modern hardware. If you look to the right, you dont get any distortion but in order to accomplish this the red lines no longer hit the middle of the edges in 2D. This is because the hardware is looking at the middle point in 3D, and if the two top vertices are further away, the texture should “creep up” towards the top.

Old hardware like PlayStation one couldn’t do this, so you got a lot of texture distortion.

So what can be done? Well the warping of the texture needs a depth value to do perspective correct texturing and that is stored in the forth texture coordinate. Yes, there is more the just U and V. You can modify this fourth value to fake that the parts of you strip are at different distances to the camera and therefor get compensate for the distortion.

This is pretty deep in to the weeds of texture hardware and UV mapping, but it can be done. Ive spent the last 3 years writing UV tools for MinistryOfFlat.com and then you get in to these kinds of details…

Thank you for the detailed response. I’m not quite agree with your perspective correction though. All my examples were made with ortho projection where you actually don’t have perspective and depth, but you still can see the issue.

I also mentioned the problem of UV interpolation inside triangles in the post. So yes, that is not a Unity unique problem. But I do not work in any other engine, so I was not sure if in UE for example, engineers hadn’t made some corrections under the hood. Did they?

The reason you get distortion is because there is no depth.

Try changing the float2 you give to the texture lookup, and modify the last value depending on the size of the strip.

I was thinkin about it, but Unity doesn’t allow you to send data to trail vertices. So there is no good way to get width inside shader. Therefore I don’t use trail width curve, but use texture to sample it. And I’m already modifying uv.y depending on it but in another way. Of course, by the cost of increased fillrate, because geometry width is actually 1 and there will be pretty much 0 alpha there.

I do not want to write custom trail system, bc you’ll need to write too much stuff to support at least particle system on the same level as built-in trails. That’s the most valuable argument to start looking for some workarounds with built-in trails. Like the one I’ve described here.

You dont have to write your own trail shader, you just need to put in width of the trail in to the last texture coordinate.

tex2D(texture, float4(uv, 0, 1.0 / width_of_trail));

As I said, you can’t send any data to trail geometry (uv for example). You will not be able to get width trail inside shader without: a) sampling it from a texture or b) writing custom trail renderer.

a) If you sample width from a texture, I just do not understand, why you have to deal with trail actual width. It will be harder to work with trails when you need both trail geometry width curve and width texture. So I use just a texture and const 1.0 width.

b) Problems with custom trail renderer I’ve already described.