Archive
This post is archived and may contain outdated information. It has been set to 'noindex' and should stop showing up in search results.
Create 2D Mesh Outline in Unity (Silhouette)
Mar 6, 2017ProgrammingComments (2)
This code creates an outline of a 2D mesh utilizing the LineRenderer component in Unity. It does not require any shaders and seems to run very quickly from my testing. It also supports meshes that have mutliple non-connected components.

How to Use It
To use it, create an OutlineCreator C# script, copy the below code in, and add the Script component to a GameObject that has a 2D mesh. You can specify the line width and material it should use, which are the two public configurable options. When you hit Play, the script will generate child GameObjects with Line Renderers.

OutlineCreator C# Code
using System.Collections.Generic;
using UnityEngine;

public class OutlineCreator : MonoBehaviour
{
public float lineWidth;
public Material material;

void Start()
{
// Stop if no mesh filter exists
if (GetComponent<MeshFilter>() == null) {
return;
}

// Get triangles and vertices from mesh
int[] triangles = GetComponent<MeshFilter>().mesh.triangles;
Vector3[] vertices = GetComponent<MeshFilter>().mesh.vertices;

// Get just the outer edges from the mesh's triangles (ignore or remove any shared edges)
Dictionary<string, KeyValuePair<int, int>> edges = new Dictionary<string, KeyValuePair<int, int>>();
for (int i = 0; i < triangles.Length; i += 3) {
for (int e = 0; e < 3; e++) {
int vert1 = triangles[i + e];
int vert2 = triangles[i + e + 1 > i + 2 ? i : i + e + 1];
string edge = Mathf.Min(vert1, vert2) + ":" + Mathf.Max(vert1, vert2);
if (edges.ContainsKey(edge)) {
edges.Remove(edge);
} else {
edges.Add(edge, new KeyValuePair<int, int>(vert1, vert2));
}
}
}

// Create edge lookup Dictionary
Dictionary<int, int> lookup = new Dictionary<int, int>();
foreach (KeyValuePair<int, int> edge in edges.Values) {
if (lookup.ContainsKey(edge.Key) == false) {
lookup.Add(edge.Key, edge.Value);
}
}

// Create line prefab
LineRenderer linePrefab = new GameObject().AddComponent<LineRenderer>();
linePrefab.transform.name = "Line";
linePrefab.numPositions = 0;
linePrefab.material = material;
linePrefab.startWidth = linePrefab.endWidth = lineWidth;

// Create first line
LineRenderer line = Instantiate(linePrefab.gameObject).GetComponent<LineRenderer>();
line.transform.parent = transform;

// This vector3 gets added to each line position, so it sits in front of the mesh
// Change the -0.1f to a positive number and it will sit behind the mesh
Vector3 bringFoward = new Vector3(0f, 0f, -0.1f);

// Loop through edge vertices in order
int startVert = 0;
int nextVert = startVert;
int highestVert = startVert;
while (true) {

// Add to line
line.numPositions++;
line.SetPosition(line.numPositions - 1, vertices[nextVert] + bringFoward);

// Get next vertex
nextVert = lookup[nextVert];

// Store highest vertex (to know what shape to move to next)
if (nextVert > highestVert) {
highestVert = nextVert;
}

// Shape complete
if (nextVert == startVert) {

// Finish this shape's line
line.numPositions++;
line.SetPosition(line.numPositions - 1, vertices[nextVert] + bringFoward);

// Go to next shape if one exists
if (lookup.ContainsKey(highestVert + 1)) {

// Create new line
line = Instantiate(linePrefab).GetComponent<LineRenderer>();
line.transform.parent = transform;

// Set starting and next vertices
startVert = highestVert + 1;
nextVert = startVert;

// Continue to next loop
continue;
}

// No more verts
break;
}
}
}
}
Comments (2)
Add a Comment
Nick Vogt   May 19, 2017
You're right. I had a prefab that I forgot about. I just updated the code to no longer need that external prefab. The prefab is now created in the code, and it's a little cleaner as well.
Jarrett Dunn   May 18, 2017
Question, did you leave out a step as in line 44 you are setting up a lineprefab for a game object which does not exist by default. The Resources.LoadGameObject>("Levels/Line");