Can anyone point me towards a resource on how to fix these texture stretch at the corners of the line renderer? Think it has something to do with UV coordinates
No actually I take it back, none of the modes seems to be adding extra UV stretch (or I should say, less) to the corners.
I think maybe to do what you want you have two choices:
- Add more points in that corner
- Use Splines
and one of the extrusion scripts that come with it. But it’s a completely different system!
Adding more points to the corner does not work. Tried adding 100s. I’ll see if splines has the issue.
Ah sorry, I didn’t mean adding geometry to the corner, but literally more “control points”, which should create more segments.
But then it all becomes harder to manage… it depends on how many of these you have, how are they created (by hand?), etc.
Any fix here? This seems like a pretty ridiculous state for corners.
Finally fixed this by generating a custom mesh from scratch. This code assum an animated material on X UV axis. My material is build with shadergraph and animated with a tiling and offset node along the X UVs.
The segmented Quad are needed to realign the U in the UVs after a corner as they get out of sync and start streching the texture. There’s no doupt a better way to do this, but this work around is good enough for now. See it in action:
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using UnityEngine;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class MeshTest : MonoBehaviour
{
private Mesh mesh;
private float width = 0.7f;
private float height = 0.7f;
[SerializeField]
private CornerDirection cornerDirection = CornerDirection.DOWN_TO_LEFT;
// Angle constants
private const float ANGLE_0 = 0f;
private const float ANGLE_90 = Mathf.PI / 2f; // 90 degrees
private const float ANGLE_180 = Mathf.PI; // 180 degrees
private const float ANGLE_270 = Mathf.PI * 1.5f; // 270 degrees
private const float ANGLE_NEG_90 = -Mathf.PI / 2f; // -90 degrees
public enum CornerDirection
{
DOWN_TO_LEFT,
DOWN_TO_RIGHT,
RIGHT_TO_DOWN,
LEFT_TO_DOWN,
TOP_TO_RIGHT,
TOP_TO_LEFT,
LEFT_TO_TOP,
RIGHT_TO_TOP,
}
private enum Direction
{
DOWN_TO_UP,
LEFT_TO_RIGHT,
RIGHT_TO_LEFT,
UP_TO_DOWN
}
// Start is called before the first frame update
void Start()
{
mesh = new Mesh();
MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
meshFilter.mesh = mesh;
// Create lists to store mesh data
List<Vector3> vertices = new List<Vector3>();
List<int> triangles = new List<int>();
List<Vector2> uvs = new List<Vector2>();
vertices.Add(Vector3.zero); // Bottom-left corner
uvs.Add(new Vector2(1f, 0f)); // Bottom-left corner
vertices.Add(new Vector3(width, 0f, 0)); // Bottom Right
uvs.Add(new Vector2(1, 1f)); // Bottom Right
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
var nextIndices = AddQuarterCircle(vertices, triangles, uvs, cornerDirection);
switch (cornerDirection)
{
case CornerDirection.DOWN_TO_LEFT:
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.RIGHT_TO_DOWN);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.TOP_TO_RIGHT);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.LEFT_TO_TOP);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.DOWN_TO_LEFT);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.RIGHT_TO_TOP);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.DOWN_TO_RIGHT);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.LEFT_TO_DOWN);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.TOP_TO_LEFT);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
break;
case CornerDirection.DOWN_TO_RIGHT:
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.LEFT_TO_DOWN);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.TOP_TO_LEFT);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.RIGHT_TO_TOP);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.DOWN_TO_RIGHT);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.LEFT_TO_TOP);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.DOWN_TO_LEFT);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.RIGHT_TO_DOWN);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
AddQuad(vertices, triangles, uvs);
nextIndices = AddQuarterCircle(vertices, triangles, uvs, CornerDirection.TOP_TO_RIGHT);
AddQuad(vertices, triangles, uvs, startIndices: nextIndices);
AddQuad(vertices, triangles, uvs);
break;
}
// Assign the data to the mesh
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
//Log.DEBUG("My uvs: " + string.Join(", ", uvs));
mesh.uv = uvs.ToArray();
// Recalculate normals for proper lighting
//mesh.RecalculateNormals();
}
(int, int) AddQuarterCircle(List<Vector3> vertices, List<int> triangles, List<Vector2> uvs, CornerDirection direction)
{
switch (direction)
{
case CornerDirection.DOWN_TO_LEFT:
return AddQuarterCirclePivotLeft(vertices, triangles, uvs, direction);
case CornerDirection.DOWN_TO_RIGHT:
return AddQuarterCirclePivotRight(vertices, triangles, uvs, direction);
case CornerDirection.RIGHT_TO_DOWN:
return AddQuarterCirclePivotLeft(vertices, triangles, uvs, direction);
case CornerDirection.RIGHT_TO_TOP:
return AddQuarterCirclePivotRight(vertices, triangles, uvs, direction);
case CornerDirection.LEFT_TO_DOWN:
return AddQuarterCirclePivotRight(vertices, triangles, uvs, direction);
case CornerDirection.LEFT_TO_TOP:
return AddQuarterCirclePivotLeft(vertices, triangles, uvs, direction);
case CornerDirection.TOP_TO_RIGHT:
return AddQuarterCirclePivotLeft(vertices, triangles, uvs, direction);
case CornerDirection.TOP_TO_LEFT:
return AddQuarterCirclePivotRight(vertices, triangles, uvs, direction);
default:
throw new System.Exception("Invalid direction");
}
}
(int, int) AddQuarterCirclePivotRight(List<Vector3> vertices, List<int> triangles, List<Vector2> uvs, CornerDirection direction)
{
float radius = 0.7f; // Radius of the quarter-circle
int segments = 16; // Number of segments to approximate the quarter-circle
// Store the starting index for the new vertices
int pivotIndex = vertices.Count - 1;
var pivotVertex = vertices[vertices.Count - 1]; // Use the 2nd last vertex as the center
// Use the last two UVs as the base
var bottomLeftUV = uvs[uvs.Count - 2];
//var bottomRightUV = uvs[uvs.Count - 1];
// Adjust the angle range based on the orientation
float startAngle;
float endAngle;
switch (direction)
{
case CornerDirection.DOWN_TO_RIGHT:
startAngle = ANGLE_180;
endAngle = ANGLE_90;
break;
case CornerDirection.LEFT_TO_DOWN:
startAngle = ANGLE_90;
endAngle = ANGLE_0;
break;
case CornerDirection.TOP_TO_LEFT:
startAngle = ANGLE_0;
endAngle = ANGLE_NEG_90;
break;
case CornerDirection.RIGHT_TO_TOP:
startAngle = ANGLE_NEG_90;
endAngle = -ANGLE_180; // -180 degrees
break;
default:
throw new System.Exception("Invalid direction");
}
// Generate vertices along the quarter-circle
for (int i = 1; i <= segments; i++)
{
float angle = Mathf.Lerp(startAngle, endAngle, i / (float)segments); // Interpolate angle based on orientation
float x = Mathf.Cos(angle) * radius;
float y = Mathf.Sin(angle) * radius;
vertices.Add(pivotVertex + new Vector3(x, y, 0)); // Add vertex on the arc relative to the center
// Calculate UVs relative to the base UVs
float u = Mathf.Lerp(bottomLeftUV.x, bottomLeftUV.x - 1, i / (float)segments); // Interpolate U based on the curve
float v = bottomLeftUV.y;
var uv = new Vector2(u, v);
//print(uv);
uvs.Add(uv);
}
//Log.DEBUG("bottom left UV1: " + bottomLeftUV + ", bottomRight: UV2: " + bottomRightUV);
triangles.Add(pivotIndex); // Center vertex
triangles.Add(pivotIndex - 1);
triangles.Add(pivotIndex+ 1);
// Generate triangles for the quarter-circle
for (int i = 1; i < segments; i++)
{
triangles.Add(pivotIndex); // Center vertex
triangles.Add(pivotIndex + i);
triangles.Add(pivotIndex + i + 1);
}
return (vertices.Count-1, pivotIndex);
}
(int, int) AddQuarterCirclePivotLeft(List<Vector3> vertices, List<int> triangles, List<Vector2> uvs, CornerDirection direction)
{
float radius = 0.7f; // Radius of the quarter-circle
int segments = 16; // Number of segments to approximate the quarter-circle
// Store the starting index for the new vertices
int pivotIndex = vertices.Count - 2;
var pivotVertex = vertices[vertices.Count - 2]; // Use the 2nd last vertex as the center
// Use the last two UVs as the base
//var bottomLeftUV = uvs[uvs.Count - 2];
var bottomRightUV = uvs[uvs.Count - 1];
// Adjust the angle range based on the orientation
float startAngle;
float endAngle;
switch (direction)
{
case CornerDirection.DOWN_TO_LEFT:
startAngle = ANGLE_0;
endAngle = ANGLE_90;
break;
case CornerDirection.RIGHT_TO_DOWN:
startAngle = ANGLE_90;
endAngle = ANGLE_180;
break;
case CornerDirection.TOP_TO_RIGHT:
startAngle = ANGLE_180;
endAngle = ANGLE_270;
break;
case CornerDirection.LEFT_TO_TOP:
startAngle = ANGLE_NEG_90;
endAngle = ANGLE_0;
break;
default:
throw new System.Exception("Invalid direction");
}
// Generate vertices along the quarter-circle
for (int i = 1; i <= segments; i++)
{
float angle = Mathf.Lerp(startAngle, endAngle, i / (float)segments); // Interpolate angle based on orientation
float x = Mathf.Cos(angle) * radius;
float y = Mathf.Sin(angle) * radius;
vertices.Add(pivotVertex + new Vector3(x, y, 0)); // Add vertex on the arc relative to the center
// Calculate UVs relative to the base UVs
float u = Mathf.Lerp(bottomRightUV.x, bottomRightUV.x - 1f, i / (float)segments); // Interpolate U based on the curve
float v = bottomRightUV.y;
var uv = new Vector2(u, v);
//print(uv);
uvs.Add(uv);
}
//Log.DEBUG("bottom left UV1: " + bottomLeftUV + ", bottomRight: UV2: " + bottomRightUV);
// Generate triangles for the quarter-circle
for (int i = 1; i <= segments; i++)
{
triangles.Add(pivotIndex); // Center vertex
triangles.Add(pivotIndex + i);
triangles.Add(pivotIndex + i + 1);
}
return (pivotIndex, vertices.Count - 1);
}
void AddQuad(
List<Vector3> vertices,
List<int> triangles,
List<Vector2> uvs,
(int, int)? startIndices = null)
{
if (startIndices.HasValue) {
// After a corner the X UVs will be out of alignment, realign over a series of quads.
// This should be less noticeable to the player.
AddQuadSegmented(vertices, triangles, uvs, startIndices: startIndices.Value);
return;
}
int startIndex = vertices.Count - 2;
var bottomLeft = vertices[vertices.Count - 2];
var bottomRight = vertices[vertices.Count - 1];
var bottomLeftUV = uvs[vertices.Count - 2];
var bottomRightUV = uvs[vertices.Count - 1];
Direction direction = quadDirection(bottomLeft, bottomRight);
//print("bottomLeftUV: " + bottomLeftUV + ", bottomRightUV: " + bottomRightUV);
Vector3 topLeftUV = bottomLeftUV - new Vector2(1f, 0f);
Vector3 topRightUV = bottomRightUV - new Vector2(1f, 0f);
Vector3 topLeft;
Vector3 topRight;
switch (direction)
{
case Direction.DOWN_TO_UP:
topLeft = bottomLeft + new Vector3(0, height, 0);
topRight = bottomRight + new Vector3(0, height, 0f);
break;
case Direction.UP_TO_DOWN:
topLeft = bottomLeft - new Vector3(0, height, 0);
topRight = bottomRight - new Vector3(0, height, 0f);
break;
case Direction.LEFT_TO_RIGHT:
topLeft = bottomLeft + new Vector3(width, 0);
topRight = bottomRight + new Vector3(width, 0f);
break;
case Direction.RIGHT_TO_LEFT:
topLeft = bottomLeft - new Vector3(width, 0);
topRight = bottomRight - new Vector3(width, 0f);
break;
default:
throw new System.Exception("Invalid direction");
}
vertices.Add(topLeft); // Top left
uvs.Add(topLeftUV); // Top left
vertices.Add(topRight); // Top Right
uvs.Add(topRightUV); // Top Right
triangles.Add(startIndex);
triangles.Add(startIndex + 2);
triangles.Add(startIndex + 3);
triangles.Add(startIndex);
triangles.Add(startIndex + 3);
triangles.Add(startIndex + 1);
}
private Direction quadDirection(Vector3 bottomLeft, Vector3 bottomRight)
{
Vector3 vector = bottomRight - bottomLeft;
// Determine the direction based on the direction vector
// Horizontal
if (Mathf.Approximately(vector.y, 0))
{
return vector.x > 0 ? Direction.DOWN_TO_UP : Direction.UP_TO_DOWN;
}
else
{
// Vertical
return vector.y > 0 ? Direction.RIGHT_TO_LEFT : Direction.LEFT_TO_RIGHT;
}
}
void AddQuadSegmented(
List<Vector3> vertices,
List<int> triangles,
List<Vector2> uvs,
(int, int) startIndices)
{
int startIndex = vertices.Count - 1;
var bottomLeft = vertices[startIndices.Item1];
var bottomRight = vertices[startIndices.Item2];
var bottomLeftUV = uvs[startIndices.Item1];
var bottomRightUV = uvs[startIndices.Item2];
Direction direction = quadDirection(bottomLeft, bottomRight);
//print("bottomLeftUV: " + bottomLeftUV + ", bottomRightUV: " + bottomRightUV);
Vector3 topLeftUV;
Vector3 topRightUV;
Vector3 topLeft;
Vector3 topRight;
switch (direction)
{
case Direction.DOWN_TO_UP:
topLeft = bottomLeft + new Vector3(0, height, 0);
topRight = bottomRight + new Vector3(0, height, 0f);
if (bottomLeftUV.x > bottomRightUV.x) {
topLeftUV = bottomLeftUV - new Vector2(2f, 0f);
topRightUV = bottomRightUV - new Vector2(1f, 0f);
} else {
topLeftUV = bottomLeftUV - new Vector2(1f, 0f);
topRightUV = bottomRightUV - new Vector2(2f, 0f);
}
break;
case Direction.UP_TO_DOWN:
topLeft = bottomLeft - new Vector3(0, height, 0);
topRight = bottomRight - new Vector3(0, height, 0f);
if (bottomLeftUV.x > bottomRightUV.x) {
topLeftUV = bottomLeftUV - new Vector2(2f, 0f);
topRightUV = bottomRightUV - new Vector2(1f, 0f);
} else {
topLeftUV = bottomLeftUV - new Vector2(1f, 0f);
topRightUV = bottomRightUV - new Vector2(2f, 0f);
}
break;
case Direction.LEFT_TO_RIGHT:
topLeft = bottomLeft + new Vector3(width, 0);
topRight = bottomRight + new Vector3(width, 0f);
if (bottomLeftUV.x > bottomRightUV.x) {
topLeftUV = bottomLeftUV - new Vector2(2f, 0f);
topRightUV = bottomRightUV - new Vector2(1f, 0f);
} else {
topLeftUV = bottomLeftUV - new Vector2(1f, 0f);
topRightUV = bottomRightUV - new Vector2(2f, 0f);
}
break;
case Direction.RIGHT_TO_LEFT:
topLeft = bottomLeft - new Vector3(width, 0);
topRight = bottomRight - new Vector3(width, 0f);
if (bottomLeftUV.x > bottomRightUV.x) {
topLeftUV = bottomLeftUV - new Vector2(2f, 0f);
topRightUV = bottomRightUV - new Vector2(1f, 0f);
} else {
topLeftUV = bottomLeftUV - new Vector2(1f, 0f);
topRightUV = bottomRightUV - new Vector2(2f, 0f);
}
break;
default:
throw new System.Exception("Invalid direction");
}
int segments = 6;
for (int i = 0; i < segments; i++)
{
float t = (float)i / (segments - 1);
var newTopLeft = Vector3.Lerp(bottomLeft, topLeft, t);
var newTopRight = Vector3.Lerp(bottomRight, topRight, t);
vertices.Add(newTopLeft);
vertices.Add(newTopRight);
// Calculate UVs relative to the base UVs
var newTopLeftUV = Vector3.Lerp(bottomLeftUV, topLeftUV, t);
var newTopRightUV = Vector3.Lerp(bottomRightUV, topRightUV, t);
uvs.Add(newTopLeftUV);
uvs.Add(newTopRightUV);
}
startIndex += 3;
triangles.Add(startIndices.Item1);
triangles.Add(startIndices.Item2);
triangles.Add(startIndex);
triangles.Add(startIndices.Item2);
triangles.Add(startIndex);
triangles.Add(startIndex + 1);
for (int i = 0; i < segments + 1; i++)
{
triangles.Add(startIndex + i);
triangles.Add(startIndex + i + 1);
triangles.Add(startIndex + i + 2);
triangles.Add(startIndex + i + 1);
triangles.Add(startIndex + i + 2);
triangles.Add(startIndex + i + 3);
}
}
// Update is called once per frame
void Update()
{
}
}