Using the LineRenderer (the component that comes with Unity) to draw a thick line with three nodes, I get a weird, twisted line as shown below, instead of the untwisted line that I want. Am I missing something, is what I want possible, and if so, how?
In the images, the red and blue lines are part of the texture of the line's material (I included that so that the twist is more obvious).
Here is an actual render:
The problem of the LineRenderer is that it uses only the direction to the next point to calculate the tangent/binormal. If you have an angle smaller than 90 the faces get twisted.
I've written a quite simple CustomLineRenderer which works similar to the LineRenderer, but it calculates the combined tangent/binormal. It's far away from a perfect line renderer since angles smaller than 90 will still produce not good-looking lines but at least the quads don't get twisted ;).
Some important notes:
- The CustomLineRenderer needs to be on a GameObject at 0,0,0 with no rotation, because i did't include a "Use World Space" option.
- I don't use public arrays for the points so you have to assign the points per script.
- You can give each point a different color or width. There is another version of SetColor and SetWidth that can calculate a linear interpolation for two given colors or widths. These functions needs to be called again when the point count have changed.
Feel free to optimise/extend/modify it to match your needs ;)
// CustomLineRenderer.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class CustomLineVertex
{
public Vector3 position = Vector3.zero;
public Color color = Color.white;
public float width = 1.0f;
}
[RequireComponent(typeof(MeshFilter),typeof(MeshRenderer))]
public class CustomLineRenderer : MonoBehaviour
{
private Mesh m_Mesh;
private int[] m_Indices;
private Vector2[] m_UVs;
private List<CustomLineVertex> m_Points;
void Awake ()
{
m_Mesh = new Mesh();
m_Points = new List<CustomLineVertex>();
GetComponent<MeshFilter>().sharedMesh = m_Mesh;
}
void Start()
{
// To calculate the initial bounds
UpdateMesh(Camera.main);
}
void OnWillRenderObject()
{
UpdateMesh(Camera.current);
}
void UpdateMesh(Camera aCamera)
{
Vector3 localViewPos = transform.InverseTransformPoint(aCamera.transform.position);
Vector3[] vertices = m_Mesh.vertices;
Vector3[] normals = m_Mesh.normals;
Color[] colors = m_Mesh.colors;
Vector3 oldTangent = Vector3.zero;
Vector3 oldDir = Vector3.zero;
for (int i = 0; i < m_Points.Count-1; i++)
{
Vector3 faceNormal = (localViewPos - m_Points*.position).normalized;*
<em>Vector3 dir = (m_Points[i+1].position - m_Points*.position);*</em>
_*Vector3 tangent = Vector3.Cross(dir,faceNormal).normalized;*_
<em><em>Vector3 offset = (oldTangent+tangent).normalized * m_Points*.width/2.0f;*</em></em>
<em><em><em>vertices[i*2] = m_Points*.position - offset;*</em></em></em>
<em><em><em><em>vertices[i*2+1] = m_Points*.position + offset;*</em></em></em></em>
<em><em><em><em>_normals[i*2] = normals[i*2+1] = faceNormal;_</em></em></em></em>
<em><em><em><em><em>colors[i*2] = colors[i*2+1] = m_Points*.color;*</em></em></em></em></em>
<em><em><em><em><em>*if (i == m_Points.Count - 2)*</em></em></em></em></em>
<em><em><em><em>_*{*_</em></em></em></em>
<em><em><em><em>_*// last two points*_</em></em></em></em>
<em><em><em><em><em><em>vertices[i*2+2] = m_Points[i+1].position - tangent * m_Points[i+1].width/2.0f;</em></em></em></em></em></em>
<em><em><em><em><em><em>vertices[i*2+3] = m_Points[i+1].position + tangent * m_Points[i+1].width/2.0f;</em></em></em></em></em></em>
<em><em><em><em><em>_normals[i*2+2] = normals[i*2+3] = faceNormal;_</em></em></em></em></em>
<em><em><em><em><em><em>colors[i*2+2] = colors[i*2+3] = m_Points[i+1].color;</em></em></em></em></em></em>
<em><em><em><em>_*}*_</em></em></em></em>
<em><em><em><em>_*oldDir = dir;*_</em></em></em></em>
<em><em><em><em>_*oldTangent = tangent;*_</em></em></em></em>
<em><em><em><em>_*}*_</em></em></em></em>
<em><em><em><em><em>*m_Mesh.vertices = vertices;*</em></em></em></em></em>
<em><em><em><em><em>*m_Mesh.normals = normals;*</em></em></em></em></em>
<em><em><em><em><em>*m_Mesh.colors = colors;*</em></em></em></em></em>
<em><em><em><em><em>*m_Mesh.uv = m_UVs;*</em></em></em></em></em>
<em><em><em><em><em>*m_Mesh.SetTriangleStrip(m_Indices,0);*</em></em></em></em></em>
<em><em><em><em><em>*m_Mesh.RecalculateBounds();*</em></em></em></em></em>
<em><em><em><em>_*}*_</em></em></em></em>
<em><em><em><em>_*public void SetVertexCount(int aCount)*_</em></em></em></em>
<em><em><em><em>_*{*_</em></em></em></em>
<em><em><em><em>_*aCount = Mathf.Clamp(aCount,0,0xFFFF/2);*_</em></em></em></em>
<em><em><em><em><em>*if (m_Points.Count > aCount)*</em></em></em></em></em>
<em><em><em><em><em>*m_Points.RemoveRange(aCount,m_Points.Count-aCount);*</em></em></em></em></em>
<em><em><em><em><em>*while (m_Points.Count < aCount)*</em></em></em></em></em>
<em><em><em><em><em>*m_Points.Add(new CustomLineVertex());*</em></em></em></em></em>
<em><em><em><em><em><em>m_Mesh.vertices = new Vector3[m_Points.Count*2];</em></em></em></em></em></em>
<em><em><em><em><em><em>m_Mesh.normals = new Vector3[m_Points.Count*2];</em></em></em></em></em></em>
<em><em><em><em><em><em>m_Mesh.colors = new Color[m_Points.Count*2];</em></em></em></em></em></em>
<em><em><em><em><em><em>m_Indices = new int[m_Points.Count*2];</em></em></em></em></em></em>
<em><em><em><em><em><em>m_UVs = new Vector2[m_Points.Count*2];</em></em></em></em></em></em>
<em><em><em><em><em>*for (int i = 0; i < m_Points.Count; i++)*</em></em></em></em></em>
<em><em><em><em>_*{*_</em></em></em></em>
<em><em><em><em><em><em>m_Indices[i*2] = i*2;</em></em></em></em></em></em>
<em><em><em><em><em><em>m_Indices[i*2+1] = i*2+1;</em></em></em></em></em></em>
<em><em><em><em><em><em>m_UVs[i*2] = m_UVs[i*2+1] = new Vector2((float)i/(m_Points.Count-1),0);</em></em></em></em></em></em>
<em><em><em><em><em><em>m_UVs[i*2+1].y = 1.0f;</em></em></em></em></em></em>
<em><em><em><em>_*}*_</em></em></em></em>
<em><em><em><em>_*}*_</em></em></em></em>
<em><em><em><em>_*public void SetPosition(int aIndex, Vector3 aPosition)*_</em></em></em></em>
<em><em><em><em>_*{*_</em></em></em></em>
<em><em><em><em><em>*if (aIndex < 0 || aIndex >= m_Points.Count) return;*</em></em></em></em></em>
<em><em><em><em><em>*m_Points[aIndex].position = aPosition;*</em></em></em></em></em>
<em><em><em><em>_*}*_</em></em></em></em>
<em><em><em><em>_*public void SetWidth(int aIndex, float aWidth)*_</em></em></em></em>
<em><em><em><em>_*{*_</em></em></em></em>
<em><em><em><em><em>*if (aIndex < 0 || aIndex >= m_Points.Count) return;*</em></em></em></em></em>
<em><em><em><em><em>*m_Points[aIndex].width = aWidth;*</em></em></em></em></em>
<em><em><em><em>_*}*_</em></em></em></em>
<em><em><em><em>_*public void SetColor(int aIndex, Color aColor)*_</em></em></em></em>
<em><em><em><em>_*{*_</em></em></em></em>
<em><em><em><em><em>*if (aIndex < 0 || aIndex >= m_Points.Count) return;*</em></em></em></em></em>
<em><em><em><em><em>*m_Points[aIndex].color = aColor;*</em></em></em></em></em>
<em><em><em><em>_*}*_</em></em></em></em>
<em><em><em><em>_*public void SetWidth(float aStartWidth, float aEndWidth)*_</em></em></em></em>
<em><em><em><em>_*{*_</em></em></em></em>
<em><em><em><em><em>*for (int i = 0; i < m_Points.Count; i++)*</em></em></em></em></em>
<em><em><em><em>_*{*_</em></em></em></em>
<em><em><em><em><em><em>m_Points*.width = Mathf.Lerp(aStartWidth,aEndWidth,(float)i/(m_Points.Count-1));*</em></em></em></em></em></em>
<em><em><em><em><em>_*}*_</em></em></em></em></em>
<em><em><em><em><em>_*}*_</em></em></em></em></em>
<em><em><em><em><em>_*public void SetColor(Color aStart, Color aEnd)*_</em></em></em></em></em>
<em><em><em><em><em>_*{*_</em></em></em></em></em>
<em><em><em><em><em><em>*for (int i = 0; i < m_Points.Count; i++)*</em></em></em></em></em></em>
<em><em><em><em><em>_*{*_</em></em></em></em></em>
<em><em><em><em><em><em><em>m_Points*.color = Color.Lerp(aStart,aEnd,(float)i/(m_Points.Count-1));*</em></em></em></em></em></em></em>
<em><em><em><em><em><em>_*}*_</em></em></em></em></em></em>
<em><em><em><em><em><em>_*}*_</em></em></em></em></em></em>
<em><em><em><em><em><em>_*}*_</em></em></em></em></em></em>
<em><em><em><em><em><em>_*```*_</em></em></em></em></em></em>
yoyo
May 16, 2011, 11:39pm
4
Eric5h5's Vectrosity package might suit your needs.
It's more than possible since you can render several thousand custom polygons if you really wanted to. It looks like your problem has to do with the equation of the upper red line. Also, if you split the colors into two different layers you can set the red and white areas on top of your drawing manually. It's tough to see what specifically is going wrong without the code behind it though...
For other's reference, here is a modification of the code by Bunny83 that tries to preserve the line thickness better. It does not work for angles close to 0, as you can see from the images below:
(Note: this is an unoptimised hack, provided for reference. It is not safe, and not fast!)
//note: dir is the path tangent, tangent is the binormal
void UpdateMesh(Camera aCamera)
{
Vector3 localViewPos = transform.InverseTransformPoint(aCamera.transform.position);
Vector3[] vertices = m_Mesh.vertices;
Vector3[] normals = m_Mesh.normals;
Color[] colors = m_Mesh.colors;
Vector3 oldTangent = Vector3.zero;
Vector3 oldDir = Vector3.zero;
for (int i = 0; i < m_Points.Count - 1; i++)
{
Vector3 faceNormal = (localViewPos - m_Points*.position).normalized;*
<em>Vector3 dir = (m_Points[i + 1].position - m_Points*.position);*</em>
_*Vector3 tangent = Vector3.Cross(dir, faceNormal).normalized;*_
_*Vector3 offset;*_
_*if (i == 0)*_
_*{*_
<em><em>offset = (oldTangent + tangent).normalized * m_Points*.width / 2.0f;*</em></em>
<em>_*}*_</em>
<em>_*else*_</em>
<em>_*{*_</em>
<em>_*float alpha = (Mathf.PI - Mathf.Acos(Vector3.Dot(oldDir.normalized, dir.normalized))) / 2;*_</em>
<em><em><em>float d = m_Points*.width / 2.0f / Mathf.Sin(alpha);*</em></em></em>
<em><em><em>_d *= -Mathf.Sign(Vector3.Dot(tangent.normalized, oldDir.normalized));_</em></em></em>
<em><em><em>_offset = ((dir.normalized - oldDir.normalized) / 2).normalized * d;_</em></em></em>
<em><em>_*}*_</em></em>
<em><em><em><em>vertices[i * 2] = m_Points*.position - offset;*</em></em></em></em>
<em><em><em><em><em>vertices[i * 2 + 1] = m_Points*.position + offset;*</em></em></em></em></em>
<em><em><em><em><em>_normals[i * 2] = normals[i * 2 + 1] = faceNormal;_</em></em></em></em></em>
<em><em><em><em><em><em>colors[i * 2] = colors[i * 2 + 1] = m_Points*.color;*</em></em></em></em></em></em>
<em><em><em><em><em><em>*if (i == m_Points.Count - 2)*</em></em></em></em></em></em>
<em><em><em><em><em>_*{*_</em></em></em></em></em>
<em><em><em><em><em>_*// last two points*_</em></em></em></em></em>
<em><em><em><em><em><em><em>vertices[i * 2 + 2] = m_Points[i + 1].position - tangent * m_Points[i + 1].width / 2.0f;</em></em></em></em></em></em></em>
<em><em><em><em><em><em><em>vertices[i * 2 + 3] = m_Points[i + 1].position + tangent * m_Points[i + 1].width / 2.0f;</em></em></em></em></em></em></em>
<em><em><em><em><em><em>_normals[i * 2 + 2] = normals[i * 2 + 3] = faceNormal;_</em></em></em></em></em></em>
<em><em><em><em><em><em><em>colors[i * 2 + 2] = colors[i * 2 + 3] = m_Points[i + 1].color;</em></em></em></em></em></em></em>
<em><em><em><em><em>_*}*_</em></em></em></em></em>
<em><em><em><em><em>_*oldDir = dir;*_</em></em></em></em></em>
<em><em><em><em><em>_*oldTangent = tangent;*_</em></em></em></em></em>
<em><em><em><em><em>_*}*_</em></em></em></em></em>
<em><em><em><em><em><em>*m_Mesh.vertices = vertices;*</em></em></em></em></em></em>
<em><em><em><em><em><em>*m_Mesh.normals = normals;*</em></em></em></em></em></em>
<em><em><em><em><em><em>*m_Mesh.colors = colors;*</em></em></em></em></em></em>
<em><em><em><em><em><em>*m_Mesh.uv = m_UVs;*</em></em></em></em></em></em>
<em><em><em><em><em><em>*m_Mesh.SetTriangleStrip(m_Indices, 0);*</em></em></em></em></em></em>
<em><em><em><em><em><em>*m_Mesh.RecalculateBounds();*</em></em></em></em></em></em>
<em><em><em><em><em>_*}*_</em></em></em></em></em>
<em><em><em><em><em>_*```*_</em></em></em></em></em>