Converting 2d sprite to mesh - looking better algorithm

First off, I apologize if I use incorrect terminology…this is my first time tackling any kind of procedural mesh generation and delving into how mesh/sprites work in general. This was a fun problem to work on, would love it if I could finish it up with the communities help!


My goal is to create a ‘thick sprite’. Essentially I wanted to convert 2d sprites into a 3d mesh version with depth. Imagine taking a sprite in the xy plane and extruding it out along the z plane. The front and back face (I’ll call these sprite faces) of this mesh would be 1:1 mapped to the sprite and the edge meshes would just be whatever color the outer vertices are.

I got this working, but with a very naïve algorithm that fully connects all edges of the sprite face’s triangles instead of just creating triangles to bridge the outer edges of the sprite faces. The end result does properly create triangles around the outer edges that connect the front and back sprite faces, but also creates triangles running through the inside of the mesh as well. This creates lighting issues if I recalculate normals of the mesh, as well as subtle lighting issue even without recalculating normals. I assume this will create perf issues eventually too. I’ll walk through my current approach below, but my question is: Is there a good way to draw only the required triangles connecting the two sprite faces?


[187691-unity-stickman.png*|187691]
[187692-unity-stickman-wire.png*|187692]


Pseudocode

I’ll post the actual code below, but here’s the pseudocode to convert the sprite to a mesh:

Create a new mesh using vertices, triangles, and uvs derived from the Sprite object


** -Vertices**

  • create a list of vertices from Sprite.vertices
  • duplicate this list to create the ‘back vertices’
  • shift the back vertices in the z
  • combine the two lists and assign to a temp mesh object

-Triangles

  • create a list of triangles from Sprite.triangles
  • duplicate that list to create the back triangles
  • Process the back triangles to make sure the ints representing index in the vert array point towards the backface vertices
  • Flip the normals on the back triangles
  • Create a new list of triangles to connect the two planar meshes. As discussed, this naïve approach will connect all front edges to back edges rather than just the outer edges.
  • combine triangle lists into array and assign to temp mesh
  • Notice that I run mesh.recalculateNormals before adding the middle triangles, otherwise lighting gets messed up

-UVs

  • grab uvs from Sprite.uvs, duplicate them to account for the back face, and assign that back to the temp mesh

What I would love to do is figure out how to only create the triangles on the outer edges instead of having to connect all edges. The problem I face is that the Sprite.vertices seems to not be ordered in any patter that I can interpret. In order for me to only create outer triangles, I would need to need to know which vertices neighbor eachother on the front and back face. If I could somehow reorder the vertices array (and then in turn the indices in the triangles) then I could potentially iterate through the vertices and create triangles connecting to the neighbors.


I cannot think of a clever way to do this, so I’m asking the community! This was a rather interesting problem and I’d love to hear from someone with more knowledge in the field than me.


For reference, the below code is put on a gameobject in scene as a component. I then have an editor script that creates a button that calls the ‘MakeMesh’ function. m_pokeSprite is populated at editor time by draggin the desired sprite into the field. m_tempMesh is an object in scene with a MeshFilter and MeshRenderer component. The material on the renderer is an opaque standard shader material with the desired sprite in the ‘albedo’ field.


public class MeshMaker : MonoBehaviour
    {
        public Sprite m_pokeSprite;
        public MeshFilter m_tempMesh;

        public void MakeMesh()
        {
            Mesh mesh = new Mesh();

            //create vertices
            List<Vector3> vertices = new List<Vector3>();
            vertices.AddRange(Array.ConvertAll(m_pokeSprite.vertices, i => (Vector3)i));
            List<Vector3> backVertices = new List<Vector3>(vertices);
            backVertices = ShiftVertices(backVertices);
            vertices.AddRange(backVertices);
            mesh.vertices = vertices.ToArray();

            //create triangles
            List<int> triangles = new List<int>();
            triangles.AddRange(Array.ConvertAll(m_pokeSprite.triangles, i => (int)i));
            List<int> backTriangles = new List<int>(triangles);
            backTriangles = ShiftAndFlipTriangleIndexes(backTriangles, backVertices.Count);
            List<int> middleTriangels = CreateMiddleTriangles(triangles, backTriangles);
            triangles.AddRange(backTriangles);
            mesh.triangles = triangles.ToArray();
            mesh.RecalculateNormals();
            triangles.AddRange(middleTriangels);
            mesh.triangles = triangles.ToArray();

            //create uvs
            List<Vector2> uvs = new List<Vector2>(m_pokeSprite.uv);
            List<Vector2> newUvs = new List<Vector2>(uvs);
            uvs.AddRange(newUvs);
            mesh.uv = uvs.ToArray();

            //assign to mesh in scene and save mesh as asset
            m_tempMesh.sharedMesh = mesh;
            AssetDatabase.CreateAsset(mesh, "Assets/meshSaved.asset");
        }

        private List<Vector3> ShiftVertices(List<Vector3> verticies)
        {
            float dist = 1f;

            for (int i = 0; i < verticies.Count; i++)
            {
                verticies <em>= new Vector3(verticies<em>.x, verticies_.y, verticies*.z + dist);*_</em></em>

}

return verticies;
}

private List ShiftAndFlipTriangleIndexes(List indecies, int shiftAmount)
{
List toReturn = new List(indecies);
for (int i = 0; i < toReturn.Count; i++)
{
toReturn += shiftAmount;
}

for (int i = 0; i < toReturn.Count; i+=3)
{
int i0 = toReturn*;*
int i2 = toReturn[i+2];

toReturn = i2;
toReturn[i + 2] = i0;
}

return toReturn;
}

private List CreateMiddleTriangles(List frontTriangles, List backTriangles)
{
List toReturn = new List();

for (int i = 0; i < frontTriangles.Count; i += 3)
{
toReturn.AddRange(MakeQuad(frontTriangles*, frontTriangles[i + 1], backTriangles[i + 2], backTriangles[i + 1]));*
toReturn.AddRange(MakeQuad(frontTriangles[i + 1], frontTriangles[i + 2], backTriangles[i + 1], backTriangles*));*
toReturn.AddRange(MakeQuad(frontTriangles[i + 2], frontTriangles_, backTriangles*, backTriangles[i + 2]));
}*_

return toReturn;
}

private List MakeQuad(int x0, int x1, int y0, int y1)
{
List toReturn = new List();

toReturn.Add(x0);
toReturn.Add(y0);
toReturn.Add(y1);

toReturn.Add(x0);
toReturn.Add(y1);
toReturn.Add(x1);

return toReturn;
}
}
*
*

Do you have the mesh data for the 2d sprite, so the triangles and verts used to create the front face of the mesh. NOT a quad.


If so, store the vertices and triangles in two arrays. Now, clone the vertex array and triangle array, but for each vertex in the second vertex array, shift them by the depth of your model. Essentially, you’ll have the same face as before, but moved back on the z axis.


One important step, is to flip the triangles for the second triangle array. This is because you want the back face to be facing the opposite direction of the front face, to appear outside the mesh. You can do this by looping through the triangle array in intervals of 3, and swapping the 3rd triangle index with the 1st one. This will flip all the triangles.


Finally, you need to connect the two faces using quads on the side. This step is a bit easier, since the faces are identical on both sides of the mesh. Create a new vertex list and triangle list, and use a for loop for each vertex in both the first and second vertex array. Take the first two vertices in both arrays, create a quad using triangles, and add the triangles and vertices to your new vertex list and triangle list. This will create the quads that connect the two faces together.


Creating a quad is quite simple, however they may face the same way. If you can, try to ensure the normal of the triangle is facing outward with math (like dot product or smt). Have a look on the internet on how to do that. If you can’t, just clone the triangle, and flip it. This will cover both back and front faces, but should still give you the desired effect.


This is the end of the algorithm. You have two vertex and triangle arrays for your back and front faces, and one vertex and triangle array that connects them. It isn’t safe to just connect them into one mesh yet. You’ll need to create one large vertex and triangle list, and use addrange to add the first set of vertices and triangles. However, when adding the second set of triangles and verts, offset the triangles by the number of vertices that were previously in the array. This is so the triangles correspond to the right vertices. Repeat for the third set of arrays.


This algorithm is simple in theory, but there may be some mistakes or unclear explanations. If you have questions, let me know. @MegaMax5000